Saltar al contenido
Jasser Arrieta Villa
Volver a proyectos

Caso de estudio

Turnos

Aplicación web multi-rol donde los clientes reservan citas con profesionales.

Rol
Proyecto individual.
Estado
Terminado

Stack

  • Python 3.12
  • FastAPI
  • SQLAlchemy 2.0
  • Alembic
  • Pydantic v2
  • PostgreSQL 16
  • PyJWT
  • React
  • TypeScript
  • Vite
  • Tailwind CSS
  • TanStack Query
  • Docker Compose

Problema

Coordinar citas entre clientes y profesionales sin cruzar horarios: reglas de disponibilidad, confirmación, recordatorios y permisos distintos para cada tipo de usuario.

Solución

  • Reserva de citas a partir de la disponibilidad semanal del profesional y sus bloqueos por fechas.
  • Reprogramación de reservas y exportación a calendario en formato .ics.
  • Flujo de estados de la reserva (pendiente, confirmada, completada y cancelada) con correos transaccionales y recordatorios.
  • Panel de administración con métricas y reportes descargables en CSV, Excel y PDF.
  • Tema claro y oscuro, con diseño responsive.

Roles y flujos

Cliente
Se registra y verifica su correo, elige un horario libre y reserva; puede ver sus reservas, reprogramarlas, añadirlas a su calendario y cancelarlas con antelación.
Profesional
Gestiona su disponibilidad semanal y sus bloqueos, ve su agenda por día o semana y confirma, cancela o completa las reservas que le corresponden.
Admin
Administra servicios y usuarios, asigna servicios a los profesionales, ve todas las reservas y sus métricas, y descarga reportes por rango de fechas.

Arquitectura

  1. Navegador

    El cliente, el profesional o el admin.

  2. Web

    Vite y React, servida como frontend estático.

  3. API

    FastAPI, con autenticación por JWT y cookie de refresco.

  4. Base de datos

    PostgreSQL 16, con SQLAlchemy y Alembic.

  5. Correo

    Servicio SMTP para los correos transaccionales.

Decisiones técnicas

Anti doble reserva en la base de datos
Un constraint de exclusión de PostgreSQL impide solapar reservas pendientes o confirmadas del mismo profesional, incluso con peticiones simultáneas; la violación se traduce a una respuesta de horario no disponible.
Refresh tokens con rotación y detección de reutilización
El token de refresco vive en una cookie httpOnly y se rota en cada uso; si se reutiliza uno revocado, se revoca toda su familia. El token de acceso solo existe en memoria del frontend.
Generación de horarios como función pura
El cálculo de los espacios libres parte de reglas, bloqueos y reservas sin tocar la base de datos, lo que lo hace fácil de probar.
Correos fuera de la transacción
Las notificaciones se envían después de confirmar la reserva: un fallo de correo nunca revierte una reserva.
Autorización en dos capas
El rol se exige en los routers y la propiedad del recurso se comprueba en los servicios.

Retos y aprendizajes

Evitar dobles reservas
Se resolvió con un constraint de exclusión en PostgreSQL, de modo que la propia base de datos garantiza que no haya solapes aunque lleguen dos peticiones a la vez.
Reprogramar sin romper esa regla
El horario actual se libera dentro de la misma transacción para permitir corrimientos que se solapan con la propia reserva; si el nuevo horario no es válido, no se guarda nada.
Auditoría de seguridad
Se corrigieron hallazgos como el acaparamiento de horarios con reservas pendientes ilimitadas y la posibilidad de reservar sin verificar el correo; cada corrección quedó cubierta por pruebas de regresión.

Siguiente paso

  • Pasarela de pagos y depósitos al reservar.
  • Recordatorios por SMS o WhatsApp.
  • Sincronización bidireccional con Google Calendar.
  • Internacionalización y zona horaria por usuario.

Capturas

Asistente de reserva en tres pasos: servicio, profesional y día y hora.
Reservar una cita en tres pasos.
Agenda del profesional con vistas por día y semana.
Agenda del profesional.
Panel de estadísticas del administrador con reservas por día y servicios más reservados.
Panel de administración.
Lista de reservas del cliente con sus estados y acciones.
Mis reservas.

Enlaces