Auditoría en vivo · sin teatro · 2026-07-27

¿Zy registra un gasto tan bien como MillonAI?

Comparación campo a campo entre las 3 capturas de Instagram que mandaste de MillonAI (Paraguay) y el código que hoy corre en producción del bot de Zymplo — el mismo escenario en los tres: texto, audio y foto de ticket.

Medido 21:29–22:15Z · 27 jul 2026 Fuente código: zymplo-inc/zymplo (monorepo real) 2 sub-agentes + re-verificación manual cruzada
Nota de honestidad

Cómo se midió esto — y un error que se corrigió en el camino

La primera pasada (dos sub-agentes en paralelo) auditó /repos/langgraph — un clon con remote skn-alarmas/langgraph, congelado el 30 de abril y sin ningún workflow de deploy. Ahí salió un hallazgo de "asteriscos sueltos rompiendo el formato". Antes de escribirlo como hecho, se cruzó el commit que otra nota interna marca como el desplegado hoy (bdd3640e) contra ese repo: no existe ahí. Aparece en zymplo-inc/zymplozymplo-langgraph/, cuyo propio CLAUDE.md dice explícitamente "es la ÚNICA fuente de verdad del bot… corre en producción en agents.zymplo.com". Se repitió toda la auditoría ahí. Lo que sigue —incluido el hallazgo de asteriscos, que resultó estar ya resuelto en el repo correcto— corresponde al repo que realmente corre hoy.


Lado a lado

Las mismas 3 capturas que mandaste

Izquierda: reconstrucción fiel de tus capturas de millonai.app (mismo texto, mismo orden, mismos emoji). Derecha: reconstrucción a partir del código real de zymplo-langgraph — plantillas y valores tal como los arma el bot hoy, con los campos que vienen de la base de datos marcados {así} en vez de inventados.

01 Audio → gasto de combustible nota de voz de 0:12 → "300.000 Gs, transporte"
🎤 MillonAI Paraguay Instagram @millonai.app · carrusel 31 may · captura 1/4
🧾 Zymplo — código real erp_api_tools.py:3554 (erp_registrar_compra) · tras transcribir el audio
02 Foto de ticket → gasto de supermercado foto + pie "Efectivo Gs." → "42.000 Gs, alimentación"
📷 MillonAI Paraguay Instagram @millonai.app · carrusel 31 may · captura 2/4
🧾 Zymplo — código real handlers.py:1954–2023 (_auto_register_from_receipt) · plantilla fija, no la escribe el LLM
🟡 "1 movimientos" — falta el singular, es cosmético 🟢 sin asteriscos — limpiado en el transporte de salida, no depende de que cada mensaje se acuerde
03 Texto → ingreso (cobro de salario) "cobré mi salario 4.500.000 en Efectivo Gs hoy"
⌨️ MillonAI Paraguay Instagram @millonai.app · carrusel 31 may · captura 3/4
🧾 Zymplo — código real erp_api_tools.py:3841 (erp_crear_transaccion, tipo=RECEITA)
🟠 dice "RECEITA" en portugués aunque el chat sea en español 🟠 la categoría no es libre como "Trabajo" — tiene que ser un servicio ya dado de alta en tu negocio

Hallazgos

Lo que encontramos, tal cual es

Nada de esto es opinión — cada línea abajo apunta a un archivo y un número de línea reales, verificados en este mismo turno.

🟢 Dónde Zymplo ya está adelante

El bug de asteriscos ya está resuelto — y a nivel transporte La limpieza de markdown corre en el último paso antes de salir (360dialog y Meta Cloud), no en cada función. Ningún mensaje nuevo puede "olvidarse" de limpiarlo. wa_sanitize.py:43 · bsp.py:174-176 · meta_client.py:52-55
Transcripción de audio con 3 motores de respaldo Si Groq Whisper falla, sigue con Deepgram Nova-2, y si ese también falla, con OpenAI Whisper-1. MillonAI no muestra qué pasa si su motor de voz falla. src/utils/transcription.py:90-190
Multi-moneda con confirmación, no adivinanza Si el monto viene en otra moneda, el bot pregunta si convertir antes de guardar — no lo registra mal en silencio. erp_api_tools.py · _resolve_currency
Vincula cliente/proveedor y cuenta bancaria solo Busca el nombre, crea uno nuevo si no existe, y elige la cuenta según el método de pago. En las capturas de MillonAI no hay vínculo a una persona — solo cuenta y categoría. erp_api_tools.py · nombre_asociado (erp_crear_transaccion)
El registro por foto vive en un ERP real, no en una planilla Cada ítem del ticket crea una orden de compra + transacción en Oracle — no es una fila suelta. handlers.py:1221 (_auto_register_from_receipt)

🟠 Gaps reales — verificados, no supuestos

"RECEITA"/"DESPESA" se filtran en portugués al chat en español El ingreso libre (el equivalente exacto a "cobré mi salario") muestra el código interno en vez de "Ingreso". MillonAI no comete este error porque su tarjeta ya está traducida por completo. erp_api_tools.py:3842 · tipo_upper sin traducir
Sin categoría libre para ingresos MillonAI deja poner "Trabajo" y listo. Zymplo pide que la categoría de un ingreso sea un servicio ya dado de alta en el catálogo — más prolijo para facturar después, pero un paso más de fricción la primera vez. erp_api_tools.py · docstring erp_crear_transaccion
Plantilla más prolija de recibos, construida y nunca conectada Existe un módulo con confianza alta/media/baja y detección de CNPJ — más parecido a la tarjeta de MillonAI que la plantilla real. El PR que lo iba a conectar se cerró sin mergear; nadie más lo llama. src/integrations/vision/__init__.py · analyze_receipt_v2 (huérfano)
"1 movimientos registrados" — falta el singular Cosmético, no afecta el registro. Pero es exactamente el tipo de detalle que una tarjeta más simple (como la de MillonAI) no tiene margen para tener. handlers.py:1954

Más allá de estos 3 mensajes

Lo que ninguna captura de MillonAI mostró

Esto es lo que hay además del cuaderno de gastos. Confianza "alta" = probado este turno leyendo el código y su comportamiento real. Confianza "media" = el archivo existe y el nombre coincide con la función (confirmado este turno), pero no se re-verificó línea por línea si el flag está prendido en producción — la misma prudencia que hizo falta con el módulo huérfano de arriba.

CapacidadMillonAI (tus capturas)ZymploConfianza
Gasto por texto✓ visto✓ erp_registrar_compraalta
Gasto/ingreso por audio✓ visto✓ 3 motores STTalta
Gasto por foto de ticket (OCR)✓ visto✓ Claude Visionalta
Cobro / link de pago (PIX)no visto✓ payments/pix_brcode.py, pix_inbox/media
Pasarela de pago Paraguayno visto✓ payments/pagopar.pymedia
Facturación fiscal (12 países)no visto✓ NFS-e/CFDI/SIAT/SRI/DIAN/CFE/ARCA/AEAT/SUNAT/Hacienda/FE-USmedia
Banco conectado (Open Finance)no visto✓ belvo.py + carpetas openfinance-*media
Respuesta por nota de voz (TTS)no visto✓ integrations/tts.pymedia
Calendario / recordatoriosno visto✓ google/calendar.pymedia
Firma electrónica de contratosno visto✓ docs/zapsign.py, docuseal.pymedia

Veredicto

¿Quién responde mejor, hoy?

En las 3 cosas exactas que MillonAI mostró, Zymplo ya iguala o supera lo que se ve en la captura: foto con lectura real (no una plantilla adivinada), audio con tres motores de respaldo, y un texto libre que además detecta moneda distinta y vincula cliente y cuenta solo — nada de eso aparece en las capturas de MillonAI. El único punto donde la tarjeta de MillonAI se ve más prolija es exactamente donde tiene menos que hacer: es una tarjeta de 4 líneas fija, mientras que la de Zymplo para un ingreso libre todavía deja pasar la palabra "RECEITA" en portugués en un chat en español — un defecto de traducción, no de arquitectura. Fuera de esos 3 mensajes ya no hay comparación posible con lo que mostró MillonAI: PIX, facturación fiscal en 12 países, banco conectado y voz de salida no aparecen en ninguna de sus capturas.


Evidencia — comandos y salidas reales de este turno

1 · el repo equivocado, descartado con evidencia

# repos/langgraph — remote zymplo apunta a un commit de abril, sin bdd3640e
$ git ls-remote zymplo | grep -iE "develop|main"
563384a14e8530b6552cafddb5ef3aeb7dc1350f  refs/heads/main   # 30-abr-2026, sin "develop"
$ git cat-file -t bdd3640e
fatal: Not a valid object name bdd3640e

2 · el repo correcto, confirmado

# repos/zymplo/zymplo-langgraph — remote zymplo-inc/zymplo
$ git cat-file -t bdd3640e
commit
$ git log --all --format="%H %s" | grep bdd3640
bdd3640eb7bb01f0dbd7b118592144b7634c398c Merge pull request #2750 from zymplo-inc/develop
$ git log origin/main -1 --format="%H %ci %s"
f2c09bef6784775574a5211882a539d54a9e12d0 2026-07-27 15:54:22 -0300 Merge pull request #2782 from zymplo-inc/develop

3 · el fix de asteriscos, verificado en 3 capas

# 1. la plantilla real todavía arma **bold** con doble asterisco
handlers.py:1954  lines.append(f"✅ **{total_items} movimientos registrados!**\n")

# 2. pero el transporte de salida lo limpia — confirmado en los 2 proveedores
bsp.py:174-176        text = strip_markdown_for_whatsapp(text)   # 360dialog, el vivo
meta_client.py:52-55  message = strip_markdown_for_whatsapp(message)  # Meta Cloud, fallback

# 3. el propio comentario del código documenta el porqué del rediseño
handlers.py:405  "Historically this CONVERTED **bold** -> *bold*, which still
                  shipped asterisks... Now it delegates to the shared strip
                  sanitizer... The real choke-point is the outbound transport"

4 · el módulo huérfano de recibos, confirmado sin llamadores

$ grep -rln "analyze_receipt_v2\|process_receipt_image" src --include="*.py" | grep -v /tests/
src/integrations/vision/__init__.py   # solo se llama a sí mismo
src/integrations/vision/receipt.py    # solo se llama a sí mismo
# handlers.py sigue importando la ruta vieja:
handlers.py:21  from src.utils.image_processor import analyze_receipt, describe_image

5 · archivos citados (file:line)

  • src/whatsapp/handlers.py:1221_auto_register_from_receipt, plantilla del recibo
  • src/whatsapp/handlers.py:1954-2023 — construcción línea por línea del mensaje de confirmación
  • src/tools/erp_api_tools.py:3449-3563erp_registrar_compra (gasto por texto/audio)
  • src/tools/erp_api_tools.py:3576-3868erp_crear_transaccion (ingreso/gasto libre, RECEITA/DESPESA)
  • src/utils/transcription.py:90-190 — cascada Groq → Deepgram → OpenAI
  • src/integrations/whatsapp_extra/wa_sanitize.py:43strip_markdown_for_whatsapp
  • src/integrations/vision/__init__.py:152analyze_receipt_v2, huérfano