Fixes — 2026-07-02
Registro de los arreglos hechos en esta sesión (rama main).
1. Metas híbridas: sync automático corregido + modo manual
Problema reportado: al asociar una cuenta a una meta de “pagar deuda”, el progreso se calculaba “por derecha” con el saldo completo, sin que el usuario eligiera monto. Además la meta de deuda nacía completada.
Causas:
pay_off_debtcalculabacurrent = target - balance, pero los pasivos guardan saldo negativo → progreso instantáneo ≥ 100%.- El selector de cuentas del frontend no filtraba por tipo (podías atar la cuenta de ahorros a una meta de deuda).
- No existía forma de destinar un monto parcial: atar cuenta = usar todo el saldo.
Solución:
- Nuevos campos en
goals:tracking_mode(auto/manual) ybaseline_balance(snapshot de la deuda al atar la cuenta). Migracióne5f7a9b1c3d2. - Modo
autodeuda: progreso =|deuda al atar| - |deuda actual|, acotado a[0, target]. Metas viejas sin snapshot usantargetcomo deuda inicial. - Modo
manual: la cuenta queda como referencia y el progreso avanza solo con aportes explícitos (POST /goals/{id}/contribute). - Validación 400 si el tipo de cuenta no corresponde al tipo de meta.
- Editar el nombre de una meta ya no re-toma el snapshot (no resetea progreso).
- Frontend: selector filtrado por tipo, toggle ⚡Automático/✋Manual, botón “Aportar Monto” para metas manuales, badge “Sincronizado”/”Referencia”.
Tests: backend/tests/test_goals.py (5 casos nuevos, 12/12 pasan).
2. Corregir saldo de apertura no guardaba
Problema reportado: editar el saldo de apertura desde el listado de transacciones y guardar dejaba el valor anterior.
Causa: las filas “Saldo inicial” son transacciones virtuales (id = initial_{account_id}, generadas al vuelo en GET /transactions con include_initial=true; no existen en la tabla transactions). El PUT /transactions/initial_xxx iba al motor de transacciones, no encontraba la fila y fallaba; el frontend refrescaba mostrando el valor viejo.
Solución (backend/app/routers/transactions.py):
PUT /transactions/initial_{account_id}: actualizaAccount.initial_balancey desplazaAccount.balancepor el delta (preserva el efecto del historial de transacciones). En pasivos se conserva el signo (el form manda magnitud).DELETE /transactions/initial_{account_id}: deja la apertura en 0 con el mismo ajuste de balance.- Validación de perfil activo y 404 si la cuenta no existe.
- Frontend (
transactions/page.tsx): el modal muestra “Corregir Saldo Inicial” con aviso de que solo aplica el monto.
Tests: backend/tests/test_initial_balance.py (5 casos, 5/5 pasan).
3. Bot de WhatsApp: responde “Moneybot” viejo pero no registra en la app
Diagnóstico final (2026-07-02, corrige la primera hipótesis de abajo):
El webhook de WhatsApp de Meta apunta al pipeline VIEJO de MoneyPro, no al backend nuevo. Evidencia:
- Los textos que envía el bot (“Pregunta:”, “📋 Detalles a registrar:”, “¿Procedo?”) no existen en este repo ni en toda su historia git (
git log --all -Ssin resultados) → responde otro codebase. - CloudWatch muestra que la cola SQS vieja
moneypro-commands.fifo(cuenta AWS 811710375370) recibió mensajes hoy exactamente cuando el usuario escribió → Meta entrega los mensajes al flujo viejo (webhook → AWS → SQS → bot viejo). - El bot viejo escribe en la base de datos vieja de MoneyPro, por eso “hace como que registra” pero nada aparece en finanzasai.me.
- El backend nuevo en
api.finanzasai.meestá vivo y con verify_token/app_secret configurados (verificado con curl), pero Meta nunca le envía nada.
Hipótesis inicial descartada: el WHATSAPP_ACCESS_TOKEN del .env LOCAL está vencido desde 01-abr-26 (error 190), pero ese es solo el token de desarrollo; el pipeline viejo responde con su propio token válido.
MIGRACIÓN COMPLETADA (2026-07-02, misma tarde) — pasos ejecutados:
- El usuario revocó el token viejo y generó uno nuevo de la app MoneyProBot (id
942101211562111, la única app con WhatsApp del negocio). - Token nuevo verificado contra Graph API (200) y guardado en
backend/.env; App Secret confirmado contra producción con un POST firmado de payload vacío (200 → la firma valida). WHATSAPP_ACCESS_TOKENactualizado en DigitalOcean víadoctl apps update(app34921df6, deployment de spec quedó ACTIVE).- Webhook re-apuntado por API (
POST /{app_id}/subscriptionscon app tokenapp_id|app_secret):- Antes:
https://8u8c5ws3xb.execute-api.us-east-1.amazonaws.com/webhook(API Gateway del pipeline MoneyPro viejo — por eso no aparecía fácil en el dashboard: estaba configurado por API). - Ahora:
https://api.finanzasai.me/api/whatsapp/webhook(verificado por Meta con el verify_token, subscription active:true).
- Antes:
- El número (Test Number de Meta, +1 555-171-9269) no tiene override de webhook a nivel de teléfono: hereda la URL nueva.
Pendientes:
- Actualizar
WHATSAPP_ACCESS_TOKENen Azure App Service (no afecta al bot: el webhook apunta a DO, pero el env quedó con el token revocado). - Apagar el pipeline viejo de AWS (API Gateway
8u8c5ws3xb+ Lambda + cola SQSmoneypro-commands.fifo, cuenta 811710375370) cuando el bot nuevo quede confirmado en uso. - Migrar del Test Number a un número real de WhatsApp (el test tiene límite de 5 destinatarios).
- Prueba end-to-end del usuario: CONFIRMADA (transcript 13:19-14:08). El bot nuevo responde y registra.
4. Fluidez del bot nuevo (hallazgos del transcript de prueba)
Del transcript de la primera prueba real salieron 4 bugs, corregidos:
**literales en WhatsApp: los mensajes usaban negrita Markdown (**texto**) pero WhatsApp usa UN asterisco → se veían asteriscos sueltos. Fix:format_draft_confirmation_messageconvierte**→*al final; mensajes de éxito y bienvenida reescritos con*.- Confirmación disparada por corrección: “Cambia a categoría Alimentación si existe…” registraba la transacción porque
is_confirmation_messageaceptaba CUALQUIER palabra del set en el mensaje (“si” ∈ mensaje → confirmar). Fix: solo frases exactas o mensajes ≤3 palabras que EMPIEZAN con palabra de confirmación. - Error técnico crudo al usuario:
ACCOUNT_AMBIGUOUS:Cartera:...llegaba tal cual al chat. Fix: se traduce a pregunta conversacional (“Tienes varias cuentas que coinciden con Cartera: … ¿con cuál?”) y el draft se conserva para reintentar. - “¿Cuánto tengo en Nequi?” devolvía TODOS los saldos: la ruta regex llegaba a
handle_querysinparams.account. Fix: se detecta la cuenta mencionada en el texto contra los nombres del perfil (si varias coinciden, muestra solo esas).
Causa de fondo de la falta de fluidez: producción NO tenía LLM_PROVIDER configurado → el “cerebro” Bedrock estaba apagado y todo caía a las heurísticas regex (por eso “el restaurante y pagué” terminó como categoría). Fix operativo: LLM_PROVIDER=bedrock + BEDROCK_MODEL_ID agregados al spec de DigitalOcean (las credenciales AWS ya estaban). Para acercarse a la fluidez del bot MoneyPro original, ver el plan de mejoras en bot-moneypro-original-informe.md §4 (prompt restrictivo, campo faltante, validación post-LLM).
Mejora implementada: nuevo endpoint de diagnóstico GET /api/whatsapp/status que valida el token contra Meta y reporta token_valid, token_error y webhook_subscribed, para que este tipo de fallo silencioso sea visible sin acceso a logs.
5. Fluidez fase 2: virtudes del bot MoneyPro original portadas
Implementación del plan de bot-moneypro-original-informe.md §4 sobre el bot nuevo (LLM Bedrock + fallback regex). Cambios:
- Prompt restrictivo con Reglas de Oro (
nlp_llm._build_context): 11 reglas imperativas numeradas (“NUNCA inventes categorías”, “NUNCA inventes montos”, “payee solo si es un nombre propio”) + 10 ejemplos con casos edge (consulta vs transacción, multi, cuotas, corrección de borrador). Categorías en formato jerárquicoTronco: subcategoríascon instrucción de preferir la subcategoría (virtud 5). _normalize_draftya no inventa defaults:amountpuede quedarNone(el usuario no lo dijo),account_name/payee_namequedanNoneen vez de “Cartera”/”Establecimiento” duros. Enmultilos drafts sin monto se descartan (se registran directo, sin confirmación).- Confirmación en dos fases con campo faltante (virtud 2): nueva
agent.validate_draft(db, draft)→(draft, pregunta|None). Si falta el monto (o la cuenta es ambigua/inexistente, o falta el destino de una transferencia) el bot pregunta SOLO ese campo, con actionclarify_draft(sin botones Sí/Cancelar) y el borrador parcial persistido enwhatsapp_drafts(status=”incomplete”) → sobrevive redeploys y turnos. La respuesta del usuario (“45 mil”, “con Nequi Personal”) pasa porapply_feedback_to_draft+ re-validación. - Validación post-LLM antes de confirmar (virtud 4):
- Cuentas resueltas contra la BD en fase de BORRADOR: un
ACCOUNT_AMBIGUOUSahora sale como pregunta con opciones ANTES de confirmar, no como error al registrar; los nombres quedan exactos. - Payees genéricos rechazados (
_GENERIC_PAYEES, portado del original): “el restaurante”, “la tienda” → sin payee. Tampoco se registra más el payee basura “Establecimiento” por defecto. - Cuenta ausente → autocompleta con la cuenta por defecto de la categoría o la primera del perfil (misma regla del engine), y la confirmación muestra la cuenta REAL.
- “sí” sobre un borrador incompleto NO registra: re-pregunta.
- Cuentas resueltas contra la BD en fase de BORRADOR: un
- Compatibilidad ingest:
/api/ingest/notificationacepta tambiénclarify_draft(sobreescribe cuenta/payee con datos de la notificación y re-valida al confirmar). El camino regex ya no fuerza “Cartera”.
Tests: 5 nuevos en test_agent_llm.py (campo faltante en dos turnos, “sí” sobre incompleto, payee genérico, cuenta ambigua en dos turnos, cuenta inexistente). Suites verdes: agent_llm 11, whatsapp, ingest, new_features, transaction_engine, api, payees, balance_invariants.
Compatibilidad Azure (migración en unas semanas): todo es código backend puro + env vars — nada atado a DigitalOcean. Pendientes en Azure App Service: WHATSAPP_ACCESS_TOKEN nuevo (el viejo está revocado), LLM_PROVIDER=bedrock, BEDROCK_MODEL_ID y credenciales AWS. El webhook de Meta apunta al dominio api.finanzasai.me: al transferir el dominio de Namecheap solo se cambia el DNS hacia Azure (+ certificado del custom domain); no hay que tocar Meta.