diff --git a/pkg/services/tarea_service.go b/pkg/services/tarea_service.go index 24f101a..f2c5804 100644 --- a/pkg/services/tarea_service.go +++ b/pkg/services/tarea_service.go @@ -1,7 +1,10 @@ package services import ( + "encoding/json" "fmt" + "os" + "path/filepath" "time" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" @@ -65,6 +68,53 @@ func AsignarTarea(id uint, asignadoID *uint) (*models.Tarea, error) { return full, nil } +// AdjuntarDocumentoTarea guarda como comentario de una tarea el archivo (PDF/foto) +// que el usuario acaba de enviar por Telegram en este chat. Reutiliza el mismo +// formato de "archivos" (JSON) que ya usa el comentario web (AddComentario), para +// que el archivo aparezca igual en /app/tareas sin importar de dónde se subió. +func AdjuntarDocumentoTarea(chatID int64, tareaID uint, comentario string) (*models.TareaComentario, error) { + if tareaID == 0 { + return nil, fmt.Errorf("id de tarea requerido") + } + tarea, err := models.GetTareaByID(tareaID) + if err != nil { + return nil, fmt.Errorf("tarea no encontrada: %w", err) + } + att, ok := PopTelegramAttachment(chatID) + if !ok { + return nil, fmt.Errorf("no hay ningún documento pendiente en este chat; pide al usuario que lo envíe de nuevo") + } + if comentario == "" { + comentario = fmt.Sprintf("Archivo adjunto vía Telegram: %s", att.OriginalName) + } + + dir := fmt.Sprintf("uploads/tareas/%d", tareaID) + if err := os.MkdirAll(dir, 0755); err != nil { + _ = os.Remove(att.Path) + return nil, fmt.Errorf("no se pudo crear el directorio: %w", err) + } + ext := filepath.Ext(att.OriginalName) + finalPath := filepath.Join(dir, fmt.Sprintf("%d_telegram%s", time.Now().UnixMilli(), ext)) + if err := os.Rename(att.Path, finalPath); err != nil { + return nil, fmt.Errorf("no se pudo guardar el archivo: %w", err) + } + + archivosJSON, _ := json.Marshal([]map[string]string{ + {"nombre": att.OriginalName, "ruta": finalPath}, + }) + + c := &models.TareaComentario{ + TareaID: tareaID, + Contenido: comentario, + Archivos: string(archivosJSON), + } + if err := models.CreateComentario(c); err != nil { + return nil, fmt.Errorf("no se pudo guardar el comentario: %w", err) + } + go NotificarTareaComentario(tarea, comentario) + return c, nil +} + func cambioAsignado(a, b *uint) bool { if a == nil && b == nil { return false diff --git a/pkg/services/telegram_agent_service.go b/pkg/services/telegram_agent_service.go index 4057115..9ed7335 100644 --- a/pkg/services/telegram_agent_service.go +++ b/pkg/services/telegram_agent_service.go @@ -362,6 +362,11 @@ func agentTools() []agentTool { "id": num("ID de la tarea"), "asignado_id": num("ID del usuario responsable (usa listar_usuarios si no lo sabes)"), }, []string{"id", "asignado_id"})), + tool("adjuntar_documento_tarea", "Guarda como comentario de una tarea el archivo (PDF/foto) que el usuario acaba de enviar por Telegram en este chat. Úsala cuando el usuario mande un archivo junto con instrucciones sobre una tarea (nueva o existente). Solo funciona si hay un archivo adjunto pendiente. Si la tarea es nueva, primero créala con crear_tarea y usa el id que devuelve.", + obj(map[string]agentToolParam{ + "id": num("ID de la tarea a la que se adjunta el archivo"), + "comentario": str("Comentario que acompaña al archivo (opcional)"), + }, []string{"id"})), tool("actualizar_estado_tarea", "Cambia el estado de una tarea. Los valores deben coincidir con las columnas del tablero Kanban del dashboard.", obj(map[string]agentToolParam{ "id": num("ID de la tarea"), @@ -1044,6 +1049,13 @@ func runTool(chatID int64, name string, a map[string]interface{}) (interface{}, } return map[string]interface{}{"ok": true, "id": t.ID, "asignado_id": asignadoID}, nil + case "adjuntar_documento_tarea": + c, err := AdjuntarDocumentoTarea(chatID, uint(getInt("id", 0)), getStr("comentario")) + if err != nil { + return nil, err + } + return map[string]interface{}{"ok": true, "comentario_id": c.ID, "tarea_id": c.TareaID}, nil + case "actualizar_estado_tarea": id := uint(getInt("id", 0)) estado := getStr("estado") @@ -1497,9 +1509,10 @@ COMPORTAMIENTO: - Cuando el mensaje empiece con "[Documento adjunto recibido: ...]", el usuario acaba de enviar un PDF o foto por Telegram y puedes leerlo directamente (está adjunto a este mismo mensaje, no es solo un nombre de archivo). Decide primero de qué se trata el archivo antes de guardarlo: · Si es una factura → primero determina la dirección leyendo quién emite y quién recibe: si el emisor/vendedor es "U-Site S.A.S BIC" (o similar) y el destinatario es un cliente, es una factura de VENTA → identifica cliente, monto y número, resuelve el cliente_id con listar_clientes, y llama a adjuntar_factura. Si es al revés — un proveedor (AWS, Coolify, un hosting, cualquier tercero) le está facturando A U-Site — es una factura de COMPRA → llama a adjuntar_factura_compra con el nombre de ese proveedor, no busques cliente_id. Ante la duda, mira a nombre de quién sale la plata: si U-Site cobra, es venta; si U-Site paga, es compra · Si es un documento de un proyecto (contrato, orden de servicio, entregable, etc.) → identifica a qué proyecto corresponde (usa listar_proyectos si hace falta) y llama a adjuntar_documento_proyecto - Si logras identificar los datos necesarios (del documento o de lo que escribió el usuario), guarda directamente sin pedir confirmación — solo pregunta si de verdad no hay forma de determinar el cliente/proyecto o falta un dato imprescindible. No uses estas tools si el mensaje no menciona ningún documento adjunto + · Si el mensaje habla de una tarea (ej: "asigna esto a Natalia", "esta es la tarea para el diseño") → usa adjuntar_documento_tarea. Si la tarea todavía no existe, créala primero con crear_tarea (con asignado_id si corresponde) y luego llama a adjuntar_documento_tarea con el id que te devolvió; si ya existe, resuélvela con listar_tareas + Si logras identificar los datos necesarios (del documento o de lo que escribió el usuario), guarda directamente sin pedir confirmación — solo pregunta si de verdad no hay forma de determinar el cliente/proyecto/tarea o falta un dato imprescindible. No uses estas tools si el mensaje no menciona ningún documento adjunto - Para registrar un proyecto nuevo con sus fases (el usuario puede describirlo en texto o mandarlo en un documento), primero usa crear_proyecto y luego llama a crear_fase_proyecto una vez por cada fase que corresponda — no hace falta preguntar confirmación entre cada fase -- Para asignar una tarea a alguien, resuelve el nombre con listar_usuarios y usa asignado_id en crear_tarea (si es nueva) o asignar_tarea (si ya existe) — la persona asignada recibe una notificación +- Para asignar una tarea a alguien, resuelve el nombre con listar_usuarios y usa asignado_id en crear_tarea (si es nueva) o asignar_tarea (si ya existe) — la persona asignada recibe una notificación. Si además el usuario mandó un archivo junto con la instrucción de asignar, después de crear/asignar la tarea usa también adjuntar_documento_tarea para guardar ese archivo en la tarea COMANDOS ESPECIALES (el usuario puede escribirlos): - /reset — olvidar el historial de esta conversación