Arquitectura basada en agente-ycloud-main, pero con el canal (WhatsApp, Instagram, un
CRM...) separado del bot: el bot (src/core) solo conoce InboundEvent/OutboundMessage
(definidos en src/channels/types.ts), nunca el formato de un canal específico. Ver
ANALISIS_LA_JULITA.docx, sección 11, para la explicación completa del porqué.
Cada bot es una carpeta en src/agentes/, con todo lo suyo adentro (su prompt, sus
herramientas, su definición). Está armado así para que varias personas trabajen en paralelo, una
por bot, sin editar los mismos archivos ni pelearse al unir ramas. Ver
src/agentes/LEEME.md para el detalle y para cómo crear un bot nuevo.
src/agentes/ LOS BOTS — una carpeta por bot, se registran solos
_base.md prompt común a todos (persona, tono, regla de precios)
_tipos.ts el contrato de un agente
_registro.ts descubre las carpetas solo; nadie lo edita para sumar un bot
ventas/ atiende hoy: informacion + reservas + pagos
postventa/ atiende: postventa
orquestador/ el router: decide qué bot atiende cada mensaje
reservas/ pagos/ reservados, sin construir (los atiende ventas por ahora)
src/core/ INFRAESTRUCTURA COMPARTIDA (la usan todos los bots)
db/ todo lo que habla con Supabase
integrations/ LobbyPMS (disponibilidad real, reservas, bloqueos)
queue/ BullMQ + Redis
llm/ OpenRouter
pipeline/ el turno completo: runTurn, envío, comandos, recontacto, bloqueos
lib/ tools/types.ts utilidades y el contrato de una herramienta
src/channels/ un adaptador por canal: console y whatsapp-ycloud
src/web/ servidor del webhook + panel de administración (/admin)
src/worker/ los procesos que consumen las colas
El bot nunca conoce el canal: src/agentes/ y src/core/ solo manejan InboundEvent /
OutboundMessage (definidos en src/channels/types.ts), nunca el formato de WhatsApp ni de
ningún otro canal. Por eso el mismo bot funciona igual desde la consola que desde WhatsApp.
El orquestador (src/agentes/orquestador/) clasifica cada mensaje entrante en
informacion, reservas, pagos, postventa o humano, y guarda esa decisión en
estado_conversacion para no reclasificar a ciegas en cada turno. El pipeline le pregunta al
registro qué bot atiende ese tema. humano corta el flujo con un mensaje fijo (la notificación
automática al equipo sigue pendiente).
npm run typecheck # tipos de todo el proyecto
npx tsx scripts/probar-agentes.ts # el registro de bots está sano
npx tsx scripts/probar-parser-lobbypms.ts # el parser de disponibilidad (sin red)
npx tsx scripts/diagnostico.ts # auditoría completa (necesita red y credenciales)
npx tsx scripts/probar-lobbypms.ts # API oficial de LobbyPMS (necesita IP autorizada)
npm install
cp .env.example .env
# completa OPENROUTER_API_KEY en .env (puede ser la misma cuenta de OpenRouter que ya
# usas en agente-ycloud-main)
npm run chat:localscripts/mock-openrouter.mjs es un servidor local que imita la respuesta de OpenRouter
(siempre llama a preguntas_frecuentes) — sirve para probar que el cableado
consola → bot → herramienta → respuesta funciona, sin llamar a la IA real. Así se probó
esta primera versión:
node scripts/mock-openrouter.mjs &
OPENROUTER_BASE_URL=http://localhost:8787/v1 OPENROUTER_API_KEY=test npm run chat:localEscribe cualquier cosa y el bot debería responder con el texto de ejemplo de "ubicación".
Con esto confirmamos que: el mensaje escrito llega al pipeline, el modelo (simulado) pide
la herramienta preguntas_frecuentes, la herramienta se ejecuta y su respuesta se envía
tal cual por el adaptador de consola — exactamente el mismo camino que seguirá un mensaje
de WhatsApp real cuando conectemos ese canal.
El servidor necesita una URL pública para que YCloud le mande los mensajes — como corre en tu máquina, usamos ngrok como túnel temporal mientras programamos.
- Completa en tu
.env:YCLOUD_API_KEY,YCLOUD_FROM_PHONE_NUMBER(tu número de pruebas en formato+573...) yYCLOUD_WEBHOOK_SECRET(lo generas/copias al configurar el webhook en el paso 4). - Arranca el servidor:
npm run web— por defecto enhttp://localhost:3000. - En otra terminal, expón ese puerto con ngrok (si no lo tienes:
npm install -g ngrok, crea una cuenta gratis en ngrok.com y sigue sus instrucciones dengrok config add-authtoken ...la primera vez):Te da una URL pública tipongrok http 3000https://algo-random.ngrok-free.app— cada vez que reinicies ngrok cambia, así que hay que volver a pegarla en YCloud cuando pase. - En la consola de YCloud, en la configuración del webhook de tu WABA, pon la URL:
https://algo-random.ngrok-free.app/webhooks/whatsapp. YCloud debería mostrarte (o dejarte generar) el secreto de firma — cópialo enYCLOUD_WEBHOOK_SECRETde tu.env. - Manda un WhatsApp real a tu número de pruebas y mira la terminal donde corre
npm run web— deberías ver el mensaje llegar y, si todo está bien configurado, la respuesta del bot en tu WhatsApp.
Antes de meterte con ngrok, puedes probar el servidor solo (localmente): con
npm run web corriendo, node scripts/test-webhook.mjs manda un webhook de WhatsApp
simulado, ya firmado correctamente — así confirmas que el servidor, la verificación de
firma y el bot funcionan antes de complicarte con el túnel público.
Interfaz web donde el equipo de ventas edita precios/planes, adicionales, horarios de check-in/check-out, fechas bloqueadas y las preguntas frecuentes — y donde se puede revisar qué está hablando el bot con los clientes. El bot lee esos mismos datos (tablas de Supabase) en cada mensaje, así que un cambio guardado ahí queda disponible para el bot al instante, sin tocar código ni reiniciar el servidor.
- Crea una cuenta gratis en supabase.com y un proyecto nuevo.
- En tu proyecto, ve al SQL Editor → "New query", pega todo el contenido de
sql/schema.sqly dale "Run" (se puede correr varias veces sin problema, es idempotente — ya se probó corriéndolo dos veces seguidas contra un Postgres real). Crea:- Lo que el bot usa hoy:
planes(los domos),adicionales,configuracion,fechas_bloqueadas,faq,mensajesyestado_conversacion(del orquestador). - El diseño completo para cuando se construyan los agentes de Reservas/Pagos/
Postventa (
src/agentes/*/prompt.md):temporadas(tarifas por época),contacts(identidad del huésped, separada del canal),reservations,reserva_adicionales,pagosypoliticas_cancelacion— quedan creadas desde ya para no rediseñar el schema en cada fase, aunque el código todavía no las use.equipoqueda lista para cuando se porte la notificación automática al escalar a humano (ver más abajo).
- Lo que el bot usa hoy:
- En Project Settings → API, copia la Project URL y la service_role key
(¡no la
anonpublic! — esa no sirve aquí) y ponlas en tu.env:SUPABASE_URL=https://tu-proyecto.supabase.co SUPABASE_SERVICE_ROLE_KEY=tu-service-role-key - En tu
.env, define también:ADMIN_PASSWORD=una-clave-que-inventes-para-el-equipo SESSION_SECRET=cualquier-texto-largo-y-aleatorio - Reinicia
npm run webpara que tome las variables nuevas. - Entra a
http://localhost:3000/admin(o a tu URL pública +/adminsi estás con ngrok/cloudflared) e inicia sesión conADMIN_PASSWORD.
Si SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY no están configuradas, tanto el bot como el
panel siguen funcionando sin caerse — el bot responde "todavía no tengo esa información" y
el panel muestra un aviso de que nada se va a guardar hasta que las completes.
Nota de seguridad: la service_role key de Supabase tiene acceso total a la base de
datos — vive solo en el .env del servidor (que nunca se sube a git) y nunca debe
aparecer en código del navegador. El panel de administración usa una sola contraseña
compartida (ADMIN_PASSWORD) por ahora — suficiente para un equipo pequeño, pero sin
distinguir quién hizo cada cambio; si más adelante quieres cuentas individuales por
vendedor, se puede migrar a Supabase Auth.
- Canal de consola para probar el bot sin WhatsApp.
- Patrón de herramientas + FAQ, planes, adicionales y horarios conectados a Supabase.
- Adaptador real de WhatsApp/YCloud (envío, verificación de firma, parseo de mensajes entrantes) y servidor web con el webhook.
- Historial de conversaciones en Supabase (sobrevive a un reinicio del servidor).
- Panel de administración (
/admin): planes, adicionales, horarios, fechas bloqueadas, FAQ y monitor de conversaciones. - Orquestador (
src/agentes/orquestador/route.ts+src/agentes/orquestador/prompt.md): enruta cada mensaje ainformacion/reservas/pagos/postventa/humano, con pegajosidad (estado_conversacion.last_agent) y trazabilidad (mensajes.agent_name).humanocorta el flujo con un mensaje fijo;reservas/pagos/postventaya se enrutan como tales pero, al no tener herramientas propias todavía, los sigue atendiendo el Agente de Información — falta construir esos 3 agentes de verdad (ver punto siguiente). Requiere correr de nuevosql/schema.sqlen Supabase (agregaestado_conversaciony la columnamensajes.agent_name, ambos conif not exists, no rompe lo que ya había). - Completar los datos reales de La Julita desde el panel (
/admin) — ubicación, cómo llegar, mascotas, precios reales, etc. Siguen con[PENDIENTE]hasta que el equipo los cargue. - Construir los agentes de Reservas, Pagos y Postventa (diseño ya escrito en
src/agentes/*/prompt.md, cada uno marca con[PENDIENTE]qué herramientas le faltan). La pieza más crítica esverificarDisponibilidadFechas— hoyfechas_bloqueadases solo un registro, el bot todavía no la consulta ni hay cotización/reserva real. - Notificación automática al equipo cuando el orquestador escala a
humano(hoy solo queda en los logs del servidor) — portar el patrón deleadNotify.tsdeagente-ycloud-main. - Separar los procesos web/worker con una cola de verdad (BullMQ + Redis), como en
agente-ycloud-main— hoy el webhook procesa cada mensaje directo, sin cola. - Manejo de audio/imagen entrantes (hoy solo se detectan, no se transcriben/describen).
- Cuentas individuales por vendedor en el panel de administración (hoy es una sola clave compartida).