Crea tu propio Calendly con IA: las seis piezas que de verdad importan
Una página de reservas parece la aplicación más aburrida del mundo. Miras mi calendario, te enseño los huecos libres, eliges uno.
Tres frases. ¿Qué puede salir mal?
Pues resulta que dentro de esas tres frases viven la aritmética de intervalos, el horario de verano, una condición de carrera de manual, un formato de fichero de 1998 con reglas de plegado a 75 octetos y una fuga de privacidad que casi nadie mira.
Por eso Calendly es el proyecto perfecto para el fin de semana. No porque sea fácil, sino porque parece fácil y no lo es, y esa distancia es justo donde se aprende.
Este post no repite el método. La receta general —cómo eliges qué reconstruir, el prompt cero, los criterios de aceptación, los prompts de rescate— la tienes en cómo crear tu propio X con IA. Aquí vamos a lo concreto:
- Un prompt único para arrancar ahora mismo, si no quieres leer más
- Qué entra y qué no entra en un Calendly propio que se termina
- Las seis piezas donde se juega el partido, con el prompt de cada una
- La fuga de privacidad que tu agente no va a detectar solo
- Qué hacer con los recordatorios, el trozo que todo el mundo olvida
- Dónde mirar cuando te atasques, ahora que Cal.com ha cambiado de sitio
- Cómo generar el checklist que te dice si esto está de verdad terminado
Vamos al lío.
El prompt único, si quieres empezar ahora mismo ¶
Hay gente que ha llegado hasta aquí con el portátil abierto y el agente esperando. Para ti va esto.
Copia este prompt, pégalo y dale al enter.
Vas a construir "Slot", una alternativa deliberadamente pequeña a
Calendly, para una sola persona que ofrece reuniones de 30 y 60
minutos. Quiero una aplicación completa que funcione de verdad, no
una maqueta ni una colección de componentes sueltos.
Usa el stack que veas en el repositorio; si está vacío, propón uno
habitual y pregúntame antes de instalar nada exótico.
ANTES DE ESCRIBIR CÓDIGO
Devuélveme un plan corto con las rutas, los endpoints, las tablas y
las decisiones que estés tomando. Espera a que yo lo apruebe.
MODELO DE DATOS
MeetingType (duración, slug, buffer antes y después, aviso mínimo,
horizonte de reserva), AvailabilityRule (franjas semanales más
excepciones por fecha) y Booking (invitado, estado, tokens de
gestión). Nada más.
LAS TRES COSAS QUE NO PUEDES HACER MAL
1. La generación de huecos vive en un módulo de dominio puro, sin
base de datos y sin consultar el reloj por dentro: el instante
actual entra como parámetro. El orden es expandir reglas, restar
la ocupación ya ensanchada con los buffers, recortar por aviso
mínimo y horizonte, y trocear al final.
2. Los instantes se guardan en UTC y se guarda aparte el NOMBRE de
la zona horaria de cada parte, no el desfase. La conversión ocurre
solo al pintar, y cada hora mostrada lleva su etiqueta de zona.
3. La reserva se confirma con una comprobación final de
disponibilidad dentro de la misma transacción que la inserta, y
con una restricción de unicidad en base de datos por debajo. Quien
pierda la carrera recibe "ese hueco acaba de ocuparse" con
alternativas, no un error genérico.
PANTALLAS
Página pública de reserva (datos de la cita, selector de zona
horaria, tira de días, lista de huecos, formulario y confirmación),
editor del tipo de reunión, editor de disponibilidad y panel con las
próximas citas. Cada pantalla declara su acción principal, su estado
de carga, su estado vacío y su error recuperable.
FUERA DE ALCANCE
Equipos, round-robin, pagos, varios proveedores de calendario,
formularios de enrutado y analítica. Si crees que hace falta algo de
esto, pregúntame en vez de construirlo.
TESTS
Unitarios del generador de huecos con estos casos: reserva en medio
del día, solape solo con el buffer, hueco en el límite del aviso
mínimo, regla que cruza el cambio de hora de octubre en
Europe/Madrid y regla que cruza la medianoche. Y un test de
concurrencia: cien intentos simultáneos sobre el mismo hueco
producen exactamente una reserva confirmada.
ENTREGA
Migraciones, datos de ejemplo, .env.example comentado y un README
que me permita levantar esto desde cero dentro de seis meses.
Cuando termines, dime qué has dejado a medias y qué decisiones
tomaste sin preguntarme.
Con eso tienes una primera versión que se puede abrir en el navegador y reservar una cita.
Ahora la parte honesta, y es la que importa: eso es una primera versión, no un producto.
No habla con tu calendario real, no manda emails, no tiene enlaces de cancelación y filtra tu agenda sin que te des cuenta. El prompt cubre tres de las seis piezas difíciles. Las otras tres son las que más tiempo te van a comer.
Así que vuelve por aquí cuando lo tengas ejecutándose. El resto del post es justo eso.
El nivel de vibe coding al que apunta este post ¶
Conviene situar esto antes de empezar, porque hay tres formas de construir esta aplicación y solo una encaja aquí.
La primera es el vibe coding básico: le pides “hazme un Calendly”, aceptas lo que salga y lo despliegas. Funciona para enseñárselo a un amigo. No funciona para que tu cliente reserve una llamada. Si estás en ese punto y quieres el método de tres tiempos para salir de él, empieza por cómo programar con IA tu primer proyecto.
La tercera es la metodología completa: especificación, artefactos versionados, criterios de aceptación formales. Es lo que te conviene en un proyecto que va a durar, y lo tienes desarrollado en la guía de Spec Driven Development. Para un proyecto de fin de semana es matar moscas a cañonazos.
Este post vive en la segunda: vibe coding con criterio. Sigues conversando con el agente, sigues sin escribir specs formales, pero sabes de antemano cuáles son las seis piezas donde el modelo se va a equivocar y llegas a ellas con el prompt preparado. Si quieres el marco de cuánta garantía pedir según lo que te juegas, lo desarrollo en cómo hacer bien vibe coding.
🔑 La diferencia entre el vibe coding que funciona y el que no funciona rara vez está en el prompt. Está en saber de antemano dónde va a fallar el modelo, y llegar a ese punto con la pregunta correcta preparada.
Sobre la herramienta, da bastante igual: cualquier agente de terminal con acceso a ficheros te sirve. Claude Code, Codex, OpenCode o el Antigravity CLI de Google, que desde junio de 2026 es quien recoge el testigo del antiguo Gemini CLI y se invoca con agy. Si todavía no has elegido, tienes la comparativa de agentes de IA en terminal. Los prompts que vienen funcionan igual en todos.
El escalón de arriba, cuando el proyecto deja de caber en un fin de semana
Recorre el ciclo completo de Spec Driven Development con OpenSpec en modo asistido, que es justo la marcha que toca meter cuando estas seis piezas crecen.
Asomarte al curso gratis →Qué entra y qué no entra en tu Calendly ¶
El alcance es la decisión más importante de todo el proyecto, y se toma antes del primer prompt.
Un Calendly propio que se termina cubre esto:
- Tipos de reunión con duración, ubicación, buffer antes y después, aviso mínimo y horizonte de reserva.
- Reglas de disponibilidad semanales, más excepciones para fechas concretas.
- Generación de huecos con la zona horaria del anfitrión y visualización en la del invitado.
- Reserva con datos del invitado, pantalla de confirmación y enlaces de reagendar y cancelar.
- Panel del anfitrión con las próximas citas.
Y deja fuera esto, a propósito:
- Equipos y reparto round-robin
- Pagos
- Más de un proveedor de calendario
- Formularios de enrutado por respuestas
- Analítica de conversión
Esa lista de exclusiones no es pereza. Es la parte del producto que convierte un fin de semana en un trimestre. Escríbela en tu fichero de contexto antes de empezar y el agente dejará de proponerte cosas que no vas a construir.
Las seis piezas donde se juega el partido ¶
Aquí está el mapa. Cada una de estas piezas tiene una trampa concreta, y ninguna se resuelve sola por muy bien que escribas el prompt inicial.
| Pieza | Por qué es difícil | Cómo sabes que está bien |
|---|---|---|
| Motor de huecos | Es aritmética de intervalos, no calendario | Los buffers impiden dos citas seguidas |
| Zonas horarias | El cambio de hora rompe las reglas semanales | Mismos huecos locales a ambos lados del cambio |
| Carrera por el hueco | La lista que ve el invitado ya está caducada | Cien envíos a la vez, una sola reserva |
| Qué es “ocupado” | Google no responde lo que crees | Un evento tentativo no te bloquea el día |
| Enlaces sin cuenta | El token es toda la autorización | Un enlace viejo no toca la cita nueva |
| El fichero .ics | Formato quisquilloso de los noventa | Importa igual en los tres clientes grandes |
Las tres primeras son de lógica. Las tres siguientes son de integración. Y las de integración son las que más tiempo te van a comer, aunque parezcan las accesorias.
Saber dónde se va a equivocar el modelo antes de escribir el prompt es lo que separa un proyecto terminado de uno abandonado. Cada domingo compartimos ese tipo de aprendizajes con +6.700 developers.
Quiero esa dinamita 🧨Pieza 1: el motor de huecos es resta de intervalos ¶
Esta es la función central de toda la aplicación y merece vivir sola, sin base de datos y sin interfaz.
Implementa getAvailableSlots como módulo de dominio puro, sin acceso a
base de datos y sin consultar el reloj por dentro.
Entradas: reglas semanales en la zona del anfitrión, excepciones por
fecha, intervalos ocupados del calendario externo, reservas
existentes, buffer antes y después, aviso mínimo, horizonte de
reserva, rango consultado y el instante actual como parámetro.
Salida: instantes de inicio en UTC.
El orden de la operación importa:
1. Expande las reglas a intervalos concretos del rango.
2. Resta los intervalos ocupados, ya ensanchados con los buffers.
3. Recorta a la ventana de aviso mínimo y horizonte.
4. Trocea lo que sobreviva en inicios del tamaño de la reunión.
Está terminada cuando los buffers impiden de forma demostrable dos
citas consecutivas y ningún hueco generado viola el aviso mínimo en
el momento de generarse.
Fíjate en el instante actual como parámetro de entrada. Es un detalle diminuto con consecuencias enormes: una función que consulta el reloj por dentro no se puede probar. Una que lo recibe, sí.
Y el orden tampoco es decorativo. Si troceas antes de restar los buffers, te salen huecos de quince minutos entre reuniones que nadie puede usar. Si recortas el horizonte antes de expandir las reglas, pierdes las excepciones de fecha. Dale el orden escrito y te ahorras dos tardes.
Pieza 2: el día que existe dos veces ¶
El 25 de octubre a las dos y media de la madrugada, en España, el reloj vuelve atrás. Ese día hay dos horas que se llaman igual. En marzo pasa lo contrario: una hora no existe.
Ahora imagina una regla que dice “los domingos de 2:00 a 6:00”.
Añade tests de zona horaria a getAvailableSlots. Todos deben fallar
antes de que toques la implementación.
Casos obligatorios:
- Una regla semanal que cruza el cambio de hora de octubre en
Europe/Madrid: la hora repetida no puede generar huecos duplicados.
- El cambio de marzo: una regla que cae en la hora inexistente.
- Una regla que cruza la medianoche.
- Una excepción de fecha que elimina un día entero.
- Un invitado en America/New_York con anfitrión en Europe/Madrid, en
una semana donde los dos países cambian la hora en fechas distintas.
- Un evento ocupado que solapa solo de forma parcial con un hueco.
Dime cuáles fallan y por qué antes de arreglar nada.
Ese penúltimo caso es el que casi nadie prueba: Estados Unidos y Europa no cambian la hora el mismo fin de semana. Durante un par de semanas al año, la diferencia entre Madrid y Nueva York no es la de siempre. Si tu aplicación asume un desfase fijo, esas semanas produce citas a la hora equivocada.
⚠️ Guarda el instante en UTC, guarda aparte el nombre de la zona de cada parte (no el desfase, el nombre) y convierte solo al pintar. Y muestra siempre la etiqueta de zona junto a la hora. Es la diferencia entre un bug y una reclamación.
Que los tests fallen primero no es un capricho. Un agente que escribe implementación y tests en el mismo turno tiende a escribir tests que pasan con la implementación que acaba de escribir, aunque las dos estén mal.
Pieza 3: dos personas, un hueco, un solo ganador ¶
La lista de huecos que ve un invitado está caducada en el momento en que se pinta. Dos personas abren tu página a la vez, las dos ven las 10:00, las dos rellenan el formulario con calma.
La solución no es una comprobación. Son tres capas.
Implementa el camino de confirmación de reserva con tres defensas
independientes:
1. Al elegir hueco, crea una retención temporal con caducidad de
cinco minutos, para que rellenar el formulario no sea una carrera.
2. Al enviar, ejecuta la comprobación final de disponibilidad y la
inserción de la reserva dentro de la MISMA transacción,
revalidando contra reglas, reservas y retenciones vivas.
3. Respalda todo con una restricción de unicidad en base de datos
como última línea de defensa.
Quien pierda la carrera no recibe un error genérico: recibe "ese
hueco acaba de ocuparse" con las tres alternativas más cercanas.
Las retenciones caducadas se liberan comprobando la caducidad al
leer. Nada de tareas de limpieza programadas.
Antes de implementar, propóneme dos formas distintas de escribir la
restricción de unicidad, con sus contras.
Ese último párrafo es un patrón que uso mucho: pedir alternativas antes que soluciones. Probablemente te ofrezca un índice único sobre tipo de reunión y hora de inicio filtrado por estado, o una restricción de exclusión de PostgreSQL sobre rangos temporales. La primera es sencilla y se queda corta si permites duraciones distintas. La segunda es más potente y menos gente sabe mantenerla.
Elige tú. Esa elección es tu trabajo, no el suyo.
-- Opción con restricción de exclusión: PostgreSQL rechaza el solape
-- Requiere la extensión btree_gist activada en la base de datos
ALTER TABLE "Booking" ADD CONSTRAINT no_overlapping_bookings
EXCLUDE USING gist (
"meetingTypeId" WITH =,
tstzrange("startsAt", "endsAt") WITH &&
) WHERE (status = 'confirmed');
Y la prueba de que funciona no es un test unitario. Es lanzar cien envíos concurrentes al mismo hueco y contar las reservas confirmadas. Si sale más de una, no tienes tres capas: tienes tres adornos.
Pieza 4: Google no llama “ocupado” a lo que tú crees ¶
Aquí empieza la parte de integración, y aquí es donde tu agente va a escribir código que funciona en su cabeza y falla en la tuya.
Cuando le pides a Google los intervalos ocupados de un calendario, te devuelve bloques. Pero hay decisiones de producto escondidas en esa respuesta que ninguna documentación te va a tomar por ti:
- Los eventos tentativos. Una invitación que has recibido pero no has aceptado, ¿te bloquea el hueco? Calendly te deja elegir. Tú también tienes que elegir.
- Los eventos de todo el día. Un cumpleaños en tu calendario no debería vaciarte la agenda laboral. Un día de vacaciones sí.
- Los calendarios múltiples. El personal, el del trabajo y el compartido del equipo. ¿Cuáles se consultan para bloquear y en cuál se escribe la cita nueva? No tienen por qué ser el mismo.
- Los eventos que tú mismo creaste desde esta aplicación. Si no los excluyes al consultar, se cuentan dos veces.
Implementa el adaptador de Google Calendar con estas decisiones
explícitas, no implícitas:
- Consulta de ocupación sobre una lista configurable de calendarios;
la escritura del evento va a uno solo, elegido aparte.
- Los eventos tentativos bloquean o no según un ajuste del anfitrión,
con un valor por defecto documentado.
- Los eventos de todo el día bloquean solo si el anfitrión lo activa.
- Excluye de la consulta los eventos creados por esta aplicación,
identificados por una propiedad extendida propia.
- Alcance mínimo: leer ocupación y crear el evento. Nada más.
- Los tokens de larga duración se guardan cifrados y jamás llegan al
navegador.
- Si el proveedor falla, la aplicación sigue mostrando huecos basados
solo en las reservas locales, con aviso en el log.
Y explícame qué pasa cuando el token se revoca o expira, y cómo lo
vas a detectar antes de que el anfitrión se entere por un cliente
enfadado.
Esa última pregunta separa un proyecto de juguete de uno que aguanta. Los tokens revocados son la causa número uno de “mi calendario dejó de sincronizar y no me enteré en tres semanas”.
Sobre cómo mantener la ocupación fresca tienes dos caminos: consultar el calendario cada vez que alguien pide huecos, o suscribirte a las notificaciones de cambio del proveedor y cachear. Para un proyecto propio con tráfico bajo, consultar en cada petición es más simple y más correcto. Empieza ahí y no optimices lo que todavía no duele.
Pieza 5: enlaces que funcionan sin cuenta ¶
Tu invitado no se va a registrar para cancelar una llamada de treinta minutos. El enlace del email es toda la autorización, y eso tiene consecuencias.
Implementa los enlaces de reagendar y cancelar del email de
confirmación.
Cada reserva recibe tokens largos y aleatorios, guardados con hash,
cada uno con ámbito de una reserva y una acción.
Reglas de ciclo de vida, que es la parte interesante:
- Los tokens caducan cuando la reunión termina.
- Cancelar revoca el token de reagendar.
- Reagendar emite tokens nuevos y mata los anteriores: un enlace de
un email viejo no puede modificar la hora nueva.
- Token inválido, caducado y revocado devuelven la MISMA respuesta,
para que nadie pueda descubrir qué reservas existen.
- Registra cada uso y limita el ritmo de intentos.
Está terminado cuando un email reenviado a un tercero permite
gestionar esa cita y solo esa, y cuando todos los enlaces dejan de
servir en cuanto la cita se cancela o pasa.
Ese detalle de que los tres errores devuelvan la misma respuesta es pequeño y muy fácil de perder en una refactorización. Merece un test propio.
Pieza 6: el .ics es más quisquilloso de lo que parece ¶
El adjunto del email parece la parte trivial. Es un fichero de texto. Hasta que lo abres en Google Calendar, Apple Calendar y Outlook y descubres que cada uno interpreta una cosa.
Genera el adjunto .ics de la confirmación.
Un VCALENDAR con un VEVENT: UID, DTSTAMP, DTSTART, DTEND, SUMMARY,
DESCRIPTION, LOCATION, ORGANIZER y ATTENDEE.
El formato tiene trampas:
- Las líneas terminan en CRLF.
- Cualquier línea de más de 75 octetos se pliega con un espacio de
continuación. Cuenta octetos, no caracteres: un emoji en el título
te delata.
- Comas, puntos y comas y saltos de línea dentro de los campos de
texto van escapados.
- Horas en UTC con sufijo Z, o un bloque VTIMEZONE en condiciones.
Nada de horas locales flotantes.
- El UID se mantiene estable por reserva: reagendar envía el mismo
UID con SEQUENCE incrementado, no un evento duplicado. Cancelar
envía METHOD:CANCEL con STATUS:CANCELLED.
Y un fallo del proveedor de email NUNCA deshace la reserva. Registra
el error y sigue.
Está terminado cuando una descripción de 200 caracteres con emoji
sobrevive al plegado intacta y un reagendado actualiza el evento
original en los tres clientes en vez de crear uno nuevo.
Ese último detalle sobre el email parece obvio leído. En el código generado sin esa instrucción, lo normal es que el envío viva dentro de la misma transacción y una caída del proveedor de correo te borre citas ya confirmadas.
Los detalles que se pierden en una refactorización (un CRLF, un token que no caduca) son justo los que nos contamos en la newsletter: 12 recursos cada semana y las aportaciones de quienes ya la reciben. Gratis, desde 2018.
Quiero esa dinamita 🧨La fuga que tu agente no va a detectar solo ¶
Aquí va la parte que no he visto en ningún tutorial de este tipo, y que a mí me parece la más interesante de toda la aplicación.
Tu página de reservas publica tus huecos libres. Por diferencia, publica también tus huecos ocupados.
Alguien que consulte tu página cada día durante un mes puede reconstruir tu agenda completa. No los títulos de las reuniones, pero sí cuándo estás ocupado, cuántas horas seguidas, qué días viajas. Para un consultor eso es información comercial. Para otras personas es información de seguridad personal.
Calendly tiene ajustes para esto. Tu versión, por defecto, no.
Revisa qué información se puede deducir de la aplicación desde fuera:
1. ¿Cuánto revela el listado de huecos sobre la agenda privada del
anfitrión? ¿Se puede reconstruir consultando a diario?
2. ¿Se puede enumerar qué anfitriones existen probando slugs?
3. ¿El endpoint de huecos tiene límite de ritmo, o alguien puede
barrer un año entero en una petición?
4. ¿La respuesta de error distingue entre "usuario no existe" y
"usuario sin huecos"?
Propón mitigaciones proporcionadas para un proyecto personal, no para
un banco. Prioriza por impacto real. No implementes nada todavía.
Las mitigaciones razonables son baratas: limitar el horizonte consultable a unas pocas semanas, poner un tope de ritmo por IP en el endpoint público y añadir un ajuste de granularidad que redondee los huecos a bloques de treinta minutos. Con eso, la reconstrucción deja de ser fiable.
Y ese prompt merece una sesión nueva, con el contexto limpio. Un agente que acaba de escribir el endpoint lleva encima todas las razones por las que le pareció buena idea. No es un revisor: es el autor defendiendo su obra.
El trozo que todo el mundo olvida: los recordatorios ¶
Tienes la aplicación funcionando. Alguien reserva. Y no aparece.
Los recordatorios son la parte menos glamurosa de Calendly y la que más valor aporta en el mundo real. También son la primera vez en el proyecto que necesitas trabajo en segundo plano, así que conviene tratarlos con cariño.
Añade recordatorios por email: uno 24 horas antes y otro una hora
antes de cada cita.
Requisitos:
- Trabajo programado que consulta las citas próximas, no un
temporizador por reserva.
- Marca de envío por recordatorio y cita, para que un reintento del
trabajo no envíe dos veces el mismo aviso.
- Cancelar o reagendar invalida los recordatorios pendientes.
- Si la cita es dentro de menos de 24 horas al reservarse, ese
recordatorio no se envía.
- El estado del trabajo es visible: pendiente, enviado, fallido.
Está terminado cuando ejecutar el trabajo tres veces seguidas envía
exactamente los mismos correos que ejecutarlo una vez.
💡 “Ejecutar el trabajo tres veces seguidas envía exactamente los mismos correos que ejecutarlo una vez.” Esa frase es la definición práctica de idempotencia y vale para cualquier trabajo en segundo plano que escribas en tu vida. Guárdatela.
Dónde mirar cuando te atasques (y una novedad que cambia el sitio) ¶
Durante años la respuesta a “quiero ver cómo lo hace alguien de verdad” era clonar el repositorio de Cal.com, la alternativa open source a Calendly. Ese enlace ya no lleva donde llevaba.
En abril de 2026 Cal.com movió su código de producción a un repositorio privado. El repositorio público pasó a llamarse Cal.diy y es ahora la versión comunitaria, con licencia MIT completa y sin la parte comercial: fuera equipos, organizaciones, informes, workflows y SSO. Se mantiene el motor de agenda entero, la tienda de aplicaciones y los flujos de reserva. Según el propio anuncio del proyecto, todo lo que era gratuito sigue estando.
Lo que a ti te importa de ese cambio: el motor de agenda, que es justo lo que estás construyendo, sigue siendo público y ahora con una licencia más permisiva que antes.
Esa es la pantalla que estás replicando. Úsala como referencia visual y como referencia de comportamiento: fíjate en dónde coloca el selector de zona horaria, cómo se comporta la lista cuando un día no tiene huecos y qué pasa al cambiar de mes.
El proyecto va por la versión 6.2.0, tiene alrededor de 14.500 forks y cerca de mil personas han contribuido, según el registro de OpenSourceAlternatives verificado en agosto de 2026. Está construido con Next.js, tRPC, React, Tailwind y Prisma sobre PostgreSQL.

El propio README avisa de algo que conviene leer dos veces: la edición comunitaria está pensada para uso personal y no de producción, y montarla requiere saber de administración de servidores y bases de datos. No es un docker compose up y a otra cosa.
Si decides levantarlo en local para curiosear el código, el arranque pide Node 18 o superior, PostgreSQL 13 o superior y Yarn. La configuración empieza generando dos claves con openssl rand, una para las sesiones y otra para cifrar las credenciales de los calendarios conectados.

Ese detalle de la clave de cifrado tiene una consecuencia que merece la pena copiar en tu proyecto: si la pierdes, todas las credenciales guardadas se vuelven ilegibles y hay que reconectar cada calendario desde cero. Haz copia de esa clave el día que la generes, no el día que la necesites.
🛡️ Mirar el código de un proyecto maduro cuando te atascas no es hacer trampa. Es lo contrario de copiar el prompt gigante: tú ya has intentado resolver la pieza, tienes el problema en la cabeza y por eso entiendes por qué ellos lo resolvieron así.
La IA miente: compruébalo
Verificar lo que entrega el agente, con método y no a ojo
Verás cómo montar la verificación de lo que generan los agentes, del test que falla primero a la revisión con contexto limpio que destapa lo que el autor no ve.
Entrar a la masterclass →Masterclass en directo grabada · Web Reactiva Premium
Cómo se genera el checklist de “esto está terminado” ¶
Has llegado hasta aquí siguiendo el post. Tienes seis piezas construidas y, repartidos entre ellas, seis criterios de “está terminado cuando…”. El problema es que están sueltos, en seis conversaciones distintas que ya has cerrado.
Toca juntarlos en un solo sitio. Y hay una forma buena de hacerlo y una mala.
La mala es pedirle al agente “dime si el proyecto está terminado”. Te va a decir que sí. Siempre dice que sí.
Los tres tipos de comprobación ¶
Antes de generar nada, el reparto. Cualquier checklist útil de este proyecto tiene tres cajones, y confundirlos es lo que hace que la gente acabe con listas de treinta puntos que nadie repasa.
Lo que se comprueba solo. Los tests. El generador de huecos, los buffers, el cambio de hora, la concurrencia. Se ejecutan con un comando y devuelven verde o rojo. Aquí no hay opinión.
Lo que se comprueba a mano en diez minutos. Abrir la página en el móvil, importar un .ics en tres clientes, reenviar un email de confirmación a otra dirección y comprobar que el enlace de cancelar funciona. Son cosas que automatizar cuesta más de lo que valen en un proyecto personal.
Lo que solo se contesta con criterio. ¿Los eventos tentativos se comportan como tú decidiste? ¿El horizonte consultable es el que querías? Aquí el agente no puede ayudarte, porque son decisiones de producto y las tomaste tú.
El prompt que lo genera ¶
Con ese reparto claro, el prompt tiene sentido. Fíjate en que no le pido una lista: le pido un fichero verificable.
Recorre el repositorio y genera un fichero VERIFY.md con el checklist
de verificación de este proyecto antes de darlo por terminado.
Reglas para construirlo:
- Cada punto describe CÓMO se comprueba, no solo qué se comprueba.
"Los buffers funcionan" no vale. "Ejecutar el test X y ver que
pasa" sí vale.
- Agrupa los puntos en tres secciones: automático (un comando),
manual (menos de diez minutos) y decisión de producto (una
pregunta que solo puedo responder yo).
- Los puntos automáticos citan el fichero y el nombre del test que
ya existe. Si un criterio no tiene test que lo respalde, NO te lo
inventes: márcalo como hueco y ponlo en una lista aparte al final.
- Los puntos manuales incluyen el dato de prueba concreto: qué zona
horaria poner, qué cliente de calendario abrir, qué ancho de
pantalla usar.
- Las decisiones de producto van formuladas como pregunta cerrada,
con el valor que está implementado ahora mismo entre paréntesis.
Al final, una sección de huecos: criterios que aparecen en el código
o en los comentarios pero que nadie está comprobando.
No arregles nada. Solo el fichero.
Las dos líneas que hacen todo el trabajo son la del “cómo se comprueba” y la de “si no hay test, márcalo como hueco”. Sin la primera, te devuelve una lista de deseos. Sin la segunda, te inventa tests que no existen y los da por pasados.
Lo que debería devolverte ¶
Si el proyecto está donde debe, el fichero se parece a esto.
En el cajón automático: los tests del generador de huecos con los seis casos de zona horaria, el test de concurrencia con cien intentos simultáneos, y las migraciones aplicándose sobre una base de datos vacía sin intervención.
En el manual: la página a 375 píxeles, el .ics importado en Google, Apple y Outlook, un reagendado que actualiza el evento original en vez de duplicarlo, un email de confirmación reenviado a un tercero y el enlace de cancelar dejando de servir después.
Y en el de decisiones: si los eventos tentativos bloquean (ahora mismo, no), cuántas semanas de horizonte se pueden consultar (ahora mismo, ocho), y en qué calendario se escriben las citas nuevas cuando hay varios conectados.
Ese tercer cajón es el que da valor al ejercicio, porque son cosas que decidiste hace tres días y ya no recuerdas.
La regla que impide que el checklist mienta ¶
Un checklist se corrompe siempre por el mismo sitio: alguien marca una casilla sin comprobarla.
Con un agente de por medio pasa a los dos minutos. Le pides que repase el fichero y te devuelve todo marcado, porque marcar es lo más parecido a completar la tarea que le has pedido.
La regla es sencilla: cada punto se cierra con una evidencia pegada al lado. La salida del comando, el nombre del cliente donde importaste el .ics, la fecha en la que lo probaste. Sin evidencia, la casilla sigue vacía aunque el agente jure que funciona.
Repasa VERIFY.md y ejecuta solo los puntos de la sección automática.
Para cada uno, pega debajo la salida real del comando: la de verdad,
no un resumen tuyo.
Los puntos manuales y las decisiones de producto los dejas sin
tocar: esos los cierro yo.
Si un punto falla, no lo arregles. Anótalo y sigue con el siguiente.
Ese “no lo arregles, anótalo y sigue” evita el problema clásico: el agente se atasca arreglando el primer fallo, se le va el contexto y nunca llegas a saber si los otros diez puntos pasaban.
Las tres señales de que puedes cerrar el portátil ¶
Con el fichero cerrado, quedan tres preguntas que ningún checklist responde.
La primera: tú lo usas. Has cambiado el enlace de tu firma de correo y llevas dos semanas recibiendo reservas de verdad sin sobresaltos.
La segunda: la sección de huecos está vacía. No quedan criterios sueltos que aparezcan en el código y que nadie esté comprobando.
La tercera, y es la que más cuesta: la siguiente funcionalidad que se te ocurre está en la lista de exclusiones. Cuando te sorprendas pensando “sería fácil añadir equipos”, ese es el momento de cerrar el portátil.
Guarda el VERIFY.md en el repositorio y repásalo antes de cada despliegue. Es lo único de todo el proyecto que te va a seguir sirviendo dentro de seis meses, cuando vuelvas a tocarlo y no recuerdes ni por dónde empezaba.
Si en algún momento crece más allá de eso —porque le has cogido gusto o porque quieres cobrarlo— ahí sí toca cambiar de marcha y meter una metodología con especificación de por medio. Pero esa es otra conversación, y ese día ya no estarás haciendo vibe coding.
Preguntas frecuentes ¶
¿Cuánto se tarda en montar un Calendly propio con IA?
Una versión mínima con un tipo de reunión, disponibilidad semanal y reservas cabe en unas seis horas de trabajo real. Añadir sincronización con Google Calendar, zonas horarias bien resueltas y enlaces de reagendar se va a un par de días. Igualar el producto completo son meses, y es justo la parte que conviene no construir.
¿Cuál es la parte más difícil de construir un Calendly?
El motor de generación de huecos combinado con las zonas horarias. Es aritmética de intervalos —disponibilidad menos ocupación menos buffers, troceada en inicios— y el cambio de horario de verano rompe las reglas semanales de formas que no se ven hasta octubre.
¿Cómo se evita que dos personas reserven el mismo hueco?
Con tres capas independientes: una retención temporal del hueco mientras el invitado rellena el formulario, una comprobación final de disponibilidad dentro de la misma transacción que crea la reserva, y una restricción de unicidad en base de datos como última línea de defensa.
¿Por qué hay que guardar las fechas en UTC?
Porque un instante con desfase horario embebido cambia de significado en los cambios de hora y si el servidor se despliega en otra región. Guarda el instante en UTC, guarda aparte el nombre de la zona horaria de cada parte y convierte solo al mostrar.
¿Los eventos tentativos del calendario deben bloquear un hueco?
Es una decisión de producto, no técnica. Lo habitual es que una invitación aceptada bloquee y una pendiente no, pero conviene dejarlo como ajuste del anfitrión con un valor por defecto documentado, porque hay flujos de trabajo donde lo tentativo pesa igual que lo confirmado.
¿Qué le ha pasado al repositorio de Cal.com?
En abril de 2026 Cal.com movió su código de producción a un repositorio privado y renombró el repositorio público como Cal.diy. Esa edición comunitaria mantiene el motor de agenda completo con licencia MIT y deja fuera las funciones comerciales como equipos, organizaciones, workflows y SSO.
¿Puede alguien deducir mi agenda desde mi página de reservas?
Sí, por diferencia. Quien consulte tus huecos libres cada día puede reconstruir cuándo estás ocupado. Se mitiga limitando el horizonte consultable, poniendo un tope de ritmo en el endpoint público y redondeando los huecos a bloques.
¿Hace falta escribir specs formales para este proyecto?
Para un proyecto de fin de semana que reconstruyes para aprender, no. Basta con conocer de antemano las piezas difíciles y llegar a ellas con el prompt preparado. La metodología formal empieza a compensar cuando el proyecto va a durar o va a tocarlo más gente.
¿Cómo sé que mi Calendly propio está terminado?
Genera un fichero de verificación con tres secciones: comprobaciones automáticas que se ejecutan con un comando, comprobaciones manuales de menos de diez minutos y decisiones de producto que solo puedes responder tú. Cada punto describe cómo se comprueba y se cierra pegando la evidencia al lado, nunca marcando una casilla.
¿Merece la pena si ya existe una alternativa open source?
Depende del objetivo. Si necesitas una herramienta para producción, instala la que ya existe. Si lo que quieres es aprender, reconstruir un producto cuyo comportamiento ya conoces te convierte en el mejor tester posible de tu propio código.
Fuentes ¶
- Cal.diy, repositorio oficial en GitHub. Licencia MIT, requisitos de instalación, stack y avisos de uso.
- Cal.com, “Going Closed-Source: Technical Changes Behind Cal.diy”. Anuncio del paso a repositorio privado y qué funciones quedan fuera de la edición comunitaria.
- OpenSourceAlternatives, ficha de Cal.diy. Versión, forks, contribuidores e issues abiertas.
- PostgreSQL, “Exclusion Constraints”. Restricciones de exclusión con rangos temporales.
- Google, “Calendar API: OAuth 2.0 scopes”. Alcances de permisos para consultar ocupación y crear eventos.
- IETF, “RFC 5545: Internet Calendaring and Scheduling Core Object Specification”. Especificación del formato iCalendar, plegado de líneas y campos del VEVENT.
🧨 Última oprtunidad para recibir la dinamita que mereces sobre programación con IA el próximo domingo: Suscríbete gratis a Web Reactiva en https://webreactiva.com/newsletter
12 recursos para developers cada domingo en tu bandeja de entrada
Además de una skill práctica bien explicada, trucos para mejorar tu futuro profesional y una pizquita de humor útil para el resto de la semana. Gratis.