Saltar al contenido
ERIK PORTILLO
Volver a los proyectos

Plataforma de Reserva de Pádel

TFG · demo publicada

El reto: impedir reservas dobles admitiendo duraciones variables (60/90 min). PostgreSQL garantiza la integridad relacional y la validación de solapamientos vive en el backend, acotada a cada pista y fecha. Autenticación JWT sin estado, con endpoints protegidos por rol.

Tipo
Proyecto final de grado (TFG). Funciona como demo desplegada; no está en producción.
Mi rol
Algoritmo de detección de conflictos, esquema PostgreSQL, autenticación JWT, middleware por roles y panel de administración.
Equipo
Desarrollado junto a Jose Jandula, con ramas por funcionalidad y revisión de cambios antes de cada merge.
Ver demo(se abre en una pestaña nueva)
Panel principal: Disponibilidad de pistas y próximas reservas.
Panel principal. Disponibilidad de pistas y próximas reservas · captura real

EL PROBLEMA

Una plataforma para digitalizar las reservas de pistas de pádel: centralizar la disponibilidad, evitar solapamientos y dar al club un panel de administración configurable.

El reto principal era garantizar que dos reservas nunca se superpongan con duraciones variables (60 y 90 minutos), acotando la comprobación a una pista y una fecha concretas. Eso exigía una lógica sólida en el backend y una interfaz clara para usuarios y administradores.

LA DECISIÓN CLAVE

Comprobar solo la hora exacta de inicio deja pasar solapamientos en cuanto las duraciones varían. La validación pasó al backend: antes de confirmar, se cargan las reservas de esa pista y ese día y se comprueba si el nuevo intervalo se cruza con alguno.

Las duraciones permitidas se leen de la configuración de cada pista, así que el administrador cambia las reglas sin tocar código.

const isAvailable = (courtId, startTime) => {
  // Solo verifica hora exacta, no solapamientos
  return !db.getReservation(courtId, startTime)
}
Antes: solo se comprobaba la hora exacta
async function validateConflict(courtId, date, startTime, duration) {
  const config = await getCourtConfig(courtId)
  if (!config.allowedDurations.includes(duration))
    throw new ValidationError('Duration not allowed')

  const dayReservations = await db.getReservationsByCourtAndDate(courtId, date)
  const endTime = addMinutes(startTime, duration)

  const hasConflict = dayReservations.some(r =>
    startTime < addMinutes(r.startTime, r.duration) && endTime > r.startTime
  )

  return { valid: !hasConflict, endTime }
}
Después: intervalo completo, acotado a pista y fecha

CÓMO LO CONSTRUIMOS

El orden real del trabajo, de los requisitos al control de versiones.

  1. Requisitos y arquitectura

    Usuarios con roles (usuario y administrador), reservas con duraciones configurables y validación de conflictos. Elegimos PostgreSQL para garantizar la integridad de los datos.

  2. Modelo de datos

    Tablas users, pistas, reservations y admin_config. Cada reserva guarda pista_id, start_time y duration para validar solapamientos de forma eficiente.

  3. Autenticación y roles

    JWT con middleware. Los usuarios reservan; los administradores configuran duraciones, ven el calendario y resuelven conflictos.

  4. Validación de conflictos

    La lógica central: el intervalo (start_time, start_time + duration) no puede cruzarse con ninguna reserva de la misma pista y día.

  5. Panel de administración

    Gestión de pistas, calendario completo, reglas de reserva y resolución manual de conflictos.

  6. Frontend

    Pantallas responsive, pensadas primero para móvil, para reservar, ver disponibilidad y confirmar. Todo validado por el backend antes de confirmar.

  7. Pruebas e iteración

    Casos de conflicto, duraciones límite y permisos por rol, iterando con lo que encontrábamos.

  8. Control de versiones

    Git con una rama por funcionalidad; revisábamos los cambios del otro antes de cada merge.

ARQUITECTURA

Frontend

  • Componentes en React
  • Diseño responsive pensado para móvil
  • Validación en cliente como primera capa
  • Confirmación de reservas

Backend

  • Node.js y Express
  • Autenticación JWT
  • Middleware por roles
  • Validación de conflictos
  • Endpoints REST

Base de datos

  • PostgreSQL (Supabase) para la integridad
  • Tablas users, pistas, reservations y config
  • Índices en pista_id + fecha
  • Restricciones para validar datos

Seguridad

  • Tokens JWT con expiración
  • Roles usuario y administrador
  • Validación obligatoria en backend
  • Comprobación de propiedad en endpoints

CAPTURAS

Nueva reserva: Selección de pista, duración y confirmación.
Nueva reserva. Selección de pista, duración y confirmación · captura real
Administración: Gestión de pistas, duraciones permitidas y reglas de reserva.
Administración. Gestión de pistas, duraciones permitidas y reglas de reserva · captura real
Inicio en móvil: Acceso directo a reservar.
Inicio en móvil. Acceso directo a reservar · captura real
Reserva en móvil: El mismo flujo, validado por el backend.
Reserva en móvil. El mismo flujo, validado por el backend · captura real

RESULTADO

La plataforma centraliza las reservas y rechaza los solapamientos antes de confirmarlos. Los usuarios reservan desde cualquier dispositivo y los administradores cambian las reglas sin tocar código.

Lo que más me enseñó fue la lógica de conflictos: razonar sobre intervalos de tiempo acotados a varias dimensiones (pista y fecha) y decidir qué validación vive en el servidor.

¿ENCAJO EN TU EQUIPO?