Que un agent d’IA reservi de debò: el forat que ja no existeix

La diferència entre un assistent que recull sol·licituds i un que reserva al teu calendari. Teardown del sistema que fem servir, amb la condició de cursa, l’horari d’estiu i què passa quan el model s’inventa la data.

Publicat el 15 de setembre del 2026

Punts clau

  • Gairebé tots els assistents que diuen que reserven en realitat recullen una sol·licitud que algú confirma després a mà.
  • Reservar de debò exigeix dues eines, no una: consultar forats i crear la cita. I entre les dues passa temps.
  • El que es comprova en oferir un forat no val en confirmar-lo: cal tornar a mirar el calendari just abans d’escriure.
  • No diguis només que no. Un xoc d’horari ha de respondre amb alternatives o l’usuari abandona.
  • Al model no se li pot confiar l’entrada: envia dates velles, cadenes amb la paraula "undefined" i telèfons que diuen "no".

Reservar de debò o semblar-ho

Quan un proveïdor et diu que el seu assistent "reserva cites", convé preguntar una cosa concreta: escriu al calendari, o deixa una sol·licitud perquè algú la confirmi?

La segona opció és perfectament raonable i és el que fa la majoria. Però no és el mateix. Si algú tria un forat a les onze de la nit i l’endemà al matí recepció li diu que aquell forat no era lliure, has convertit una reserva en una gestió, i a sobre amb una decepció al davant.

Aquest article va del que cal per a la primera opció. És el sistema que fa servir l’assistent d’aquesta web per agendar les sessions inicials, i es pot provar des del xat.

Dues eines, no una

L’agent no parla amb el calendari directament. Té dues eines separades, i aquesta separació és la que fa que el sistema sigui fiable.

La primera consulta disponibilitat. Rep una data, o cap, i torna forats lliures. Té tres modes, perquè la gent pregunta de tres maneres diferents: per un dia concret, per una setmana sencera, o directament "el primer que tinguis". Aquest últim mode és el que més es fa servir, i el que evita la conversa tediosa d’anar proposant dies un a un.

La segona crea la cita. Rep les dades de la persona i el forat triat, i és l’única que escriu.

Entre que la primera ofereix un forat i la segona el confirma, passa temps real: l’usuari llegeix, s’ho pensa, escriu el seu nom i el seu email. Poden ser dos minuts o poden ser deu.

El forat que ja no existeix

Aquí hi ha el problema que gairebé ningú contempla. Si el sistema confia en la disponibilitat que va consultar al principi de la conversa, tard o d’hora crea una cita a sobre d’una altra. N’hi ha prou que dues persones estiguin parlant amb l’assistent alhora, o que algú de l’equip fiqui alguna cosa al calendari a mà mentrestant.

La solució no és elegant ni llesta, és simplement la correcta: l’eina que crea la cita torna a consultar el calendari just abans d’escriure. No es fia del que es va comprovar fa cinc minuts, ni tan sols de la seva pròpia consulta anterior.

El flux complet queda així: rebre les dades, mirar els esdeveniments que ja hi ha aquell dia, validar que el forat demanat continua lliure, i només llavors crear l’esdeveniment i enviar els correus. Si en aquell últim moment el forat està ocupat, no s’escriu res.

Solapar no és coincidir

Un detall petit que arruïna el sistema si es fa malament: comprovar si dues cites xoquen no és comprovar si comencen a la mateixa hora.

Una cita de 10:00 a 10:20 i una altra de 10:10 a 10:30 no comencen igual i se solapen igualment. La comprovació correcta és la d’intervals de tota la vida:

const isOccupied = busy.some(b => slotStart < b.end && slotEnd > b.start)

Dos intervals se solapen si un comença abans que l’altre acabi i acaba després que l’altre comenci. Quatre comparacions, cap cas rar. Si en lloc d’això compares hores d’inici, el sistema funciona a les proves, on tothom tria hores en punt, i falla en producció.

No diguis només que no

Quan el forat està ocupat, la resposta fàcil és un error. És també la manera més segura de perdre aquella persona: acaba de dedicar tres minuts a donar-te les seves dades i el que rep és una negativa.

El que fa el sistema és calcular, en aquell mateix moment, quins forats continuen lliures aquell dia i tornar-ne fins a cinc, juntament amb el missatge que aquell horari ja no està disponible. L’agent no ha de tornar a preguntar res: li arriben les alternatives i les ofereix a la mateixa frase en què dona la mala notícia.

És una decisió de producte més que tècnica. El cost és mitja dotzena de línies. La diferència entre "no està disponible" i "aquell ja no hi és, però tinc les 11:00, les 11:30 o les 12:00" és la diferència entre una conversa que mor i una que continua.

El model envia brossa i cal comptar-hi

Aquesta és la part que no apareix en cap demo i que s’emporta la meitat del codi.

Un model de llenguatge cridant una eina no és un formulari. Envia el que li sembla, i el format canvia segons per on entri la petició. De vegades les dades vénen al cos, de vegades dins d’un camp que conté una cadena amb JSON a dins, de vegades en un altre camp diferent. El primer que fa el sistema, abans de mirar res, és desembolicar totes aquestes capes fins a quedar-se amb un objecte pla.

Després hi ha els valors. El model envia literalment la paraula undefined com si fos una dada. Envia null en text. I quan algú no dona el seu telèfon, en lloc d’ometre el camp, envia coses com "no", "sense telèfon" o un guió. Tot això cal normalitzar-ho, o acabes amb una fitxa de client que diu que el seu telèfon és "undefined".

I per últim la validació: si falten camps obligatoris, l’error que es torna enumera quins falten. No és cosmètica. Aquell missatge torna a l’agent, i un agent que llegeix "falten: email, data" pot preguntar-los i reintentar. Un que llegeix "error" es queda parat.

La data que l’agent reutilitza

L’error més curiós, i el que més costa reproduir: l’agent proposa dates del passat.

Passa perquè el model té tota la conversa al seu context, i en converses llargues acaba reutilitzant una data que es va mencionar al principi, o una que apareixia en un exemple. No està trencat: està fent el que fa un model, que és continuar un patró.

La defensa és una comprovació explícita contra la data d’avui a la zona horària correcta, i un missatge d’error que no es limita a rebutjar:

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.')
}

Aquell missatge està escrit perquè el llegeixi el model, no una persona. Li diu què ha passat, quina és la data real i què ha de fer a continuació. Escriure els errors pensant en qui els llegirà és, en sistemes amb agents, la diferència entre un que es recupera sol i un que necessita que hi intervinguis.

L’horari d’estiu no s’escriu a mà

Per crear un esdeveniment al calendari cal l’hora amb el seu desfasament horari. La temptació és escriure +01:00 i continuar, i funciona fins a l’últim diumenge de març, quan Espanya passa a +02:00 i totes les cites es desplacen una hora sense que ningú entengui per què.

La manera d’evitar-ho sense llibreries de dates és preguntar-ho al sistema mateix: agafes les dotze del migdia UTC d’aquell dia, demanes que t’ho formatin a la zona horària de Madrid i mires quina hora surt. Si surt la una, el desfasament és una hora. Si surten les dues, són dues.

És un truc lleig i és correcte per construcció: no cal mantenir cap taula de quan canvia l’hora, perquè aquella taula ja la té el sistema operatiu.

El que costa mantenir-ho

Per no acabar amb la impressió que això queda enllestit, un avís honest sobre el cost real.

En separar la consulta de disponibilitat de la creació de la cita, acabes amb dos llocs que saben quin és el teu horari: el que genera els forats que s’ofereixen, i el que calcula alternatives quan hi ha un xoc. Si canvies l’horari d’obertura en un i oblides l’altre, el sistema comença a oferir forats per un costat que l’altre no contempla. No dona error, no trenca res, i només es nota quan algú intenta reservar a una hora rara.

És exactament el mateix tipus d’error silenciós que la conversió a Markdown que explicàvem en un altre article: el sistema respon, sembla correcte, i està malament. Si muntes això, el primer que hauries de fer després que funcioni és treure l’horari a un únic lloc del qual llegeixin tots dos.

Preguntes freqüents

Es pot fer això amb qualsevol calendari?

Amb qualsevol que permeti consultar i crear esdeveniments des de fora per API. Google Calendar i Microsoft 365 sí. Els programes de gestió sectorials, depèn: alguns ho permeten, altres només amb llicències concretes i altres no ho permeten en absolut. És el primer que cal comprovar, abans de prometre res.

I si el programa no permet integració?

Llavors l’assistent recull la sol·licitud amb totes les dades i la deixa llesta per confirmar en un clic. És la primera opció del principi de l’article. Funciona, és útil, i cal dir-ne pel seu nom en lloc de dir que reserva.

Cal n8n per muntar-ho?

No. Nosaltres ho tenim a n8n perquè la resta del sistema hi viu i es veu d’un cop d’ull, però la lògica és la mateixa en codi: desembolicar l’entrada, validar, rellegir el calendari, comprovar solapament, escriure o tornar alternatives. L’eina és el de menys.

Com sé que això funciona de debò?

Obrint el xat d’aquesta web i reservant. És el mateix sistema que es descriu aquí, i la cita apareix en un calendari real amb el seu correu de confirmació.