Saltar al contenido
Erik Portillo
Volver a Trabajo

Proyecto 02: Reservas de pistas de pádel

TFG · DEMO PUBLICADA

Reservas de pistas con duraciones de 60 y 90 minutos sin solapes, reglas del club configurables y cuentas con roles. Proyecto final de DAW desarrollado con Jose Jandula.

Tipo
Proyecto final de grado (DAW)
Año
2026
Equipo
Erik Portillo y Jose Jandula
Estado
Demo publicada; no está en producción

React 19 · Vite · React Router · Node.js · Express 5 · PostgreSQL (Supabase) · JWT · Nodemailer

El problema

Un club necesita que nadie reserve una pista ya ocupada, aunque las reservas no duren lo mismo. Comprobar solo la hora de inicio no basta: una reserva de 90 minutos a las 10:00 también ocupa las 11:00.

Arquitectura

Frontend en React con Vite, desplegado en Vercel. Backend en Node.js con Express, desplegado en Railway y organizado en rutas, controladores, servicios y middlewares; solo la capa de servicios accede a la base de datos.

PostgreSQL en Supabase, que también guarda las imágenes de las pistas. El correo (verificación de cuentas y recuperación de contraseña) sale por SMTP con Nodemailer.

Cómo se valida una reserva

Antes de guardar, el servidor comprueba, en este orden:

  1. 1

    Formato

    Fecha y hora válidas; solo se empieza en punto o y media.

  2. 2

    Calendario del club

    Que el día no esté bloqueado, que el club abra ese día de la semana y que la hora esté dentro de su horario.

  3. 3

    Tiempo

    Que no sea una hora pasada y que se respete la antelación mínima (la administración puede saltársela al reservar por un cliente).

  4. 4

    Duración

    Que la duración esté permitida y que la reserva termine antes del cierre.

  5. 5

    Pista

    Que la pista exista y esté activa.

Pantalla de reserva: calendario, duración de 60 o 90 minutos y huecos libres por franja
6SolapeQue el intervalo no se cruce con ninguna reserva confirmada de esa pista y ese día. Si choca, responde 409 con la franja exacta que se solapa.

Decisiones técnicas

La regla de solape, en el servidor

Dos intervalos se cruzan si cada uno empieza antes de que termine el otro. La comprobación se hace contra las reservas de esa pista y ese día, que se cargan antes de insertar.

function haySolape(inicioNueva, finNueva, inicioExistente, finExistente) {
  const nuevaInicio = horaAMinutos(inicioNueva);
  const nuevaFin = horaAMinutos(finNueva);
  const existenteInicio = horaAMinutos(inicioExistente);
  const existenteFin = horaAMinutos(finExistente);
  return nuevaInicio < existenteFin && nuevaFin > existenteInicio;
}
backend/src/utils/timeHelpers.js

La base de datos como última defensa

Un índice único sobre pista, fecha y hora de inicio impide dos reservas que empiecen a la vez. Si dos peticiones simultáneas pasan la comprobación, la segunda falla en la base de datos y el servidor lo traduce a un 409 en lugar de un error genérico.

Como el índice incluye las reservas canceladas, una franja cancelada se reutiliza actualizando su fila en vez de insertar otra.

if (error) {
  // 23505 = condición de carrera: dos peticiones simultáneas pasaron el check
  if (error.code === '23505') {
    return { ok: false, status: 409, error: 'COURT_NOT_AVAILABLE',
             message: 'Esa pista ya está reservada en ese horario' };
  }
}
backend/src/services/reservations.service.js (fragmento).

Las reglas del club, en configuración

Horario semanal por día, días bloqueados, duraciones permitidas y antelación mínima se leen de la configuración y se cambian desde el panel, sin tocar código.

Cuentas y acceso

Registro con verificación de email obligatoria, sesiones con JWT de dos horas y recuperación de contraseña con tokens de un solo uso.

Tres roles (usuario, recepción y administración) aplicados con middlewares, y limitación de peticiones: 100 cada 15 minutos en general, 5 en autenticación y 10 en las rutas sensibles.

Límites conocidos

  • La base de datos solo garantiza que no haya dos reservas con la misma hora de inicio. Dos peticiones simultáneas que se solapen con horas de inicio distintas dependen de la comprobación del servidor.
  • El proyecto no tiene tests automatizados.