La pregunta de si muntar un agendador o pagar-ne un es respon gairebé sempre mirant el preu, que és justament el criteri que menys importa. El que decideix és què estàs agendant i on vols que acabin aquestes dades. Aquí veuràs els dos casos que es confonen contínuament, què guanyes exactament tenint les cites a la teva base de dades i els dos problemes difícils que ningú no preveu abans de començar. Els exemples van en Laravel perquè és una de les nostres especialitats a AndorraDev, però el model de disponibilitat i els dos problemes difícils són els mateixos en qualsevol stack.
Calendly resol un problema que no sempre és el teu
Abans de decidir convé separar dos casos que s'assemblen molt i no ho són.
Un: ets un comercial que necessita coordinar la seva agenda real amb la de gent de fora, amb reprogramacions, recordatoris i sincronització amb el calendari on ja vius. Aquí Calendly fa exactament el que necessites i muntar-ho tu és llençar el temps.
Dos: tens una pàgina de "sol·licitar demo" i vols que el visitant triï un forat sense sortir del teu web. Aquí l'agendador és un formulari de captació amb calendari, i la dada que entra és un lead, no un esdeveniment d'agenda. És el cas en què compensa, i per una raó que no és el preu: el lead es queda a la teva base de dades, entra a la mateixa bústia que la resta dels teus formularis i no obliga el visitant a saltar a un domini aliè just en el moment de convertir.
Aquest article va del segon cas.
Un agendador és disponibilitat menys reserves
Aquesta frase és literalment el model sencer, i per això cap en tan poc codi.
Declares les franges en què atens, la durada de cada cita, el descans entre cites, amb quanta antelació mínima es pot reservar i fins a quants dies vista. Amb això generes els forats possibles d'un dia i li restes els que ja estan reservats. El que queda és el que es pinta.
'demo' => [
'duration' => 30, // minuts per reunió
'buffer' => 0, // descans entre reunions
'days_ahead' => 21, // fins on es pot reservar
'min_notice' => 120, // antelació mínima en minuts
// 1 = dilluns ... 7 = diumenge
'availability' => [
1 => [['10:00', '14:00'], ['16:00', '20:30']],
5 => [['10:00', '14:00']],
],
],
Declarar-ho a la configuració en comptes de fer-ho en una interfície d'administració és una decisió, i per a aquest cas és la correcta: la teva disponibilitat comercial canvia dues vegades l'any, no cada dia. Construir un panell per editar-la és més codi a mantenir que la mateixa funcionalitat.
Els forats es calculen, no s'emmagatzemen
Si la disponibilitat és una resta, el costat esquerre no cal desar-lo. Això elimina d'un cop la taula de forats, el procés que l'omple i tots els problemes de sincronització que porten tots dos.
No hi ha una taula de forats que calgui omplir per endavant ni un procés nocturn que generi el calendari del mes que ve. Només es desen les reserves. Els forats lliures són una funció de la configuració i d'aquesta taula, avaluada en el moment.
Això vol dir que canviar la teva disponibilitat té efecte immediat, que no hi ha res per resincronitzar i que no existeix l'estat inconsistent clàssic de "la taula de forats diu una cosa i les reserves en diuen una altra".
Dues reserves simultànies és l'únic problema difícil
Calcular forats, restar reserves i pintar un calendari són operacions deterministes: donen el mateix avui que d'aquí a un any. Només hi ha un punt de tot el sistema on el resultat depèn de a quina velocitat passen les coses.
Dos visitants poden estar mirant la mateixa pantalla i prémer el mateix forat el mateix segon. La solució que funciona té dues capes i calen totes dues.
La primera és un índex únic a la base de dades sobre el parell (tipus de reunió, hora d'inici). És la garantia dura: encara que tota la lògica falli, la base de dades no permet dues reserves per al mateix forat.
La segona és una recomprovació en el moment de confirmar, just abans de desar, perquè el segon usuari rebi un missatge entenedor en comptes d'un error:
if (! $this->generator()->isFree($this->selectedSlot)) {
$this->addError('selectedSlot', __('Ese hueco acaba de ocuparse.'));
$this->selectedSlot = null;
return;
}
Sense l'índex, la comprovació sola té una finestra de cursa. Sense la comprovació, l'índex només produeix un error lleig. Amb les dues, el cas està cobert i a més és amable.
L'índex únic sobre (tipus de reunió, hora d'inici) és la garantia dura contra la doble reserva, i convé que ho continuï sent quan ampliïs. Si més endavant hi afegeixes cancel·lacions, decideix llavors si l'estat entra a l'índex o si cancel·lar esborra la fila. Presa en ampliar, la decisió és trivial; presa amb dades a dins, ja no.
El fus horari és on es trenquen aquests sistemes
Gairebé tots els agendadors que fallen ho fan pel mateix, i no és la concurrència. És que en algun punt algú va desar una hora sense dir d'on era, i a partir d'aquí l'error viatja sense que ningú se n'adoni fins que un client es presenta amb una hora de diferència.
Un agendador maneja tres rellotges alhora: el de l'organitzador, que és on estan declarades les franges; el del visitant, que és on estarà quan soni la cita; i UTC, que és on cal desar-ho tot per poder comparar.
La regla que evita el noranta per cent dels problemes és senzilla: desa sempre en UTC i mostra sempre dient en quin fus mostres. Un selector que pinta "10:00" sense dir d'on és una trampa: el visitant d'un altre país tria creient que són les deu del seu matí i descobreix l'hora real al correu de confirmació.
Aquell cartellet de "horari d'Andorra (CET)" al costat del selector costa una línia i estalvia reunions perdudes.
On és el límit d'un component i comença el producte
Aquí és on es decideix de veritat, així que convé tenir-ho al davant. Un agendador lleuger cobreix el cas de captació sencer i deixa fora, expressament, quatre coses que són funcionalitat de producte.
La sincronització amb el teu calendari extern. L'agendador coneix les seves pròpies reserves, no les reunions que tinguis a Google Calendar. Per a captació es resol declarant franges una mica més conservadores que la teva agenda real, i és la diferència de fons amb una eina de subscripció.
La reprogramació autònoma. El correu de confirmació convida a respondre, cosa que per a un volum de demos comercials és suficient i a més obre conversa. Si el teu negoci són cites de servei amb volum, allà sí que vols un enllaç i un flux propi.
Els recordatoris i la invitació de calendari. Una tasca programada i un fitxer .ics. No és difícil i s'afegeix quan facin mal les absències, que sol ser bastant després de llançar.
L'enviament en cua. Els correus surten en el moment, cosa que amb volum baix és el correcte i és una peça menys per mantenir. Quan el volum ho demani, encuar aquella crida és una línia.
Quan compensa cada opció
Amb tot l'anterior sobre la taula, la decisió es torna bastant neta.
Compensa muntar-ho tu si l'agendador és una pàgina de captació, si la teva disponibilitat és estable, si t'importa que el visitant no surti del teu domini i si la dada s'ha de quedar a la teva base perquè alimentarà el teu CRM. Són unes set-centes línies i una taula.
Compensa pagar la subscripció si necessites sincronització real amb el calendari on vius, reprogramació autònoma, recordatoris automàtics, cobrament en el moment de reservar o repartiment entre diverses persones. Tot això és funcionalitat de producte, no de component, i reconstruir-ho és un projecte.
I hi ha una tercera resposta que és la que donem més vegades: començar pel més simple i afegir. Afegir recordatoris quan facin mal les absències, i sincronització quan de veritat tinguis conflictes d'agenda, és molt més barat que treure't una dependència externa a la qual ja has enganxat tres integracions.
I si no treballes amb Laravel
Les decisions es traslladen tal qual:
- Desa només les reserves i calcula els forats. Una taula de forats precalculats és estat que es desincronitza.
- Posa l'índex únic a la base de dades, no només la comprovació al codi.
- Tot en UTC, i digues sempre en quin fus mostres.
- Reinicia i calcula de manera mandrosa, en el moment de la petició, en comptes de fer-ho amb tasques programades.
- I decideix aviat si hi haurà cancel·lacions, perquè condiciona l'esquema.
On és el codi
El paquet és a Packagist, el codi a GitHub i la documentació del paquet recull totes les opcions. És MIT, una sola taula i sense dependències de tercers.
Està en producció a la pàgina de demo de Crowd Legal, on cada reserva arriba a la mateixa bústia que la resta de formularis de contacte, perquè una demo agendada és un lead més.
Si el teu negoci viu de les cites i això se't queda curt, això ja és una altra cosa i és exactament el que fem a plataformes de reserves. Si ens expliques el volum i el flux, et diem si et val un component o necessites producte.