feat(telegram): el bot puede corregir facturas y registrar cobros

Auditoría de qué expone /api/v2 contra qué puede hacer el bot. El bot no
llama a la API: usa su propio registro de herramientas, así que la API
puede crecer y el bot queda atrás sin que nada lo señale.

De 46 herramientas contra ~300 endpoints de v2, el hueco más caro estaba
en el flujo que se estuvo usando hoy:

- Se podían crear cuentas por cobrar y por pagar, pero no marcarlas
  pagadas — y marcar pagado es justamente lo que genera la transacción en
  contabilidad. O sea que el bot podía registrar deuda pero nunca cerrarla.
- No había forma de corregir una factura mal cargada, que es exactamente lo
  que hacía falta después del bug del monto en cero.

Se agregan actualizar_factura, marcar_cuenta_cobro_pagada y
marcar_cuenta_pagar_pagada. actualizar_factura solo pisa los campos que
vinieron, así que corregir el monto no borra el número ni el concepto.

Quedan sin herramientas áreas enteras que v2 sí cubre (hostinger,
cloudflare, websms, portal-usuarios, uMind, api-keys, saas, plantillas).
No se agregan a ciegas: cada herramienta ocupa lugar en el prompt y
compite por la atención del modelo, así que conviene sumarlas cuando haya
un uso real detrás.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-15 21:08:24 -05:00
co-authored by Claude Sonnet 5
parent d5fbdf6d97
commit 560dc261e9
+81
View File
@@ -216,6 +216,24 @@ func agentTools() []agentTool {
"fecha_emision": str("Fecha de emisión YYYY-MM-DD; si no se indica, hoy"),
"fecha_vencimiento": str("Fecha de vencimiento YYYY-MM-DD, opcional"),
}, []string{"cliente_id", "monto"})),
tool("actualizar_factura", "Corrige los datos de una factura de venta ya registrada: monto, número, concepto o estado. Útil cuando quedó mal cargada.",
obj(map[string]agentToolParam{
"factura_id": num("ID de la factura (usa listar_facturas si no lo sabes)"),
"monto": num("Nuevo monto, si hay que corregirlo"),
"numero": str("Nuevo número de factura"),
"descripcion": str("Nuevo concepto"),
"estado": agentToolParam{Type: "string", Description: "pendiente | pagada | vencida | cancelada", Enum: []string{"pendiente", "pagada", "vencida", "cancelada", ""}},
}, []string{"factura_id"})),
tool("marcar_cuenta_cobro_pagada", "Marca como pagada una cuenta por cobrar y registra el ingreso en contabilidad.",
obj(map[string]agentToolParam{
"cuenta_id": num("ID de la cuenta por cobrar (usa listar_cuentas_cobro)"),
"fecha_pago": str("Fecha del pago YYYY-MM-DD; si no se indica, hoy"),
}, []string{"cuenta_id"})),
tool("marcar_cuenta_pagar_pagada", "Marca como pagada una cuenta por pagar y registra el egreso en contabilidad.",
obj(map[string]agentToolParam{
"cuenta_id": num("ID de la cuenta por pagar (usa listar_cuentas_pagar)"),
"fecha_pago": str("Fecha del pago YYYY-MM-DD; si no se indica, hoy"),
}, []string{"cuenta_id"})),
tool("adjuntar_factura", "Guarda como factura de VENTA el documento (PDF/foto) que el usuario acaba de enviar por Telegram: U-SITE es quien factura/cobra al cliente. Solo funciona si hay un archivo adjunto pendiente. Si el documento es al revés (un proveedor le factura a U-SITE), usa adjuntar_factura_compra en su lugar, no esta.",
obj(map[string]agentToolParam{
"cliente_id": num("ID del cliente al que pertenece la factura (usa listar_clientes si no lo sabes)"),
@@ -714,6 +732,69 @@ func runTool(chatID int64, name string, a map[string]interface{}) (interface{},
"numero": f.Numero, "cliente_id": f.ClienteID,
}, nil
case "actualizar_factura":
id := uint(getInt("factura_id", 0))
if id == 0 {
return nil, fmt.Errorf("factura_id requerido")
}
f, err := models.GetFacturaByID(id)
if err != nil {
return nil, fmt.Errorf("no se encontró la factura %d", id)
}
// Solo se pisan los campos que vinieron: así corregir el monto no
// borra el número ni el concepto que ya estaban bien.
if v := getFloat("monto", -1); v >= 0 {
f.Monto = v
}
if v := getStr("numero"); v != "" {
f.Numero = v
}
if v := getStr("descripcion"); v != "" {
f.Descripcion = v
}
if v := getStr("estado"); v != "" {
f.Estado = v
}
if err := models.UpdateFactura(f); err != nil {
return nil, err
}
return map[string]interface{}{
"ok": true, "factura_id": f.ID, "monto_guardado": f.Monto,
"numero": f.Numero, "estado": f.Estado,
}, nil
case "marcar_cuenta_cobro_pagada":
id := uint(getInt("cuenta_id", 0))
if id == 0 {
return nil, fmt.Errorf("cuenta_id requerido")
}
fecha := time.Now()
if v := getStr("fecha_pago"); v != "" {
if t, err := time.Parse("2006-01-02", v); err == nil {
fecha = t
}
}
if err := models.MarcarCuentaCobroPagada(id, fecha); err != nil {
return nil, err
}
return map[string]interface{}{"ok": true, "cuenta_id": id, "fecha_pago": fecha.Format("2006-01-02")}, nil
case "marcar_cuenta_pagar_pagada":
id := uint(getInt("cuenta_id", 0))
if id == 0 {
return nil, fmt.Errorf("cuenta_id requerido")
}
fecha := time.Now()
if v := getStr("fecha_pago"); v != "" {
if t, err := time.Parse("2006-01-02", v); err == nil {
fecha = t
}
}
if err := models.MarcarCuentaPagarPagada(id, fecha); err != nil {
return nil, err
}
return map[string]interface{}{"ok": true, "cuenta_id": id, "fecha_pago": fecha.Format("2006-01-02")}, nil
case "adjuntar_factura":
att, ok := PopTelegramAttachment(chatID)
if !ok {