Problema
En Perú las empresas no se comunican con sus clientes por correo: lo hacen por WhatsApp. Así que cada sistema que construí terminó necesitando lo mismo —avisar de una cita, mandar un comprobante, recordar un pago— y cada uno lo resolvió por su cuenta, hablando directo con Meta.
Ese atajo se paga tres veces. El sistema de agendamiento acaba conociendo la ventana de 24 horas, el catálogo de plantillas y el modelo de precios de Meta, que no son su problema. Un recordatorio encolado sale aunque la cita se haya cancelado, porque nadie preguntó antes de despachar. Y cuando llega la factura de Meta, no hay con qué defenderla.
NexoConecta se queda con ese problema para que ningún sistema cliente lo tenga.
Contexto
El dominio de WhatsApp cambió entero en los últimos dos años, y casi toda la documentación que circula está vencida:
- El API on-premises está apagado desde el 23 de octubre de 2025. Solo existe Cloud API.
- El cobro es por mensaje desde el 1 de julio de 2025. El modelo de «conversaciones de 24 horas» que casi todos recuerdan está deprecado.
- Desde el 1 de octubre de 2026 se cobran los service messages. Los mensajes libres dentro de la ventana dejaron de ser gratis, con un tramo de 1,000 por número al mes que se refresca y no acumula.
Construir contra lo que uno cree recordar aquí no es un detalle de estilo: es facturar mal.
Arquitectura
La API pública no expone «enviar mensaje». Expone «ocurrió esto». La decisión de qué sale, cuándo y con qué plantilla vive dentro de NexoConecta, es configurable y cambia sin tocar el sistema del cliente.
| Pieza | Qué hace |
|---|
| Playbooks | El motor que traduce un evento en una secuencia de acciones. Los pasos esperan, condicionan y ramifican; activar una versión archiva la anterior |
| Ventanas | El estado de la ventana de 24 h vive materializado en la base y se lee en O(1). Nunca se le pregunta a Meta si se puede enviar |
| Transporte | Cloud API detrás de una interfaz. La lógica de negocio no conoce a Meta, así que mañana puede haber otro canal |
| Billing | El objeto pricing que devuelve Meta se persiste tal cual. Es la única fuente de verdad del costo |
| Integraciones | Webhooks de entrada desde Meta y de salida hacia el sistema del cliente, en direcciones y colas separadas |
| Aislamiento | Row Level Security de PostgreSQL más un trait con global scope. Hay pruebas de aislamiento que fallan si un modelo se queda fuera |
| Panel | Panel de operación en Filament, con tres roles y constancia de quién hizo qué |
Cinco colas, cada una un supervisor propio en Horizon, ordenadas por urgencia hacia el paciente: realtime, webhooks_in, scheduled, bulk y webhooks_out. Una campaña de 6,000 mensajes nunca puede retrasar la respuesta de alguien que está mirando el teléfono.
Decisiones clave
- Cancelar es tan importante como enviar. Un recordatorio de una cita ya cancelada es peor que no enviar nada. Toda operación programada nace con un
cancel_token y una condición de vigencia que se evalúa en el momento del despacho, no en el de la programación.
- La quinta cola es otro eje, no una más. Las cuatro originales se ordenan por urgencia hacia el paciente; los webhooks salientes van al sistema del cliente. Por
bulk, el aviso de que una paciente confirmó su cita saldría detrás de la campaña; por realtime, 12,000 avisos de entrega competirían con una respuesta real.
- Dos claves de cifrado, no una.
META_TOKEN_KEY cifra los tokens de las cuentas de los clientes y es distinta de APP_KEY a propósito: APP_KEY cifra sesiones y cachés, y acaba compartida con más sistemas de los que deberían poder enviar mensajes en nombre de una clínica.
- Todo cambio de Meta es un webhook que hay que escuchar. Meta recategoriza plantillas, las pausa y baja el quality rating. Nada de eso se descubre por polling ni por sorpresa en la factura.
- Un simulador de Meta desde el principio. Todo el desarrollo corre contra un simulador propio, y ningún test puede salir a la red. Es lo que permite probar la ventana, los reintentos y el rate limiting sin un número real ni el riesgo de que Meta bloquee la cuenta.
- El contrato público existe desde el primer commit, aunque el primer consumidor sea mi propio sistema. Si la API interna y la pública divergen, la pública nunca se termina.
Lo que NexoConecta no es. No es un CRM, ni un inbox de ventas, ni un constructor de chatbots, ni una plataforma de marketing. Esos mercados están saturados y competir ahí es la pelea equivocada. Es infraestructura de mensajería transaccional, y nada más.
Cómo se construye
PHP 8.4 y Laravel 13 sobre Octane con FrankenPHP, PostgreSQL con RLS, Redis con Horizon y un panel en Filament. Pest 5 para las pruebas y Larastan para el análisis estático. Diez dominios —desde Channels hasta Playbooks y Billing—, un centenar de archivos de prueba y 33 decisiones de arquitectura escritas antes de implementarlas.
La imagen no corre como root, y la revisión de seguridad está cerrada: MFA en el panel, protección contra SSRF en los webhooks salientes, límites de tamaño en la ingesta y sesión cifrada.
Qué queda por delante
Lo honesto: esto nunca ha hablado con Meta de verdad. Hay app, WABA y número por crear, y el procedimiento está escrito paso a paso, pero mientras eso no pase el producto vive en un simulador.
La última auditoría de completitud —de septiembre, y con sesgo declarado porque la corrió la misma sesión que escribió el código— puntuó el backend en poco más de la mitad, y la infraestructura, el alta en Meta y el cumplimiento legal casi en cero. Encontró además tres violaciones de reglas no negociables, incluida una que hacía salir un recordatorio de una cita cancelada. Las tres están cerradas; el resto de lo que marcó, no todo.
Y hay una regla que me puse y que es la que más cuesta cumplir: ningún otro proyecto se integra hasta que NexoConecta esté terminado. Podoplus, Diseña y FarmaOrden ya tienen la interfaz lista esperando, y la tentación de conectar el primero antes de tiempo es exactamente lo que dejaría el producto a medias para siempre.
Aprendizajes
El error de diseño que más veces vi —y que yo mismo cometí— es tratar la ventana de 24 horas como una consulta. Se siente natural preguntarle a Meta si se puede enviar, y es la decisión que hace el sistema lento, caro y dependiente. La ventana es estado propio: se materializa, se lee en memoria y se reconcilia con los webhooks.
El segundo: programar no es decidir. Durante semanas el motor decidía al encolar, que es el momento en que menos se sabe. Mover la evaluación al despacho cambió el modelo de datos entero, y es la diferencia entre un sistema que avisa y uno que molesta.