Corrige adjuntos no-imagen (docx, xlsx, etc.) en Telegram con Claude

Un archivo adjunto que no fuera PDF o imagen se mandaba igual como
bloque "image" a la API de Claude, que la rechaza (400) y tumbaba toda
la respuesta del agente. Ahora solo se intenta previsualizar tipos que
Claude soporta (PDF/jpeg/png/webp/gif); cualquier otro formato se
guarda igual con adjuntar_documento_tarea/proyecto/factura, solo que
la IA no puede leer su contenido, únicamente el nombre.
This commit is contained in:
Lizandro GD
2026-08-04 22:57:58 +00:00
parent e5849549a0
commit 25cde836f1
2 changed files with 30 additions and 9 deletions
@@ -141,3 +141,16 @@ func ResolverMimeType(mimeType, filename string) string {
return "application/octet-stream"
}
}
// esMimePrevisualizablePorClaude indica si la API de Claude puede recibir ese
// tipo de archivo como contenido visible (bloque "image" o "document"). Un
// tipo fuera de esta lista (docx, xlsx, zip, etc.) mandado igual como bloque
// "image" hace que Anthropic responda 400 y tumbe la respuesta del agente.
func esMimePrevisualizablePorClaude(mime string) bool {
switch mime {
case "application/pdf", "image/jpeg", "image/png", "image/webp", "image/gif":
return true
default:
return false
}
}
+17 -9
View File
@@ -321,7 +321,7 @@ func agentTools() []agentTool {
"orden": num("Orden de la fase dentro del proyecto (0, 1, 2...)"),
"fecha_estimada": str("Fecha estimada de entrega, formato YYYY-MM-DD"),
}, []string{"proyecto_id", "nombre"})),
tool("adjuntar_documento_proyecto", "Guarda como documento del proyecto el archivo (PDF/foto) que el usuario acaba de enviar por Telegram en este chat. Solo funciona si hay un archivo adjunto pendiente.",
tool("adjuntar_documento_proyecto", "Guarda como documento del proyecto el archivo (PDF, foto, Word, Excel o cualquier otro tipo) que el usuario acaba de enviar por Telegram en este chat. Solo funciona si hay un archivo adjunto pendiente.",
obj(map[string]agentToolParam{
"proyecto_id": num("ID del proyecto (usa listar_proyectos si no lo sabes)"),
"tipo": agentToolParam{Type: "string", Enum: []string{"contrato", "orden_servicio", "otro"}, Description: "Tipo de documento (default otro)"},
@@ -362,7 +362,7 @@ 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.",
tool("adjuntar_documento_tarea", "Guarda como comentario de una tarea el archivo (PDF, foto, Word, Excel o cualquier otro tipo) 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), sin importar el formato. 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)"),
@@ -1506,7 +1506,7 @@ COMPORTAMIENTO:
- Los valores monetarios son en COP (pesos colombianos)
- Si una herramienta falla, explica el error y sugiere alternativas
- En Coolify: si solo hay una instancia activa se usa automáticamente, no hace falta preguntar por config_id; si hay varias, consulta coolify_instancias y pregunta cuál. Para actuar sobre algo necesitas su UUID: búscalo con coolify_listar por nombre antes de llamar a coolify_accion. Reiniciar/desplegar es rutinario, hazlo directo; detener algo en producción sí confírmalo antes. Si un despliegue falla, revisa coolify_logs y coolify_deployments para explicar la causa concreta
- 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:
- Cuando el mensaje empiece con "[Documento adjunto recibido: ...]", el usuario acaba de enviar un archivo por Telegram (puede ser cualquier formato: PDF, foto, Word, Excel, etc.). Si es PDF o foto, viene adjunto a este mismo mensaje y puedes leerlo directamente. Si es otro formato (Word, Excel, zip, etc.) no puedes ver su contenido, solo el nombre — de todas formas guárdalo con la herramienta que corresponda usando lo que el usuario haya escrito como contexto, no rechaces el archivo por el formato. 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 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
@@ -1583,14 +1583,22 @@ Comandos: /reset /instancias /ayuda`, nil
userMsg := agentMessage{Role: "user", Content: userText}
// Si hay un documento/foto pendiente en este chat, se lo pasamos al modelo
// como archivo real (solo Anthropic lo lee de verdad) en vez de que la IA
// tenga que adivinar cliente/monto a partir del nombre del archivo.
// tenga que adivinar cliente/monto a partir del nombre del archivo. Solo se
// hace esto para tipos que la API de Claude sabe previsualizar (imagen o
// PDF): un .docx/.xlsx/etc mandado como bloque "image" lo rechaza la API
// (400) y tumba toda la respuesta. Para esos, el archivo se queda igual
// adjunto/guardado (adjuntar_documento_tarea, etc.), solo que la IA no
// puede "leer" su contenido, solo el nombre.
if strings.ToLower(ai.Provider) == "anthropic" {
if att, ok := PeekTelegramAttachment(chatID); ok {
if data, err := os.ReadFile(att.Path); err == nil {
userMsg.AttachmentBase64 = base64.StdEncoding.EncodeToString(data)
userMsg.AttachmentMime = ResolverMimeType(att.MimeType, att.OriginalName)
} else {
log.Printf("[AGENT] No se pudo leer el adjunto pendiente (%s): %v", att.Path, err)
mime := ResolverMimeType(att.MimeType, att.OriginalName)
if esMimePrevisualizablePorClaude(mime) {
if data, err := os.ReadFile(att.Path); err == nil {
userMsg.AttachmentBase64 = base64.StdEncoding.EncodeToString(data)
userMsg.AttachmentMime = mime
} else {
log.Printf("[AGENT] No se pudo leer el adjunto pendiente (%s): %v", att.Path, err)
}
}
}
}