IA aplicada · Automatización
Agente de WhatsApp que califica y enruta leads a un CRM
Problema
Un retailer de pisos y revestimientos recibía consultas por WhatsApp fuera de horario y de forma desordenada: leads perdidos, datos incompletos y asignación manual entre asesores de dos ciudades.
Arquitectura
- WhatsApp Cloud API
- n8n (self-hosted)
- Gemini via OpenRouter
- CRM: contact + deal
- Advisor assignment
In progress → Qualified · VIP · Not qualified · Abandoned | Reminders · Session timeout · After-hours
Solución
- Agente con Gemini sobre n8n que califica al lead conversando, un dato por mensaje (nombre, ciudad, tipo de proyecto, presupuesto, correo).
- Clasificación por presupuesto y asignación automática al asesor de la ciudad con menos negocios abiertos, sincronizada con el CRM.
- Flujo fuera de horario, recordatorios de seguimiento, recontacto por timeout y manejo de clientes recurrentes.
- Plataforma multi-cliente operada con Claude Code controlando n8n por MCP, con workflows versionados y rollback.
Decisiones de diseño
- Un solo dato por mensaje: la conversación se siente natural y se evitan respuestas ambiguas.
- Si no se detecta la ciudad, el negocio queda con el administrador en vez de adivinar un asesor.
- Cada script de reparación corre primero en dry-run; los workflows se archivan en lugar de borrarse, para poder volver atrás.
- Los secretos se separan por cliente y la plataforma nunca asume una URL de producción por defecto.
Desafíos técnicos
- Un deploy desde PowerShell 5.1 corrompió el encoding UTF-8 y rompió las regex con acentos: ahora hay sanity check antes y después de cada despliegue.
- Un endpoint del CRM ignoraba en silencio los filtros vacíos y devolvía todos los registros, vinculando negocios a contactos ajenos: se corrigió verificando el teléfono y se aplicó el patrón en todos los workflows.
- Auditoría y reparación con scripts que corren primero en dry-run y luego con --apply; reglas operativas nacidas de incidentes reales.
Patrón destacado
// Never trust results[0]: a CRM list call with an empty filter
// silently returns EVERY record, linking deals to the wrong contact.
const last10 = (p) => String(p).replace(/\D/g, "").slice(-10);
function findContactByPhone(results, phone) {
const wanted = last10(phone);
if (wanted.length < 10) return null; // refuse empty/short filters
return results.find((c) => c.phones.some((p) => last10(p) === wanted)) ?? null;
}Resultados
- En producción desde junio de 2026, con mantenimiento activo.
- Auditoría de 443 ejecuciones sin leads perdidos: los 15 teléfonos con mensajes quedaron guardados.
- Workflow principal de ~130 nodos y asignación balanceada entre 4 asesores.
n8n, Gemini, OpenRouter, WhatsApp Cloud API, Bitrix24, MCP, AWS EC2