Skip to content

fix(whatsapp): el panel no marca como enviado lo que Meta rechaza (ventana 24 h y estados) - #84

Draft
santmun wants to merge 1 commit into
mainfrom
cursor/whatsapp-window-delivery-ccb3
Draft

santmun wants to merge 1 commit into
mainfrom
cursor/whatsapp-window-delivery-ccb3

Conversation

@santmun

@santmun santmun commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Qué cambia

El panel deja de guardar como enviado un mensaje de WhatsApp que Meta rechazó, y muestra el estado real de entrega (enviado, entregado, visto o fallido).

Por qué

Ticket de soporte #D473B2 (v1.0.77, WhatsApp Cloud API). Una clienta mandó 26 seguimientos desde el panel a contactos que no escribían hace días. El panel los mostró como enviados y ninguno llegó. Meta responde 131047 (re-engagement) cuando el texto libre sale de la ventana de 24 h desde el último mensaje del cliente.

Causa confirmada en este repo (las dos hipótesis del reporte se sostienen):

  • La ruta del panel no revisaba la ventana y se tragaba el rechazo. POST /admin/conversations/:id/reply llamaba sendReply sin { strict: true } y sin mirar las 24 h. En src/channels/whatsapp.ts, un HTTP no exitoso solo se logueaba. La ruta seguía y guardaba el texto con role owner. Por eso el panel decía "enviado".
  • No existe api-inbox.ts ni un windowFor() previo en este starter. La cuenta de 24 h ya vivía en src/segments.ts (WINDOW_MS; las campañas usan el margen de 23 h, WINDOW_SAFE_MS). El panel ahora llama ese mismo helper (windowFor / serviceWindowFrom) en vez de copiar la regla. Telegram y el resto de canales no tienen esa ventana y siguen respondiendo igual.
  • El webhook tiraba los recibos. parseWhatsAppEvents saltaba cualquier change sin value.messages (el comentario era "statuses-only u otros → ignora"). sent, delivered, read y failed nunca se guardaban. Ahora parseWhatsAppStatuses los lee y se persisten en message_deliveries, ligados por el wamid que Graph devuelve al enviar.

Con la ventana cerrada el cuadro de respuesta pasa a ser un aviso con candado. Abierta, dice hasta qué hora se puede escribir. La lista tiene los filtros "Puedo escribirle" y "Ventana cerrada", y cada chat de WhatsApp muestra las horas que quedan o el candado. Cada mensaje saliente muestra ✓ Enviado, ✓✓ Entregado, ✓✓ Visto, o en rojo "No le llegó: fuera de la ventana de 24 h".

La tabla nueva es aditiva (CREATE TABLE IF NOT EXISTS en src/db/schema.sql, igual que el resto del esquema). Hay que aplicarla con pnpm db:apply / pnpm db:apply:remote. Si todavía no existe, el webhook de estados no tumba los mensajes entrantes.

Cómo lo probaste

  • pnpm test pasa (581 tests)
  • pnpm typecheck limpio
  • (si aplica) probado contra un bot real

Los tests cubren: la ventana cerrada no llama al canal y no guarda el mensaje; un rechazo (strict / 131047) no queda como owner; con la ventana abierta se manda en strict y se guarda el wamid; Telegram con un mensaje de hace días sigue enviando; el parser de statuses (sent/delivered/read/failed + código 131047) y que un recibo atrasado no baja un "visto"; el HTML del panel (candado, horas, palomitas, filtros).

Ventana cerrada: candado en vez del cuadro y fallo 131047 en el mensaje
Ventana abierta: se puede escribir hasta una hora, entregado y visto
Filtros Puedo escribirle y Ventana cerrada con horas restantes

Checklist

  • No toqué la carpeta member/ (config de cada quien)
  • El PR es de un solo tema (enfocado y chico)
  • Si tu agente (Claude) hizo el PR, revisaste el diff tú mismo antes de abrirlo
  • No hay secrets ni API keys en el código

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

La respuesta del panel no revisaba la ventana de 24 h y WhatsApp se tragaba
el 131047, así que el mensaje quedaba como del dueño aunque no saliera.
Ahora esa ruta reutiliza la ventana de segments, manda en modo strict y
guarda el wamid. Los webhooks de estado se persisten y el hilo muestra
enviado, entregado, visto o el fallo.

Co-authored-by: santmun <santmun@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants