Servidor Node.js + TypeScript que recibe mensajes de WhatsApp vía Evolution API, los procesa con un agente de OpenAI (con tools) y responde. Soporta texto y audio (transcripción con Whisper). Diseñado para ser fácilmente adaptable a distintos clientes (agencia de viajes, taller mecánico, etc.).
Webhook -> filtros (no grupos, no fromMe)
-> normalizar (texto/audio Whisper)
-> guardrail NSFW (OpenAI Moderation)
-> agente GPT-4o + tools (catálogo + leads)
-> guardrail PII (regex)
-> sendText via Evolution API
src/
├── clients/sofia/ <-- CONFIG DEL CLIENTE (lo que cambias por cliente)
│ ├── prompt.md <-- system prompt
│ └── config.ts <-- IDs de Doc/Sheet, schemas de tools
├── tools/
│ ├── catalog.ts <-- consultar_catalogo (Google Doc + cache)
│ └── leads.ts <-- guardar_lead (Google Sheets append)
├── index.ts <-- Express + webhook
├── pipeline.ts <-- 6 pasos
├── agent.ts <-- OpenAI loop + tool dispatch
├── audio.ts <-- Whisper
├── evolution.ts <-- cliente Evolution API
├── guardrails.ts <-- NSFW + PII
├── memory.ts <-- Map<phone, mensajes> sliding window
└── client-loader.ts <-- carga cliente activo segun CLIENT_NAME
npm installcp .env.example .envEditar .env con tus valores (ver comentarios en el archivo).
- Ir a https://console.cloud.google.com → crear/seleccionar proyecto
- Habilitar Google Sheets API
- APIs y servicios → Credenciales → Crear credenciales → Cuenta de servicio
- Descargar el JSON y guardarlo en
./credentials/service-account.json - Compartir el Google Sheet con el email del service account (algo tipo
mi-bot@mi-proyecto.iam.gserviceaccount.com) con permiso Editor - El Google Doc del catálogo: hacerlo público de lectura ("Cualquier persona con el enlace - Lector") o compartirlo con el service account
En tu instancia de Evolution, configurar el webhook para que dispare a:
POST http://tu-servidor:3000/agente-viajes-whatsapp
Eventos: al menos MESSAGES_UPSERT.
# Desarrollo (hot reload)
npm run dev
# Producción
npm run build
npm startHealthcheck: GET http://localhost:3000/health
-
Copiar la carpeta del cliente:
cp -r src/clients/sofia src/clients/taller
-
Editar
src/clients/taller/prompt.md: ahí vive toda la personalidad y reglas del agente. Reemplazar el contenido por el prompt del taller (ej: "Sos Juan, encargado del taller mecánico..."). Mantener los placeholders{{pushName}}y{{phoneNumber}}. -
Editar
src/clients/taller/config.ts:catalog.docId: ID del nuevo Google Doc (servicios, precios, horarios del taller)leads.spreadsheetId: ID del nuevo Google Sheetleads.columnOrder: si las columnas del Sheet son distintas, ajustar el ordentools: si el dominio requiere otras tools (ej.consultar_disponibilidad,agendar_turno), modificar los schemas. Si cambias nombres, ajustar también el dispatch ensrc/agent.ts.rejectionMessage: mensaje cuando el guardrail NSFW bloquea
-
Compartir Sheet y Doc con el Service Account.
-
Cambiar
.env:CLIENT_NAME=taller -
Reiniciar el servidor.
El resto del código (pipeline, agente, evolution, guardrails, memoria) no se toca.
- Memoria: en RAM, se pierde al reiniciar. Sliding window de 10 turnos por sesión (phone number).
- Background processing: el webhook responde 200 OK al instante y procesa con
setImmediate. Si un mensaje crashea no afecta a otros. - Cache del catálogo: TTL de 60min configurable en
config.ts. - Whisper: si Evolution no devuelve
base64en el endpoint configurado, ajustargetMediaBase64ensrc/evolution.tspara la variante que use tu instancia (algunas usanPOST /chat/getBase64FromMediaMessage/{instance}). - Guardrails fail-open: si la Moderation API falla, NO bloqueamos el mensaje (para no dejar al bot mudo). Configurable.
| Variable | Descripción |
|---|---|
PORT |
Puerto del servidor (default 3000) |
CLIENT_NAME |
Carpeta dentro de src/clients/ a cargar |
EVOLUTION_API_URL |
Base URL de Evolution |
EVOLUTION_API_KEY |
API key de Evolution (header apikey) |
OPENAI_API_KEY |
Para GPT-4o, Whisper y Moderation |
GOOGLE_APPLICATION_CREDENTIALS |
Ruta al JSON del Service Account |
NSFW_THRESHOLD |
Umbral (0-1) del guardrail de entrada |