Skip to content

Flujo de Reservas

Jugador selecciona slot
┌─────────────────┐
│ PENDIENTE_PAGO │ ← Slot BLOQUEADO para otros jugadores
│ (TTL: 15 min) │
└────────┬────────┘
┌────┴────┐
│ │
▼ ▼
┌───────┐ ┌──────────┐
│Pago OK│ │Timeout / │
│ │ │Cancelar │
└───┬───┘ └─────┬─────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│CONFIRMADA│ │CANCELADA │ ← Slot LIBERADO
└────┬─────┘ └──────────┘
┌──────────┐
│COMPLETADA│ (fecha/hora ya pasó)
└──────────┘
  1. Cuando un jugador selecciona un slot para reservar, el sistema crea una reserva en estado PENDIENTE_PAGO.

  2. El slot queda BLOQUEADO — ningún otro jugador puede reservar ese mismo horario en esa cancha mientras exista una reserva PENDIENTE_PAGO activa.

  3. TTL de 15 minutos: si el pago no se confirma en 15 minutos, la reserva expira y el slot se libera automáticamente.

  4. Validación de pago: la reserva pasa a CONFIRMADA cuando:

    • El jugador completa el pago en MercadoPago (vía webhook), O
    • El dueño marca manualmente el pago como verificado desde su panel
  5. Reservas manuales del dueño: cuando el owner crea una reserva manual, pasa directamente a estado CONFIRMADA (no requiere pago).

Las reservas en estado PENDIENTE_PAGO tienen un tiempo máximo de vida de 15 minutos. Si el pago no se confirma en ese lapso, la reserva se cancela automáticamente y el slot se libera.

El TTL usa un enfoque de limpieza oportunista — sin cron jobs ni procesos externos:

  1. Cada vez que un usuario consulta disponibilidad (GET /api/disponibilidad)
  2. Cada vez que se crea una nueva reserva (POST /api/reservas)
  3. Manualmente vía POST /api/reservas/liberar-expiradas

En cualquiera de estos casos, el sistema barre las reservas PENDIENTE_PAGO con más de 15 minutos de antigüedad y las marca como CANCELADA.

Cuando una reserva expira:

  • Jugador: recibe notificación in-app + email informando que su reserva expiró
  • Dueño: recibe notificación de que el slot fue liberado

El sistema de notificaciones cubre todos los cambios de estado:

Evento Jugador Dueño
Reserva creada ✅ in-app + email
Pago confirmado ✅ in-app + email ✅ in-app + email
Reserva cancelada ✅ in-app + email ✅ in-app + email
Partido completado ✅ in-app + email
Reserva expirada (TTL) ✅ in-app + email ✅ in-app + email
  • In-app: campanita con badge + drawer de notificaciones (persistente, marca como leída)
  • Email: vía Resend (requiere RESEND_API_KEY en .env)

El sistema valida que no existan dos reservas activas (PENDIENTE_PAGO o CONFIRMADA) para la misma cancha con horarios solapados:

SELECT * FROM reservas
WHERE cancha_id = $1
AND estado IN ('PENDIENTE_PAGO', 'CONFIRMADA')
AND slot_inicio < $fin
AND slot_fin > $inicio

Si existe un conflicto, la API devuelve 409 Conflict.

  • Webhook de MercadoPago: actualización automática de estado al recibir pago
  • Recordatorio al jugador antes del partido