feat: log de auditoría técnica por agente

Nueva tabla umind_eventos_log + tab "Auditoría" en el panel: errores y
eventos que hasta ahora solo se podían ver pidiendo los logs del servidor
(fallos al llamar al AI, respuestas sin texto usable, tools/webhooks que
fallan, tokens de correo que no se pueden refrescar, firmas de WhatsApp
inválidas, errores procesando mensajes de Telegram/WhatsApp) quedan
registrados por agente, visibles directo en el panel con el JSON crudo
expandible para diagnosticar sin acceso al servidor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-13 09:58:59 -05:00
co-authored by Claude Sonnet 5
parent 5febbd24d5
commit 997e3cc790
13 changed files with 157 additions and 31 deletions
+10
View File
@@ -59,12 +59,14 @@ func umindTools(agenteID uint) []agentTool {
herramientas, err := models.GetUmindHerramientasActivas(agenteID)
if err != nil {
log.Printf("[UMIND] Error leyendo tools custom del agente %d: %v", agenteID, err)
models.RegistrarEventoUmind(agenteID, "error", "tool", "Error leyendo tools custom", err.Error())
return tools
}
for _, h := range herramientas {
params, err := models.ParametrosFromJSON(h.ParametrosJSON)
if err != nil {
log.Printf("[UMIND] Tool %q del agente %d tiene parametros_json inválido, se omite: %v", h.Nombre, agenteID, err)
models.RegistrarEventoUmind(agenteID, "warn", "tool", fmt.Sprintf("Tool %q tiene parametros_json inválido, se omitió", h.Nombre), err.Error())
continue
}
props := map[string]agentToolParam{}
@@ -168,12 +170,14 @@ func executeUmindTool(agenteID uint, name string, args map[string]interface{}) s
authValor, err = DescifrarSecretoUmind(herramienta.AuthHeaderValorEnc)
if err != nil {
log.Printf("[UMIND] Error descifrando credencial de tool %q: %v", name, err)
models.RegistrarEventoUmind(agenteID, "error", "tool", fmt.Sprintf("Error descifrando credencial de la tool %q", name), err.Error())
return `{"error": "la tool no está configurada correctamente"}`
}
}
resultado, err := LlamarHerramientaWebhook(herramienta.URL, herramienta.AuthHeaderNombre, authValor, args)
if err != nil {
log.Printf("[UMIND] Error llamando tool %q del agente %d: %v", name, agenteID, err)
models.RegistrarEventoUmind(agenteID, "error", "tool", fmt.Sprintf("Error llamando la tool %q", name), err.Error())
return fmt.Sprintf(`{"error": %q}`, "no se pudo completar la acción, intenta de nuevo")
}
return resultado
@@ -189,6 +193,7 @@ func executeUmindEmailTool(agenteID uint, name string, args map[string]interface
}
if err := RefrescarSiVence(conexion); err != nil {
log.Printf("[UMIND] Error refrescando token OAuth (conexión %d): %v", conexion.ID, err)
models.RegistrarEventoUmind(agenteID, "error", "email", "Error refrescando el token de la cuenta de correo conectada", err.Error())
return `{"error": "no se pudo usar la cuenta de correo conectada, intenta más tarde"}`
}
@@ -208,6 +213,7 @@ func executeUmindEmailTool(agenteID uint, name string, args map[string]interface
}
if envErr != nil {
log.Printf("[UMIND] Error enviando correo (agente %d): %v", agenteID, envErr)
models.RegistrarEventoUmind(agenteID, "error", "email", "Error enviando correo", envErr.Error())
return `{"error": "no se pudo enviar el correo"}`
}
return `{"ok": true}`
@@ -223,6 +229,7 @@ func executeUmindEmailTool(agenteID uint, name string, args map[string]interface
}
if lecErr != nil {
log.Printf("[UMIND] Error leyendo bandeja (agente %d): %v", agenteID, lecErr)
models.RegistrarEventoUmind(agenteID, "error", "email", "Error leyendo la bandeja de correo", lecErr.Error())
return `{"error": "no se pudo leer la bandeja"}`
}
b, _ := json.Marshal(map[string]interface{}{"resultados": resultados})
@@ -279,6 +286,7 @@ func ProcessWidgetMessage(agente *models.UmindAgente, sessionID, userText string
aiMsg, err := callAI(&ai, messages, tools)
if err != nil {
log.Printf("[UMIND] Error llamando AI (agente %d) round %d: %v", agente.ID, round, err)
models.RegistrarEventoUmind(agente.ID, "error", "ai", fmt.Sprintf("Error contactando el AI (ronda %d)", round), err.Error())
return "", fmt.Errorf("error al contactar el sistema de IA")
}
@@ -293,6 +301,7 @@ func ProcessWidgetMessage(agente *models.UmindAgente, sessionID, userText string
// "dame más detalle" sin ninguna pista de qué pasó. Con .Raw (ver
// agentMessage.UnmarshalJSON) queda el JSON crudo para diagnosticar.
log.Printf("[UMIND] Respuesta del AI sin texto usable (agente %d) round %d: %s", agente.ID, round, string(aiMsg.Raw))
models.RegistrarEventoUmind(agente.ID, "warn", "ai", fmt.Sprintf("El AI respondió sin texto usable (ronda %d)", round), string(aiMsg.Raw))
}
finalResponse = content
if strings.TrimSpace(content) != "" {
@@ -317,6 +326,7 @@ func ProcessWidgetMessage(agente *models.UmindAgente, sessionID, userText string
if finalResponse == "" {
log.Printf("[UMIND] Agente %d: se agotaron las rondas de tool-calling sin una respuesta final", agente.ID)
models.RegistrarEventoUmind(agente.ID, "warn", "ai", "Se agotaron las rondas de tool-calling sin una respuesta final", "")
finalResponse = "Un momento, por favor — dame un poco más de detalle sobre lo que necesitas."
}
return finalResponse, nil