Bot: al cerrar la cualificación, Luisa pide una foto y entra en modo recogida;
al llegar la foto la sube como "antes" con perfilCompleto:true → dispara render +
presupuesto + entrega del PDF. Nuevo webhook /whatsapp-fotos para que, tras una
llamada, Luisa escriba al lead, referencie lo hablado y le pida las fotos
(reutiliza el mismo modo).
App: el webhook de Retell, tras el análisis de la llamada, llama a pedirFotosWhatsapp
(WHATSAPP_FOTOS_WEBHOOK_URL) con el contexto de la reforma.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cuando la conversación llega al cierre (estado presupuesto/fin_*, o Luisa anuncia
que prepara/envía el presupuesto), el bot llama una vez a POST /api/leads/:id/analizar
para que la app capture todos los datos clave de la conversación de una pasada.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Con false, al reanudar la sesión guardada el dispositivo quedaba "no disponible"
y WhatsApp no le entregaba los mensajes entrantes (socket open pero inbound=0).
Con un escaneo fresco sí recibía (por el sync de emparejamiento). Marcando online
al conectar, WhatsApp entrega los mensajes también tras reconectar/redeploy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Confirmado vía /debug: WhatsApp entrega los mensajes desde una dirección @lid
(p.ej. 239225534443615@lid), no desde el número. El bot tomaba el id del lid
como teléfono -> no casaba con ningún lead -> ignoraba el mensaje. Ahora
resolverTelefono() resuelve el @lid a número vía msg.key.remoteJidAlt o el mapa
LID->PN de Baileys antes de buscar el lead. Debug captura también remoteJidAlt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BAILEYS_AUTH_DIR permite arrancar una sesión limpia (QR fresco) sin perder la
persistencia (subcarpeta del volumen) — para re-vincular cuando WhatsApp deja
muerto el dispositivo tras varios reinicios. Y messages.upsert ahora registra
en /debug todos los eventos (incluido type != notify) antes de filtrar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /debug (Basic auth, QR_TOKEN) devuelve el estado de la conexión de WhatsApp
y un anillo de los últimos mensajes entrantes (remoteJid real, fromMe, tipo) y
el resultado del matching lead↔teléfono. Para diagnosticar por qué el bot no
responde (conexión zombi vs formato de jid @lid vs no llega el mensaje).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El bot no respondía a las réplicas del cliente: la sesión lead↔teléfono vivía
solo en memoria y no casaba (se pierde al reiniciar el contenedor). Ahora si no
está en memoria, getOrCreateContext busca el lead en la BD por teléfono vía un
EP nuevo GET /api/leads/by-phone (match por últimos 9 dígitos) y re-registra la
sesión. Aparte, los ids de modelo de Claude del bot estaban mal (-4-5 vs 4.5).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El funnel promete "te escribimos por WhatsApp" pero el bot solo registraba la
sesión y esperaba a que el cliente escribiera primero → no llegaba nada. Ahora
WhatsappService escucha un startEmitter y manda el mensaje de apertura de Luisa
al teléfono (verifica el número con onWhatsApp), persiste estadoWa/botStep y el
intento. Además normaliza la clave de teléfono a solo-dígitos en leadSessions
(antes "+34..." no casaba con los dígitos del jid entrante → ignoraba al cliente).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review de seguridad: autenticar por query string filtra el secreto (logs,
historial, Referer) y usaba la misma FUNNEL_API_KEY que autoriza la API.
Ahora /qr usa HTTP Basic (credencial en cabecera), un QR_TOKEN dedicado
distinto de FUNNEL_API_KEY, comparación en tiempo constante (timingSafeEqual
sobre hashes) y Referrer-Policy: no-referrer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El QR de Baileys solo salía como ASCII en los logs (ilegible en Dokploy). Ahora
el WhatsappService empuja el QR al WebhookListener, que sirve GET /qr?key=
<FUNNEL_API_KEY> con el código como imagen (lib qrcode), autorrefresco y estado
"ya conectado". Añade dependencia qrcode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>