From e9cb8107eb58500d139edcc4a1ce57463aeccd14 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:01:02 -0500 Subject: [PATCH] fix(telegram): el monto de una factura se guardaba en cero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El bot informó "FACTURA GUARDADA EXITOSAMENTE" con $290.000 y el registro no aparecía. Revisando el camino salieron tres cosas, y las tres explican por qué el mensaje del bot no se podía tomar como prueba de nada. 1. Los montos se leían con getInt, que solo acepta números JSON. Si el modelo manda el monto como texto ("290000", "$290.000") devuelve el valor por defecto: 0. Y con decimales los truncaba. La factura quedaba creada en cero mientras el bot informaba el monto correcto, porque el bot repite lo que dijo el usuario, no lo que se guardó. Ahora hay un getFloat que acepta número o texto y limpia $ , y espacios. 2. Se registraba la llamada a la herramienta pero no su resultado, así que no había forma de saber si guardó o devolvió error. Ahora se registra también el resultado: es la única fuente confiable de qué pasó, porque el mensaje del bot lo escribe el modelo. 3. El prompt no prohibía informar éxito sin haberlo verificado. Ahora se le exige no decir que guardó algo si la herramienta devolvió error, y confirmar con los valores que devolvió la herramienta y no con los que dijo el usuario. Co-Authored-By: Claude Sonnet 5 --- pkg/services/telegram_agent_service.go | 53 ++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/pkg/services/telegram_agent_service.go b/pkg/services/telegram_agent_service.go index 9fae16e..da0c9a2 100644 --- a/pkg/services/telegram_agent_service.go +++ b/pkg/services/telegram_agent_service.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "path/filepath" + "strconv" "strings" "time" @@ -453,6 +454,29 @@ func runTool(chatID int64, name string, a map[string]interface{}) (interface{}, } return def } + // Los montos van por acá y no por getInt: getInt trunca los decimales y + // devuelve 0 si el modelo manda el número como texto ("290000"), que es un + // formato que sí usa. El resultado era una factura guardada en 0 mientras + // el bot informaba el monto correcto, porque el bot repite lo que dijo el + // usuario, no lo que se guardó. + getFloat := func(key string, def float64) float64 { + v, ok := a[key] + if !ok { + return def + } + switch x := v.(type) { + case float64: + return x + case int: + return float64(x) + case string: + limpio := strings.NewReplacer("$", "", ",", "", " ", "", "COP", "").Replace(x) + if f, err := strconv.ParseFloat(limpio, 64); err == nil { + return f + } + } + return def + } getStr := func(key string) string { if v, ok := a[key]; ok { if s, ok := v.(string); ok { @@ -652,7 +676,7 @@ func runTool(chatID int64, name string, a map[string]interface{}) (interface{}, ClienteID: clienteID, Numero: getStr("numero"), Descripcion: getStr("descripcion"), - Monto: float64(getInt("monto", 0)), + Monto: getFloat("monto", 0), Estado: "pendiente", FechaEmision: time.Now(), Visible: true, @@ -676,7 +700,7 @@ func runTool(chatID int64, name string, a map[string]interface{}) (interface{}, return map[string]interface{}{"ok": true, "factura_id": f.ID}, nil case "adjuntar_factura_compra": - cp, err := AdjuntarFacturaCompra(chatID, getStr("proveedor"), getStr("descripcion"), float64(getInt("monto", 0))) + cp, err := AdjuntarFacturaCompra(chatID, getStr("proveedor"), getStr("descripcion"), getFloat("monto", 0)) if err != nil { return nil, err } @@ -828,7 +852,7 @@ func runTool(chatID int64, name string, a map[string]interface{}) (interface{}, t := &models.Transaccion{ Tipo: getStr("tipo"), Descripcion: getStr("descripcion"), - Valor: float64(getInt("valor", 0)), + Valor: getFloat("valor", 0), Notas: getStr("notas"), } if err := models.CreateTransaccion(t); err != nil { @@ -851,7 +875,7 @@ func runTool(chatID int64, name string, a map[string]interface{}) (interface{}, cc, doc, err := CrearCuentaCobroConDocumento( uint(getInt("cliente_id", 0)), getStr("descripcion"), - float64(getInt("valor", 0)), + getFloat("valor", 0), nil, getStr("notas"), "telegram", @@ -1567,6 +1591,14 @@ Tienes acceso total al sistema mediante herramientas. Puedes: - Monitorear servidores y URLs COMPORTAMIENTO: +- NUNCA digas que guardaste, creaste, actualizaste o borraste algo si la + herramienta no te devolvió un resultado exitoso. Si devolvió {"error": ...}, + decí exactamente qué falló y qué hace falta para reintentar. Un "guardado + exitosamente" sobre algo que no se guardó es peor que un error: la persona + se entera días después, cuando el registro no aparece. +- Al confirmar que guardaste algo, informá los valores que devolvió la + herramienta (por ejemplo el id), no los que te dijo el usuario: son los que + realmente quedaron en el sistema. - Responde siempre en español, de forma clara y concisa - Usa las herramientas para obtener datos reales antes de responder - Cuando el usuario pida una acción (deploy, renovar, crear), hazla directamente sin pedir confirmación salvo que sea destructiva @@ -1724,6 +1756,10 @@ Comandos: /reset · /instancias · /ayuda`, nil log.Printf("[AGENT] Ejecutando tool: %s args: %s", tc.Function.Name, tc.Function.Arguments) toolResult := executeAgentTool(chatID, tc.Function.Name, toolArgs) + // El resultado también se registra: el modelo puede decir que + // guardó algo aunque la herramienta haya devuelto un error, así que + // el log es la única fuente confiable de qué pasó de verdad. + log.Printf("[AGENT] Resultado de %s: %s", tc.Function.Name, recortar(toolResult, 500)) // Agregar resultado al contexto (solo en memoria, no en BD) messages = append(messages, agentMessage{ @@ -1740,3 +1776,12 @@ Comandos: /reset · /instancias · /ayuda`, nil } return finalResponse, nil } + +// recortar limita lo que se escribe al log: un resultado de tool puede traer +// listados enteros y no vale la pena volcarlos completos. +func recortar(s string, n int) string { + if len(s) <= n { + return s + } + return s[:n] + "…" +}