
Un agente de WhatsApp que sabe cuándo llamar a un humano
El problema no era responder, era llegar a tiempo
Alguien escribe al WhatsApp de un negocio un sábado a las once de la noche. El equipo comercial contesta el lunes a media mañana. Para entonces el interesado ya escribió a otros tres.
Ese es el escenario que quise resolver. El proyecto es mío, de práctica: sin cliente y sin producción, una excusa para pelearme a fondo con un canal que tenía ganas de entender. El problema, en cambio, es de lo más real: le pasa a cualquier negocio que venda por chat.
No hacía falta un chatbot que hiciera magia. Hacía falta algo que respondiera en segundos, entendiera qué necesita la persona y levantara el teléfono solo cuando valiera la pena.
Cómo funciona por dentro
El recorrido de un mensaje es corto:
- La persona escribe al número de WhatsApp del negocio.
- El mensaje entra por la WhatsApp Cloud API de Meta, a través de Kapso, que se encarga del número, la sesión y el inbox.
- Kapso dispara un webhook a una API propia en NestJS.
- La API arma el contexto de esa conversación y le pregunta a Claude qué responder.
- Si de la conversación sale un lead calificado, salen dos cosas: un resumen al Telegram del equipo y una llamada de Twilio al interesado, que lo conecta con quien esté disponible.
Lo interesante no está en ninguno de esos pasos por separado —todos son una llamada HTTP— sino en las reglas que los rodean.
Lo primero: un número, dos proveedores
Quería que la llamada saliera del mismo número que la persona ya tenía en el chat. Que te escriba un desconocido después de hablar por WhatsApp con un negocio es justo lo que hace que nadie conteste.
Ahí me topé con el límite de la plataforma: la cuenta de WhatsApp Business de un número vive en un solo proveedor a la vez. Si Kapso tiene el WhatsApp de ese número, no puedes además registrar el mismo número como WABA en Twilio; no se comparte, y no es un problema de configuración que puedas resolver insistiendo.
La salida fue dejar de repartir proveedores y repartir canales: el chat lo maneja Kapso y la voz la maneja Twilio, sobre el mismo número de cara a la persona. Dos integraciones distintas en lugar de una, pero desde afuera es un solo contacto: el mismo número que respondió el mensaje es el que llama.
Segundo: la ventana de 24 horas
WhatsApp no es un canal donde escribes cuando se te ocurre. Si la última respuesta del usuario tiene más de 24 horas, no puedes mandarle texto libre: solo plantillas aprobadas previamente por Meta.
Esto cambia el diseño entero. El flujo tiene que saber en qué momento está cada conversación, porque no es lo mismo contestar dentro de la ventana que reabrir una conversación dormida. Si lo ignoras, el síntoma es horrible de diagnosticar: el bot "funciona" en las pruebas —donde siempre estás dentro de la ventana— y se queda mudo justo con los leads que se enfriaron.
La regla que terminé aplicando: dentro de la ventana, conversación normal; fuera de ella, plantilla o nada.
Tercero: los webhooks de Meta llegan más de una vez
Esto está en la documentación y aun así te muerde. Un webhook puede llegar duplicado, y a veces desordenado. Si procesas cada notificación tal como llega, el mismo mensaje del usuario termina generando dos respuestas del bot, dos avisos a Telegram y —esto es lo caro— dos llamadas telefónicas al mismo interesado, con segundos de diferencia.
La solución no es sofisticada, pero hay que ponerla desde el primer día: cada evento trae un identificador, y ese identificador se procesa una sola vez.
@Post('webhook')
async handle(@Body() payload: WhatsAppWebhookDto) {
// Responder rápido: Meta reintenta si te demoras
for (const event of extractMessages(payload)) {
const isNew = await this.events.claim(event.id); // INSERT ... ON CONFLICT DO NOTHING
if (!isNew) continue; // ya lo procesamos: reintento o duplicado
await this.queue.add('reply', event);
}
return { received: true };
}Dos detalles que aprendí aquí:
- Contesta el webhook rápido. Si te tomas el tiempo de llamar al modelo antes de responder, Meta asume que fallaste y reintenta. Recibe, encola, responde.
- La deduplicación va en la base de datos, no en memoria. En cuanto tienes más de una instancia, un
Seten memoria deja de servir.
Cuarto: cuándo se le puede llamar a alguien
Esta fue la decisión de producto más difícil, y no tiene nada que ver con el código.
Una notificación de Telegram es barata: si llega de más, se ignora. Una llamada telefónica no. Alguien que escribió por WhatsApp eligió el chat justamente para no hablar por teléfono; llamarlo porque dijo "hola" no es atención rápida, es una intromisión.
Así que la llamada tiene condiciones: se dispara solo cuando el agente ya reunió lo mínimo —qué necesita, para cuándo y cómo ubicarlo—, cuando la persona aceptó que la llamaran, dentro de un horario razonable y con alguien del equipo disponible para tomar la llamada. Si es medianoche o no hay nadie del otro lado, no se llama: queda el aviso en Telegram esperando. Peor que no llamar es llamar y que la persona caiga en un teléfono que nadie atiende.
Que el bot sepa callarse
La última pieza es la que más tranquilidad da: el traspaso a humano.
Si el interesado pide hablar con una persona, o la conversación se sale de lo que el agente sabe, el bot deja de responder y la conversación queda en el inbox para el equipo. Nada peor que alguien pidiendo un humano y un bot insistiendo con su guion.
En la misma línea, la instrucción del agente es explícita: responde con lo que sabe del negocio y, cuando no sabe, dilo y ofrece derivar. Prefiero un "te conecto con alguien del equipo" antes que un precio inventado que después alguien tiene que desmentir.
Lo que me llevo
- El canal manda. Las reglas de WhatsApp (ventana de 24 horas, plantillas, reintentos, un número por proveedor) definen la arquitectura mucho más que la elección del modelo.
- Los límites de la plataforma aparecen tarde. Que la WABA viva en un solo proveedor no se nota al diseñar: se nota cuando ya decidiste que la llamada sale del mismo número.
- Idempotencia desde el día uno. En un sistema que dispara acciones hacia afuera —mensajes, avisos, llamadas— un duplicado no es un log de más: es un teléfono sonando dos veces.
- La IA es la parte fácil. Lo difícil es decidir cuándo actuar, cuándo esperar y cuándo apartarse.
Si estás montando algo parecido, empieza por la última pregunta, no por el prompt.