Puntos clave
- Casi todos los asistentes que dicen reservar en realidad recogen una solicitud que alguien confirma después a mano.
- Reservar de verdad exige dos herramientas, no una: consultar huecos y crear la cita. Y entre las dos pasa tiempo.
- Lo que se comprueba al ofrecer un hueco no vale al confirmarlo: hay que volver a mirar el calendario justo antes de escribir.
- Nunca digas solo que no. Un choque de horario debe responder con alternativas o el usuario abandona.
- Al modelo no se le puede confiar la entrada: manda fechas viejas, cadenas con la palabra "undefined" y teléfonos que dicen "no".
Índice de contenidos
Reservar de verdad o parecerlo
Cuando un proveedor te dice que su asistente "reserva citas", conviene preguntar una cosa concreta: ¿escribe en el calendario, o deja una solicitud para que alguien la confirme?
La segunda opción es perfectamente razonable y es lo que hace la mayoría. Pero no es lo mismo. Si alguien elige un hueco a las once de la noche y a la mañana siguiente recepción le dice que ese hueco no estaba libre, has convertido una reserva en una gestión, y encima con una decepción por delante.
Este artículo va de lo que hace falta para la primera opción. Es el sistema que usa el asistente de esta web para agendar las sesiones iniciales, y se puede probar desde el chat.
Dos herramientas, no una
El agente no habla con el calendario directamente. Tiene dos herramientas separadas, y esa separación es la que hace que el sistema sea fiable.
La primera consulta disponibilidad. Recibe una fecha, o ninguna, y devuelve huecos libres. Tiene tres modos, porque la gente pregunta de tres maneras distintas: por un día concreto, por una semana entera, o directamente "lo primero que tengas". Ese último modo es el que más se usa, y el que evita la conversación tediosa de ir proponiendo días uno a uno.
La segunda crea la cita. Recibe los datos de la persona y el hueco elegido, y es la única que escribe.
Entre que la primera ofrece un hueco y la segunda lo confirma, pasa tiempo real: el usuario lee, lo piensa, escribe su nombre y su email. Pueden ser dos minutos o pueden ser diez.
El hueco que ya no existe
Ahí está el problema que casi nadie contempla. Si el sistema confía en la disponibilidad que consultó al principio de la conversación, tarde o temprano crea una cita encima de otra. Basta con que dos personas estén hablando con el asistente a la vez, o que alguien del equipo meta algo en el calendario a mano mientras tanto.
La solución no es elegante ni lista, es simplemente la correcta: la herramienta que crea la cita vuelve a consultar el calendario justo antes de escribir. No se fía de lo que se comprobó hace cinco minutos, ni siquiera de su propia consulta anterior.
El flujo completo queda así: recibir los datos, mirar los eventos que ya hay ese día, validar que el hueco pedido sigue libre, y solo entonces crear el evento y mandar los correos. Si en ese último momento el hueco está ocupado, no se escribe nada.
Solapar no es coincidir
Un detalle pequeño que arruina el sistema si se hace mal: comprobar si dos citas chocan no es comprobar si empiezan a la misma hora.
Una cita de 10:00 a 10:20 y otra de 10:10 a 10:30 no empiezan igual y se solapan igualmente. La comprobación correcta es la de intervalos de toda la vida:
const isOccupied = busy.some(b => slotStart < b.end && slotEnd > b.start)
Dos intervalos se solapan si uno empieza antes de que el otro acabe y acaba después de que el otro empiece. Cuatro comparaciones, ningún caso raro. Si en su lugar comparas horas de inicio, el sistema funciona en las pruebas, donde todo el mundo elige horas en punto, y falla en producción.
No digas solo que no
Cuando el hueco está ocupado, la respuesta fácil es un error. Es también la forma más segura de perder a esa persona: acaba de dedicar tres minutos a darte sus datos y lo que recibe es una negativa.
Lo que hace el sistema es calcular, en ese mismo momento, qué huecos siguen libres ese día y devolver hasta cinco, junto con el mensaje de que ese horario ya no está disponible. El agente no tiene que volver a preguntar nada: le llegan las alternativas y las ofrece en la misma frase en la que da la mala noticia.
Es una decisión de producto más que técnica. El coste es media docena de líneas. La diferencia entre "no está disponible" y "ese ya no está, pero tengo las 11:00, las 11:30 o las 12:00" es la diferencia entre una conversación que muere y una que sigue.
El modelo manda basura y hay que contar con ello
Esta es la parte que no aparece en ninguna demo y que se lleva la mitad del código.
Un modelo de lenguaje llamando a una herramienta no es un formulario. Manda lo que le parece, y el formato cambia según por dónde entre la petición. A veces los datos vienen en el cuerpo, a veces dentro de un campo que contiene una cadena con JSON dentro, a veces en otro campo distinto. Lo primero que hace el sistema, antes de mirar nada, es desenvolver todas esas capas hasta quedarse con un objeto plano.
Después están los valores. El modelo manda literalmente la palabra undefined como si fuera un dato. Manda null en texto. Y cuando alguien no da su teléfono, en lugar de omitir el campo, manda cosas como "no", "sin teléfono" o un guion. Todo eso hay que normalizarlo, o acabas con una ficha de cliente que dice que su teléfono es "undefined".
Y por último la validación: si faltan campos obligatorios, el error que se devuelve enumera cuáles faltan. No es cosmética. Ese mensaje vuelve al agente, y un agente que lee "faltan: email, fecha" puede preguntar por ellos y reintentar. Uno que lee "error" se queda parado.
La fecha que el agente reutiliza
El fallo más curioso, y el que más cuesta reproducir: el agente propone fechas del pasado.
Pasa porque el modelo tiene toda la conversación en su contexto, y en conversaciones largas acaba reutilizando una fecha que se mencionó al principio, o una que aparecía en un ejemplo. No está roto: está haciendo lo que hace un modelo, que es continuar un patrón.
La defensa es una comprobación explícita contra la fecha de hoy en la zona horaria correcta, y un mensaje de error que no se limita a rechazar:
if (date < todayISO) {
throw new Error('La fecha de reserva (' + date + ') está en el pasado. ' +
'Fecha actual en Madrid: ' + todayISO + '. El agente probablemente ' +
'reutilizó una fecha antigua. Pide al usuario una nueva fecha.')
}
Ese mensaje está escrito para que lo lea el modelo, no una persona. Le dice qué ha pasado, cuál es la fecha real y qué tiene que hacer a continuación. Escribir los errores pensando en quién los va a leer es, en sistemas con agentes, la diferencia entre uno que se recupera solo y uno que necesita que intervengas.
El horario de verano no se escribe a mano
Para crear un evento en el calendario hace falta la hora con su desfase horario. La tentación es escribir +01:00 y seguir, y funciona hasta el último domingo de marzo, cuando España pasa a +02:00 y todas las citas se desplazan una hora sin que nadie entienda por qué.
La forma de evitarlo sin librerías de fechas es preguntarle al propio sistema: coges las doce del mediodía UTC de ese día, pides que te lo formateen en la zona horaria de Madrid y miras qué hora sale. Si sale la una, el desfase es una hora. Si salen las dos, son dos.
Es un truco feo y es correcto por construcción: no hay que mantener ninguna tabla de cuándo cambia la hora, porque esa tabla ya la tiene el sistema operativo.
Lo que cuesta mantenerlo
Para no acabar con la impresión de que esto queda terminado, un aviso honesto sobre el coste real.
Al separar la consulta de disponibilidad de la creación de la cita, acabas con dos sitios que saben cuál es tu horario: el que genera los huecos que se ofrecen, y el que calcula alternativas cuando hay un choque. Si cambias el horario de apertura en uno y olvidas el otro, el sistema empieza a ofrecer huecos por un lado que el otro no contempla. No da error, no rompe nada, y solo se nota cuando alguien intenta reservar a una hora rara.
Es exactamente el mismo tipo de fallo silencioso que la conversión a Markdown que contábamos en otro artículo: el sistema responde, parece correcto, y está mal. Si montas esto, lo primero que deberías hacer después de que funcione es sacar el horario a un único sitio del que lean los dos.
Preguntas frecuentes
¿Se puede hacer esto con cualquier calendario?
Con cualquiera que permita consultar y crear eventos desde fuera por API. Google Calendar y Microsoft 365 sí. Los programas de gestión sectoriales, depende: algunos lo permiten, otros solo con licencias concretas y otros no lo permiten en absoluto. Es lo primero que hay que comprobar, antes de prometer nada.
¿Y si el software no permite integración?
Entonces el asistente recoge la solicitud con todos los datos y la deja lista para confirmar en un clic. Es la primera opción del principio del artículo. Funciona, es útil, y hay que llamarla por su nombre en lugar de decir que reserva.
¿Hace falta n8n para montarlo?
No. Nosotros lo tenemos en n8n porque el resto del sistema vive ahí y se ve de un vistazo, pero la lógica es la misma en código: desenvolver la entrada, validar, releer el calendario, comprobar solape, escribir o devolver alternativas. La herramienta es lo de menos.
¿Cómo sé que esto funciona de verdad?
Abriendo el chat de esta web y reservando. Es el mismo sistema que se describe aquí, y la cita aparece en un calendario real con su correo de confirmación.