Agente de voz que agenda citas
Un visitante habla con una página en español y cuelga con una cita de 30 minutos confirmada en el calendario del negocio.
Construido como prueba de concepto para un cliente: ElevenLabs Agents se encarga de la voz, los turnos y el modelo; Cal.com es el dueño del calendario; y en medio hay un backend en Fastify cuyo único trabajo es que el modelo nunca tenga que adivinar. El backend es el único componente que habla con Cal.com, así que la API key nunca llega al agente y ningún payload de reserva lo escribe un LLM. El agente mismo es JSON versionado que se aplica con el CLI de ElevenLabs: el prompt, el ASR, el TTS, los turnos y los contratos de las tools viven en el repositorio, no en un dashboard donde el siguiente deploy los sobrescribiría.
Cómo funciona
- NavegadorSDK de ElevenLabs sobre WebRTC
- AgenteSTT, turnos, LLM y TTS
- Tools webhookHTTPS + bearer, hacia el backend
- BackendFastify: fechas, opciones, idempotencia
- CalendarioCal.com API v2 → Google Calendar
Las reglas sobre las que está construido
- Sin aritmética de fechasLa fecha de hoy en la zona horaria del negocio se inyecta por conversación y se declara la única fuente de verdad.
- Sin horarios inventadosLa tool devuelve como máximo tres opciones ya redactadas para hablar; la reserva pasa un id de opción, nunca un timestamp.
- Doble resguardoLectura de vuelta y un “sí” explícito antes de la única acción irreversible, más una clave de idempotencia por conversación.
- Leer mientras se deletreaUna client tool dibuja nombre y correo en la página: un correo mal entendido se caza con la vista, no con el oído.
- Fallos que se pueden decirLos errores devuelven 200 con un motivo y una frase para leer en voz alta, así el modelo no improvisa a media llamada.
Lo interesante por dentro
Toda decisión adivinable, fuera del modelo
La aritmética de zonas horarias vive en un solo archivo, cubierto por tests con el reloj congelado. Cal.com devuelve decenas de slots crudos; el backend elige tres repartidos en el día y los redacta para hablarlos, así el agente lee opciones en lugar de inventarlas.
La página muestra la reserva, no el chat
La interfaz está hecha sobre el SDK de ElevenLabs y no sobre el widget embebido: un orbe que sigue al micrófono y luego a la voz del agente, una pista de subtítulos y un panel que se va llenando con los horarios reales que devolvió Cal.com, consultados al backend por id de conversación.
Los fallos, diseñados antes que el camino feliz
Si la parte del día pedida está llena se ofrece el resto del día; si el día entero está lleno se buscan los siguientes siete en paralelo, porque siete llamadas encadenadas son silencio audible. Los errores de las tools vuelven como algo que el agente puede decir en voz alta.
Iterar el prompt sin quemar minutos de voz
Un harness de simulación corre seis conversaciones guionadas contra el agente en vivo, en texto, y verifica que la reserva siempre siguió a una confirmación explícita, que no se ofreció ningún horario inventado y que confirmar dos veces no crea dos citas. 153 tests, ninguno toca la red.
Entregado como producto, no como demo
Auth por bearer en las dos tools webhook, webhook post-llamada verificado por HMAC con ventana de replay, comparaciones en tiempo constante, logs redactados, rate limiting por IP, imagen Docker multi-stage sin root y validación al arranque que mata el proceso nombrando la variable que falta en vez de fallar a media llamada.
Construido con
POR QUÉ EXISTEEl problema interesante de un agente de voz no es el prompt, es todo lo que el prompt no debería estar haciendo. Fechas, redacción de opciones, idempotencia y mensajes de error se mudaron a un backend que puedo testear; al modelo le queda conversar, que es en lo que de verdad es bueno.
Cuando necesitas criterio,
no solo código.
Más de una década enviando producto en web, mobile e IA me dejó algo más valioso que stack: criterio. Si tu equipo está atascado en una decisión técnica, evaluando un stack, o necesita una segunda opinión antes de invertir meses en una dirección, conversemos.