Agentes de IA para agendar citas: qué funciona y qué no
Equipo Agentes AI ·
Un agente que agenda citas por WhatsApp es de los proyectos de IA más fáciles de demostrar y más difíciles de operar bien. La demo son tres mensajes; producción es un sistema distribuido con condiciones de carrera, ventanas de mensajería que se cierran y usuarios que escriben “el viernes” un domingo por la noche. Aquí desarmamos el flujo pieza por pieza, enlazando cada afirmación técnica a la referencia de WhatsApp Cloud API o de Google Calendar API que la respalda, junto con los modos de fallo que hay que diseñar desde el día uno.
Anatomía del flujo
Un agendamiento completo tiene cinco etapas: consultar disponibilidad, proponer horarios, confirmar y crear el evento, recordar, y manejar cancelaciones. Cada una tiene su API y su trampa.
1. Disponibilidad: freebusy, no listar eventos
Para saber qué horarios están libres, Google Calendar expone freebusy.query: le pasas un rango (timeMin y timeMax en formato RFC3339) y una lista de calendarios, y te devuelve los bloques ocupados de cada uno. Soporta hasta 50 calendarios por consulta, suficiente para un equipo de asesores. La ventaja sobre listar eventos es doble: no expones el contenido de la agenda (títulos, invitados) al componente que decide horarios, y la respuesta ya viene en el formato que necesitas — intervalos ocupados que tu lógica invierte para obtener los libres.
Los horarios disponibles los calcula tu código, no el modelo de lenguaje. El LLM interpreta lo que pide el usuario; la aritmética de intervalos, duraciones y zona horaria va en código determinista que puedas probar con tests.
2. Propuesta: los límites de la interfaz mandan
En WhatsApp no puedes mostrar un calendario visual. Las opciones reales son dos, según la documentación de mensajes interactivos: botones de respuesta (máximo 3 por mensaje) y mensajes de lista, que permiten hasta 10 secciones con un tope de 10 filas en total, cada fila con título de máximo 24 caracteres y descripción de 72.
Diez filas no alcanzan para mostrar una semana de agenda de media hora. El patrón que funciona: primero preguntar franja (“¿mañana o tarde?”, tres botones), luego ofrecer una lista con los primeros horarios libres de esa franja. Dos turnos de conversación, cero ambigüedad, y el usuario responde tocando en vez de escribiendo — cada toque que reemplaza texto libre es un error de interpretación que no ocurre.
3. Confirmación: crear el evento de forma idempotente
Con el horario elegido, events.insert crea el evento. Dos detalles de esa referencia valen oro:
- El campo
idlo puedes fijar tú (entre 5 y 1024 caracteres en base32hex, único por calendario; la doc recomienda UUIDs tipo RFC4122). Si generas elida partir de tu identificador interno de la cita, reintentar una petición que dio timeout no crea un evento duplicado: la segunda inserción con el mismoidfalla en lugar de clonar la cita. sendUpdatescontrola si los invitados reciben la invitación por correo (all,externalOnlyonone). Para citas con clientes casi siempre quieresall: el correo de Google es un respaldo de confirmación que no depende de tu bot.
La confirmación al usuario debe repetir fecha, hora y zona horaria en texto explícito (“martes 21 de julio, 10:00 a. m., hora de Colombia”), porque es tu última oportunidad de que un malentendido se detecte antes de que alguien se presente el día equivocado.
4. Recordatorio: aquí manda la ventana de 24 horas
El recordatorio choca con la regla de mensajería central de la plataforma. La Cloud API la describe como un temporizador: cuando el cliente te escribe o te llama arranca un conteo de 24 horas, y mientras no llegue a cero la conversación es libre. Si el cliente interviene otra vez, el conteo vuelve a empezar desde 24. Pero pasado un día de silencio suyo, la API deja de aceptar mensajes sueltos: solo supera el filtro un mensaje basado en una plantilla que ya pasó la revisión de Meta.
Un recordatorio de cita casi siempre cae en ese segundo escenario — la cita se agendó hace tres días —, así que necesitas una plantilla de categoría utility preparada con anticipación. Según el modelo de precios vigente, esa plantilla se cobra por mensaje entregado cuando el conteo ya venció, y es gratuita si lo atrapas todavía corriendo. El costo del recordatorio es parte del costo por cita: preséntalo así en cualquier estimación.
Diseña el recordatorio con botones de “Confirmo” y “Necesito reagendar”. La respuesta del usuario reactiva el conteo de 24 horas y el reagendamiento fluye como conversación normal, sin plantillas adicionales.
5. Cancelación y reagendamiento
Cancelar es borrar o mover el evento y avisar. Lo difícil es enterarte de cambios que no pasaron por el bot: la recepcionista movió la cita directamente en el calendario. Para eso Google ofrece notificaciones push: registras un canal watch sobre el calendario y Google te avisa cuando algo cambia. Ojo a dos propiedades documentadas: la notificación no trae el detalle del cambio (debes hacer otra llamada para saber qué pasó), y los canales expiran sin renovación automática — necesitas un proceso que los recree antes del vencimiento. Para saber qué cambió, la sincronización incremental con syncToken trae solo las diferencias desde tu última consulta; si el token caduca, la API responde 410 y toca resincronizar completo. Programa ese caso desde el inicio: ocurre.
Los modos de fallo típicos
Dobles reservas
Entre el freebusy.query que dijo “libre” y el events.insert que crea la cita pasan segundos — o minutos, si el usuario se demora eligiendo. La API no ofrece una operación atómica de “reserva si sigue libre”, y el id idempotente evita duplicar tu evento, no que otro proceso ocupe el mismo bloque. Mitigaciones en orden de simpleza: volver a consultar disponibilidad justo antes de insertar y abortar si el bloque ya no está libre; serializar todas las escrituras de un mismo calendario en un solo worker; y verificar después de insertar si quedó solapamiento, para disparar un reagendamiento proactivo en vez de esperar el reclamo.
Ambigüedad de fechas
“El viernes”, escrito un viernes, ¿es hoy o dentro de una semana? ¿“El 8/7” es 8 de julio o 7 de agosto? Un LLM resuelve estas frases con fluidez, y esa fluidez es el peligro: resuelve con seguridad interpretaciones que pueden ser incorrectas. Regla de ingeniería: el modelo propone la interpretación, pero el bot siempre la confirma en formato absoluto y sin ambigüedad (“viernes 17 de julio de 2026”) antes de tocar el calendario. Y la zona horaria se fija explícita en cada evento — los campos start y end la aceptan — en lugar de asumir la del servidor.
Plantillas rechazadas o pausadas
Tu flujo de recordatorios depende de una plantilla aprobada, y esa aprobación no es permanente: la documentación de plantillas indica que una plantilla puede quedar pausada “por retroalimentación negativa recurrente de los clientes o tasas de lectura bajas”, y en ese estado no se puede enviar. Si tu sistema no monitorea el estado de sus plantillas, los recordatorios fallan en silencio y solo lo notas cuando suben las inasistencias. Alerta sobre el estado de la plantilla y sobre la tasa de entrega, no solo sobre errores de la API.
Calendario desincronizado
Si el bot mantiene una copia local de la disponibilidad (razonable para responder rápido), esa copia se degrada: canales push vencidos, webhooks perdidos, tokens caducados. El síntoma es ofrecer horarios que ya no existen. La defensa es tratar la copia local como caché y validar contra freebusy en el momento de confirmar, nunca antes de insertar sin verificación fresca.
Cuándo transferir a humano
Las reglas de handoff no son un adorno; son parte del contrato del flujo. Transfiere cuando:
| Señal | Por qué |
|---|---|
| Dos intentos fallidos de interpretar la fecha | El costo de un tercer malentendido supera el de una llamada |
| El usuario pide algo fuera del catálogo de horarios/servicios | El bot no debe improvisar excepciones de negocio |
| Cancelación con carga emocional o urgencia médica/legal | Un flujo de botones queda mal ahí, siempre |
| Conflicto detectado (doble reserva, evento movido externamente) | La disculpa y la solución las da mejor una persona |
Y registra cada transferencia con su motivo: esa bitácora es la que te dice qué parte del flujo rediseñar.
Qué funciona y qué no, en corto
Funciona: separar interpretación (LLM) de decisión (código), interfaces de botones y listas sobre texto libre, id idempotente en cada inserción, confirmaciones en fecha absoluta, y recordatorios con plantilla utility y botón de respuesta. No funciona: confiar en que freebusy + insert es atómico, dejar que el modelo escriba directo al calendario sin confirmación, asumir que la ventana de 24 horas estará abierta cuando la necesites, ni montar el flujo sin ruta a humano.
La prueba real no es la demo sino la operación: que chat y calendario coincidan cita por cita, aun tras un mes de viernes ambiguos, plantillas pausadas y citas movidas a mano.
Publicado por Equipo Agentes AI. Si encuentras un dato desactualizado, escríbenos a agentes.co.oficial@gmail.com y lo corregimos citando la fuente. ¿Quieres comparar tasas vigentes? Están en Tasas Colombia.