Fix IA (max_tokens del modelo de razonamiento + fallback al gateway), sidebar con toggle arriba, busqueda de un solo campo (paciente/global), editar expediente, marketing en visitas, responsive de agenda; doc de diagnostico WhatsApp
This commit is contained in:
217
docs/diagnostico-whatsapp-entrante.md
Normal file
217
docs/diagnostico-whatsapp-entrante.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# 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
|
||||
|
||||
1. **El camino de entrada (Meta → Cloudflare → cloudflared → nginx → Next.js/WACRM) está sano y verificado hoy**: la suscripción del webhook está `active=true` apuntando a `https://skeen.consultoria-as.com/api/whatsapp/webhook`, con el campo `messages` suscrito, y Meta completó un handshake real contra esa URL el 24/Sep 08:51:54 UTC respondiendo **200 + challenge**.
|
||||
2. **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.
|
||||
3. **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 `customer` en `messages` → notificación al bridge → respuesta de Sofía enviada.
|
||||
4. **Se encontró y corrigió un defecto real que hacía que Sofía NO contestara bien**: el bridge usaba `max_tokens: 500` contra `qwen3.6` (modelo de razonamiento en `api.nan.builders`). El presupuesto se agotaba en `reasoning_content`, `content` volvía `null` con `finish_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.
|
||||
5. **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 = false` en ambas conversaciones. No hay filas en `ai_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_token` coincide con el del `.env.local` y con el de `wacrm.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 alcanza `finish_reason="stop"` y escribe `content`).
|
||||
- Si Nan devuelve `content` vacío (o falla por HTTP/red, p. ej. Cloudflare bloqueando la IP del VPS) → **fallback real al gateway local** `http://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
|
||||
|
||||
1. Ver el webhook en vivo (deja `$remote_addr` 127.0.0.1 porque entra por el túnel; UA de Meta = `facebookexternalua`):
|
||||
```bash
|
||||
tail -f /var/log/nginx/access.log | grep --line-buffered api/whatsapp/webhook
|
||||
```
|
||||
2. Ver el pipeline y las respuestas de Sofía:
|
||||
```bash
|
||||
journalctl -u hermes-bridge -f # "[WACRM] Webhook recibido: message.received", "[Intent] ...", "[WACRM] sent message to ..."
|
||||
journalctl -u wacrm -f # errores de envío / webhook
|
||||
```
|
||||
3. Ver el mensaje en la base:
|
||||
```bash
|
||||
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;"
|
||||
```
|
||||
4. Re-verificar la suscripción de Meta (si se duda de ella):
|
||||
```bash
|
||||
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"
|
||||
```
|
||||
5. Prueba funcional sin depender de Meta (inyecta un entrante firmado; **borrar después**):
|
||||
generar un payload con `phone_number_id=1157212604148350`, firmarlo con `sha256=<hmac_sha256_hex>` usando `META_APP_SECRET` y `POST` a `https://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)
|
||||
|
||||
1. `webhook-bridge.js` usa `const 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.
|
||||
2. Las alertas del healthcheck se envían como texto libre a `526644990030`, fuera de la ventana de 24 h → Meta las marca `failed`. Para avisos proactivos hace falta una plantilla aprobada y usarla.
|
||||
3. 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.
|
||||
4. 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.
|
||||
5. La app está suscrita también al objeto `user` con la misma callback URL; es inocuo, pero puede limpiarse para reducir ruido.
|
||||
6. Sin commit: los cambios de `webhook-bridge.js` están sólo en disco, pendientes de revisión del responsable.
|
||||
Reference in New Issue
Block a user