16 KiB
Diagnóstico — WhatsApp entrante "Sofía no contesta"
Fecha: 2026-09-24 (UTC) · VM SKEEN · Autor: diagnóstico técnico automatizado Alcance: por qué no entran mensajes de pacientes al WACRM y por qué la IA responde (o no) cuando entran.
1. Resumen ejecutivo
- El camino de entrada (Meta → Cloudflare → cloudflared → nginx → Next.js/WACRM) está sano y verificado hoy: la suscripción del webhook está
active=trueapuntando ahttps://skeen.consultoria-as.com/api/whatsapp/webhook, con el campomessagessuscrito, y Meta completó un handshake real contra esa URL el 24/Sep 08:51:54 UTC respondiendo 200 + challenge. - No hay ningún evento de mensaje entrante de Meta desde el 3/Sep. En los logs de nginx retenidos (11→24 sep) los únicos POST de webhook son 6 el 20-22 sep, desde user-agent
facebookexternalua, con 200 OK, que corresponden a actualizaciones de estado de nuestros propios mensajes de monitoreo (llegan 1-3 s después de insertar la alerta), no a mensajes de pacientes. No hay ni un POST más desde el 22/Sep 12:30. - La aplicación procesa correctamente un mensaje entrante cuando Meta lo entrega. Se inyectó un webhook firmado con el App Secret real y el pipeline completo funcionó: contacto → conversación → mensaje
customerenmessages→ notificación al bridge → respuesta de Sofía enviada. - Se encontró y corrigió un defecto real que hacía que Sofía NO contestara bien: el bridge usaba
max_tokens: 500contraqwen3.6(modelo de razonamiento enapi.nan.builders). El presupuesto se agotaba enreasoning_content,contentvolvíanullconfinish_reason="length"y el bridge respondía el texto de disculpa "Lo siento, no pude procesar tu mensaje." a todos los pacientes. Reproducido y corregido (ver §4); la respuesta verificada ya es la de Sofía. - Hipótesis principal de la falta de entrantes: los pacientes no están escribiendo a este número, o el número no está recibiendo. Evidencia de contexto: el número del CRM es un número piloto ("Consultoria Alcaraz Salazar", +52 1 663 700 6400) y el propio código del bridge dice que falta "el cambio al número de WhatsApp de SKEEN". En toda la vida del WACRM sólo existen 2 conversaciones (la de pruebas de Ivan y la del healthcheck), es decir, el CRM nunca ha operado tráfico real de pacientes.
2. Evidencia recolectada
2.1 Lado Meta (Graph API, token del WACRM)
Token: debug_token → type: SYSTEM_USER, app_id: 1607064673936924 ("Consultoria AS Social"), is_valid: true, expires_at: 0 (no expira), scopes whatsapp_business_messaging, whatsapp_business_management, business_management.
Número 1157212604148350:
display_phone_number : +52 1 663 700 6400 status : CONNECTED
platform_type : CLOUD_API quality_rating : GREEN
code_verification_status : EXPIRED throughput : STANDARD
webhook_configuration.application : https://skeen.consultoria-as.com/api/whatsapp/webhook
WABA 773071042535033 ("Consultoria Alcaraz Salazar"): account_review_status: APPROVED, business_verification_status: verified. Es el único número de la WABA.
Suscripción de la app:
GET /1607064673936924/subscriptions
object=whatsapp_business_account | active=True
callback_url=https://skeen.consultoria-as.com/api/whatsapp/webhook
fields=account_alerts,account_review_update,account_update,calls,message_template_quality_update,
message_template_status_update,messages,phone_number_name_update,phone_number_quality_update,security
GET /{waba-id}/subscribed_apps → la app 1607064673936924 está suscrita a la WABA.
2.2 Verificación real del webhook (handshake)
Handshake con el verify_token real contra la URL pública: 200 + challenge en skeen.consultoria-as.com y en skeencrm.consultoria-as.com. Token inválido → 403 (comportamiento esperado).
Re-registro forzado el 24/Sep:
POST /1607064673936924/subscriptions → {"success":true}
Ese POST hace que Meta ejecute un handshake inmediato. En /var/log/nginx/access.log:
127.0.0.1 - - [24/Sep/2026:08:51:54 +0000] "GET /api/whatsapp/webhook?hub.mode=subscribe&hub.challenge=790688494
&hub.verify_token=skeen_webhook_verify_2026&hub_mode=subscribe&... HTTP/1.1" 200 40 "-"
"facebookplatform/1.0 (+http://developers.facebook.com)"
→ Meta sí alcanza nuestro endpoint hoy, atravesando Cloudflare → cloudflared → nginx → Next.js.
Nota: los webhooks entran por el túnel cloudflared, por eso $remote_addr es 127.0.0.1; no es tráfico local.
2.3 Logs nginx (todo el histórico retenido: 11→24 sep)
6 POST /api/whatsapp/webhook 200 UA "facebookexternalua" 20/Sep 15:45:21, 16:00:03;
21/Sep 05:48:04, 08:35:58;
22/Sep 12:15:37, 12:30:13
2 GET /api/whatsapp/webhook 403 UA "curl/8.5.0" pruebas manuales 24/Sep 08:48
Los 6 POST coinciden en el tiempo con las alertas del healthcheck insertadas en messages
(p. ej. fila creada 22/Sep 12:30:12 → POST 12:30:13) y esas filas terminaron en status='failed',
consistente con status updates de Meta, no con eventos de mensaje entrante (si lo fueran,
existirían filas de contacto/conversación/mensaje en la base).
2.4 Base de datos (Supabase supabase-db/postgres)
- Último mensaje real de paciente: 2026-09-03 00:14:43 ("Hola", conversación
f9a45f24). Sólo hay 2 conversaciones en total: la de Ivan (f9a45f24) y la del healthcheck (4557417e). ai_autoreply_disabled = falseen ambas conversaciones. No hay filas enai_configs→ la auto-respuesta propia del WACRM no está configurada; el único respondedor de IA es el bridge de Hermes.whatsapp_config: 1 fila,status=connected,phone_number_id=1157212604148350,verify_tokencoincide con el del.env.localy con el dewacrm.service.
2.5 Logs de la aplicación
journalctl -u wacrm.service (desde 1/Sep):
Sep 21 03:15:50 [send-message] Meta send failed for all variants: fetch failed
... (cada 15 min) ...
Sep 23 07:15:15 [send-message] Meta send failed for all variants: fetch failed
→ El proceso de Next.js no pudo salir a graph.facebook.com entre el 21/Sep 03:15 y el 23/Sep 07:15. Coincide con las alertas del healthcheck del 20-21/Sep ("Acceso web NO responde") y con el reinicio de la VM del 20/Sep 07:38. Hoy la salida a Meta funciona (Graph API 200, y un envío de prueba devolvió wamid).
journalctl -u hermes-bridge.service: tras reiniciar el servicio sólo aparece el arranque normal; no hay kill-switch activo (REMINDERS_ENABLED no está definido → el heartbeat de recordatorios está desactivado por diseño, no afecta a las respuestas).
2.6 Defecto del motor de respuesta (reproducido)
Petición idéntica a la del bridge (node fetch, mismo body) contra https://api.nan.builders/v1/chat/completions:
model=qwen3.6, max_tokens=500 → status 200, finish_reason="length", content=null, reasoning_content=1956 chars
model=qwen3.6, max_tokens=1500 → status 200, finish_reason="stop", content="\n\n¡Hola! 👋 Soy Sofía, tu asistente virtual en SKEEN Derma Experts..."
Con max_tokens: 500 el modelo gastaba todo el presupuesto razonando y nunca escribía la respuesta; callNanBuilders devolvía entonces 'Lo siento, no pude procesar tu mensaje.' (webhook-bridge.js:187). Verificado en vivo con un webhook firmado: antes del arreglo, el mensaje del paciente produjo como respuesta "Lo siento, no pude procesar tu mensaje."; después, "¡Hola, buenas tardes! Soy Sofía, tu asistente virtual en SKEEN Derma Experts…".
Además, el modelo local (http://localhost:8080/v1/chat/completions, gateway Hermes) responde correctamente el mismo prompt (finish_reason="stop", sin reasoning_content), por lo que sirve como respaldo real.
3. Causa raíz / hipótesis priorizadas
| # | Hipótesis | Confianza | Estado |
|---|---|---|---|
| H1 | Los mensajes de los pacientes no llegan a este número: el CRM usa un número piloto ("Consultoria Alcaraz Salazar") mientras el código del bridge documenta que el cambio al número de WhatsApp de SKEEN sigue pendiente; en toda la vida del CRM sólo hay 2 conversaciones (una de pruebas y la del healthcheck). | Alta | A confirmar por el cliente con una prueba manual (§5.A) |
| H2 | El número no puede recibir: code_verification_status: EXPIRED y estado de registro a revisar. status=CONNECTED no lo descarta por sí solo. |
Media | Requiere panel de Meta (§5.D) |
| H3 | Suscripción del webhook caída / URL mal apuntada (la causa "clásica"). | Descartada | active=true, URL correcta, handshake 200 hoy |
| H4 | Ventana de fallos de red del VPS (21→23 sep) que rompió la salida a Meta y pudo haber tirado webhooks de esos días. | Media (histórica) | Resuelta; hoy sale a Meta. No explica la ausencia de entrantes desde el 3/Sep |
| H5 | El motor de respuesta estaba roto: todos los pacientes recibían la disculpa de error. | Confirmada | Corregida hoy (§4) |
Lo único que no se puede ver desde la VM es el historial de entregas de Meta (pestaña de webhooks de la app); ver §5.C.
4. Qué se arregló hoy
4.1 Webhook de Meta re-verificado
POST /{app-id}/subscriptions con la misma URL, token y lista de campos → {"success":true}, y Meta ejecutó el handshake contra nuestro endpoint (200 OK registrado en nginx). Suscripción de la WABA y del número apuntan al mismo callback.
4.2 Respuesta de la IA reparada — /root/skeen-roadmap-hermes/hermes/webhook-bridge.js
callNanBuilders():
max_tokens: 500 → 1500(el modelo alcanzafinish_reason="stop"y escribecontent).- Si Nan devuelve
contentvacío (o falla por HTTP/red, p. ej. Cloudflare bloqueando la IP del VPS) → fallback real al gateway localhttp://localhost:8080/v1/chat/completions(verificado funcionando) y se registra[Nan] ... Fallback a Hermes. - Sólo si ambos fallan se conserva la disculpa como último recurso.
Servicio reiniciado (systemctl restart hermes-bridge, active, /health OK). node --check OK. Sin commit, según instrucción.
4.3 Datos de prueba: insertados y eliminados
Ver §6 (auditoría).
5. Qué falta hacer del lado de Meta / del cliente
A. Prueba manual de entrada (lo primero, 2 minutos).
Desde un WhatsApp personal que no sea el del CRM, enviar Prueba a +52 1 663 700 6400.
- Si el mensaje aparece en el CRM (o en los logs, §7) → la entrada funciona; el problema era sólo H5 (ya corregido) y/o el cliente estaba mirando otro número.
- Si no aparece en ningún lado, entonces el mensaje no llega a Meta/nuestro webhook → seguir con C, D, E.
B. WhatsApp Manager → Configuración (Configuration) → Webhook.
Confirmar: URL de devolución de llamada https://skeen.consultoria-as.com/api/whatsapp/webhook, token de verificación (valor real en /root/skeen-wacrm/.env.local, variable WHATSAPP_VERIFY_TOKEN) y que el campo messages esté suscrito. Debe pulsarse "Verificar y guardar" para que Meta revalide. (Ya validado por API, pero conviene que el cliente lo confirme visualmente.)
C. Panel de la app (developers.facebook.com/apps/1607064673936924 → Webhooks → entregas recientes).
Ahí está el historial de entregas que no se puede consultar por API. Filtrar por el campo messages y revisar:
- si llegan eventos de tipo mensaje en las últimas 24-72 h y con qué código HTTP respondió el servidor;
- si sólo hay eventos de estado (
statuses), confirma que no están entrando mensajes al número, no que el webhook falle.
D. WhatsApp Manager → Números de teléfono → +52 1 663 700 6400.
Completar lo que esté pendiente: la API reporta code_verification_status = EXPIRED. Rehacer la verificación del número y configurar/renovar el PIN de verificación en dos pasos (pantalla "Configuración de dos pasos"). Es el único estado del número que no está "limpio".
E. Confirmar a qué número escriben los pacientes. Preguntar al cliente qué número está publicado (Google Business, Instagram, tarjetas, sitio). Si es distinto de +52 1 663 700 6400, ese es el problema: hay que registrar/migrar ese número a la Cloud API y apuntarlo al WACRM (el bridge sigue esperando "el cambio al número de WhatsApp de SKEEN").
6. Auditoría de datos de prueba (producción)
Se inyectó un webhook firmado real (HMAC-SHA256 con META_APP_SECRET, phone_number_id reales) a la URL pública, dos veces, con el remitente ficticio 5200000000000 (número no entregable → Meta lo marca failed, no se envió nada a ninguna persona real) y texto "Hola" / "Hola, buenas tardes".
Creado: 1 conversación (b0e0c328-a94a-4dd2-b8f6-38aed040758a) y 4 mensajes (2 customer, 2 agent), más memory/_5210000000000.json del bridge.
Eliminado/restaurado (verificado después):
DELETE 4 messages (de esa conversación) → mensajes creados en la última hora: 0
DELETE 1 conversations → conversaciones totales: 2 (estado original)
DELETE 0 message_reactions
UPDATE 1 contacts → nombre restaurado a '5210000000000' (el contacto ya existía; mi prueba lo reutilizó
y le había cambiado el nombre)
rm /root/skeen-roadmap-hermes/hermes/memory/_5210000000000.json
Sin efectos en Odoo (0 partners creados por la prueba; el intent clasificado fue 'otro')
7. Cómo verificar que quedó funcionando
- Ver el webhook en vivo (deja
$remote_addr127.0.0.1 porque entra por el túnel; UA de Meta =facebookexternalua):
tail -f /var/log/nginx/access.log | grep --line-buffered api/whatsapp/webhook
- Ver el pipeline y las respuestas de Sofía:
journalctl -u hermes-bridge -f # "[WACRM] Webhook recibido: message.received", "[Intent] ...", "[WACRM] sent message to ..."
journalctl -u wacrm -f # errores de envío / webhook
- Ver el mensaje en la base:
docker exec supabase-db psql -U postgres -d postgres -c \
"select m.created_at, c.phone, m.sender_type, left(m.content_text,60) txt, m.status
from messages m join conversations cv on cv.id=m.conversation_id
join contacts c on c.id=cv.contact_id order by m.created_at desc limit 10;"
- Re-verificar la suscripción de Meta (si se duda de ella):
cd /root/skeen-wacrm && set -a && . ./.env.local && set +a
curl -s "https://graph.facebook.com/v20.0/1607064673936924/subscriptions?access_token=1607064673936924|$META_APP_SECRET"
- Prueba funcional sin depender de Meta (inyecta un entrante firmado; borrar después):
generar un payload con
phone_number_id=1157212604148350, firmarlo consha256=<hmac_sha256_hex>usandoMETA_APP_SECRETyPOSTahttps://skeen.consultoria-as.com/api/whatsapp/webhook.
Criterio de "funcionando": un mensaje real de un paciente produce en <15 s (a) fila customer en messages, (b) log message.received en el bridge, (c) fila agent con la respuesta de Sofía (no "Lo siento, no pude procesar tu mensaje.").
8. Pendientes / recomendaciones (fuera del arreglo de hoy)
webhook-bridge.jsusaconst CLINIC_PHONE = '+52 (664) 123-4567', un placeholder: el prompt le dice a Sofía un teléfono falso. Confirmar con el cliente el número correcto y corregirlo.- Las alertas del healthcheck se envían como texto libre a
526644990030, fuera de la ventana de 24 h → Meta las marcafailed. Para avisos proactivos hace falta una plantilla aprobada y usarla. - El bridge deja de responder si la conversación está asignada a un agente humano (
isConversationAssigned→/skeen/frontend/v1/wacrm/conversations/{id}/assigned). Revisar que no haya conversaciones asignadas cuando se espera que conteste Sofía. - La salida a Meta cayó del 21/Sep 03:15 al 23/Sep 07:15 (IPv4/DNS del VPS). Vale la pena añadir un check de conectividad a
graph.facebook.com(IPv4) al healthcheck, no sólo al frontend. - La app está suscrita también al objeto
usercon la misma callback URL; es inocuo, pero puede limpiarse para reducir ruido. - Sin commit: los cambios de
webhook-bridge.jsestán sólo en disco, pendientes de revisión del responsable.