Coordinar una llamada por correo suele tomar 4 o 5 mensajes: "¿puedes el martes?", "mejor el jueves", "¿a qué hora?"... Para mi portfolio quería lo contrario: que quien tenga un proyecto elija día y hora reales y agende en 30 segundos. Así lo construí.
•
Horarios de verdad: franjas laborales (GMT-5), máximo 60 días a futuro
•
Bloqueo de slots: si alguien toma las 10:00 del martes, nadie más puede
•
Aviso inmediato: cada solicitud llega a mi correo con todo el contexto
•
Y una restricción que me impuse: el sitio público no puede hablar con la base de datos — es un export estático y quiero que siga así
El truco está en separar lecturas de escrituras:
Visitante → GET /api/booking?date=2026-07-10 → { taken: ["09:00","15:00"] }
Visitante → POST /api/booking { name, email, slot… }
├─ valida horario, fecha y formato (servidor manda)
├─ 409 si el slot ya fue tomado
├─ guarda en Firestore
└─ me envía email (Nodemailer + Gmail)
Una sola Cloud Function detrás de un rewrite de Hosting hace todo el trabajo. El frontend solo pinta el calendario y consulta qué horas están libres — las reglas de Firestore niegan todo acceso público; la función escribe con Admin SDK.
Tres decisiones que repetiría
1.
El servidor es la fuente de verdad. El cliente valida para UX, pero la función revalida todo: fechas, franjas, colisiones. Un curl malicioso recibe el mismo 409 que un doble click.
2.
Estático + funciones puntuales. El sitio carga rápido y no expone credenciales; Firebase solo aparece donde hay una operación real.
3.
El email como notificación, no como sistema. La verdad vive en Firestore (con su vista de administración); el correo es solo el timbre.
¿El resultado? Las solicitudes llegan completas: nombre, tipo de proyecto, presupuesto y la llamada ya agendada. Cero correos de coordinación.
🗓️ Puedes probarlo en vivo en contacto — y si quieres algo así en tu producto, ahí mismo agendamos una llamada (sí, con este mismo sistema 😄).