feat(umind): los agentes también leen archivos, no solo audios e imágenes

Faltaba la tercera pata: audio pasaba por Whisper, imagen por OCR, y un PDF o
un Word adjunto se ignoraba en silencio. Ahora hay un interruptor por canal
—"Leer archivos adjuntos"— y los documentos que llegan por Telegram o WhatsApp
se convierten a texto antes de pasar al agente.

PDF se lee con stdlib cuando el documento es digital (facturas, cotizaciones,
lo exportado por cualquier programa) y cae al OCR si es un escaneo. Word .docx
es un zip con XML adentro; texto plano, CSV y JSON van directo.

El agente recibe el contenido con contexto ("el cliente adjuntó X, y escribió
Y"), no el chorizo pelado: sin eso el modelo contesta como si el cliente
hubiera tipeado una factura.

De paso la importación de plantillas gana PDF, que usa el mismo extractor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-17 18:50:59 -05:00
co-authored by Claude Opus 5
parent e681f41639
commit c4b0305112
14 changed files with 384 additions and 124 deletions
@@ -58,6 +58,12 @@ func ProcesarMensajeWhatsAppUmind(canal *models.UmindCanal, from, texto string)
// Si la conversión no está habilitada para ese tipo, se ignora en silencio
// (mismo comportamiento de antes de que existiera esta función).
func ProcesarMediaWhatsAppUmind(canal *models.UmindCanal, from, mediaID, tipo string) error {
return ProcesarMediaWhatsAppUmindConNombre(canal, from, mediaID, tipo, "", "")
}
// ProcesarMediaWhatsAppUmindConNombre es la versión completa: los documentos
// traen nombre de archivo y, a veces, un texto que los acompaña (caption).
func ProcesarMediaWhatsAppUmindConNombre(canal *models.UmindCanal, from, mediaID, tipo, nombreArchivo, caption string) error {
agente, err := models.GetUmindAgenteByID(canal.AgenteID)
if err != nil || !agente.Activo {
return fmt.Errorf("agente no encontrado o inactivo: %w", err)
@@ -96,6 +102,19 @@ func ProcesarMediaWhatsAppUmind(canal *models.UmindCanal, from, mediaID, tipo st
if err != nil {
return err
}
case "document":
if !canal.UsarArchivosDocs {
return nil
}
data, _, err := descargarMediaWhatsApp(credenciales["access_token"], mediaID)
if err != nil {
return fmt.Errorf("no se pudo descargar el archivo de WhatsApp: %w", err)
}
texto, err = ExtraerTextoDeArchivo(canal.AgenteID, nombreArchivo, data)
if err != nil {
return err
}
texto = TextoDeArchivoParaAgente(nombreArchivo, caption, texto)
default:
return nil
}