fix(telegram): el monto de una factura se guardaba en cero

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 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-15 21:01:02 -05:00
co-authored by Claude Sonnet 5
parent 0100986a46
commit e9cb8107eb
+49 -4
View File
@@ -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] + "…"
}