Skip to content

Reservas

Endpoints para crear y gestionar reservas de sesiones de clases.

Los siguientes modelos de Prisma pertenecen al schema gym:

Las reservaciones ahora se hacen sobre sesiones específicas (ClassSession), no sobre clases directamente.

model Reservation {
id String @id @default(cuid())
userProfileId String @map("user_profile_id")
classSessionId String @map("class_session_id")
status ReservationStatus @default(CONFIRMED)
notes String?
userProfile UserProfile @relation(fields: [userProfileId], references: [id], onDelete: Cascade)
classSession ClassSession @relation(fields: [classSessionId], references: [id], onDelete: Restrict)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([userProfileId, classSessionId])
@@map("reservations")
@@schema("gym")
}
enum ReservationStatus {
CONFIRMED
CANCELLED
COMPLETED
NO_SHOW
@@schema("gym")
}

Obtener las reservas del usuario actual o todas las reservas (Admin/Staff).

  • URL: /api/v1/reservations
  • Método: GET
  • Autenticación Requerida:

Este endpoint obtiene reservas con filtrado:

  1. Filtros disponibles:

    • userId: Filtrar por usuario (solo Admin/Staff pueden ver otros usuarios)
    • classSessionId: Filtrar por sesión específica
    • classId: Filtrar por clase (busca en las sesiones relacionadas)
    • status: CONFIRMED, CANCELLED, COMPLETED, NO_SHOW
    • dateFrom/dateTo: Rango de fechas de la sesión
  2. Incluye:

    • userProfile del reservante (id, name, avatar_url)
    • classSession con información de la clase
  3. Ordenado por fecha de creación descendente

  4. Paginación estándar con page/limit

Parámetros de Consulta de Solicitud (Opcionales)

Section titled “Parámetros de Consulta de Solicitud (Opcionales)”
  • status: Filtrar por status (CONFIRMED, CANCELLED, COMPLETED, NO_SHOW)
  • dateFrom: Filtrar desde fecha (de la sesión)
  • dateTo: Filtrar hasta fecha (de la sesión)
  • userId: Filtrar por usuario Admin Staff
  • classSessionId: Filtrar por sesión específica
  • classId: Filtrar por clase
  • page: Número de página (por defecto: 1)
  • limit: Resultados por página (por defecto: 10)
{
"data": [
{
"id": "...",
"status": "CONFIRMED",
"notes": null,
"classSession": {
"id": "...",
"sessionDate": "2023-10-27",
"startTime": "09:00",
"endTime": "10:00",
"class": {
"id": "...",
"name": "Yoga",
"description": "..."
}
},
"userProfile": {
"id": "...",
"name": "John Doe",
"avatar_url": "..."
}
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 5,
"totalPages": 1
}
}
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.reservations.$get({
query: {
status: 'CONFIRMED',
dateFrom: '2023-10-27',
dateTo: '2023-11-03'
}
})

Crear una nueva reserva para una sesión de clase.

  • URL: /api/v1/reservations
  • Método: POST
  • Autenticación Requerida:

Este endpoint crea una nueva reserva:

  1. Validación completa (validateReservation):

    • Membresía activa: Verifica que el usuario tenga membresía ACTIVE con fechas vigentes
    • Sesión válida: Verifica que la sesión existe y tiene status SCHEDULED o IN_PROGRESS
    • Fecha futura: Verifica que la sesión no haya pasado
    • Capacidad disponible: Compara currentBookings con maxCapacity de la sesión (o de la clase)
    • Sin duplicados: Verifica que no exista reserva activa del usuario para esa sesión
  2. Si validación falla, retorna error 400 con razón específica:

    • “No tienes una membresía activa”
    • “La sesión no existe”
    • “La sesión no está disponible”
    • “La sesión ya ha pasado”
    • “La sesión ha alcanzado su capacidad máxima”
    • “Ya tienes una reserva para esta sesión”
  3. En una transacción:

    • Crea reserva con userProfileId, classSessionId y status: CONFIRMED
    • Incrementa currentBookings de la sesión
  4. Retorna reserva con userProfile y classSession incluidos

{
"classSessionId": "session_id_uuid",
"notes": "Notas opcionales"
}
{
"message": "Reserva creada exitosamente",
"data": {
"id": "...",
"status": "CONFIRMED",
"notes": null,
"classSession": {
"id": "...",
"sessionDate": "2023-10-27",
"startTime": "09:00",
"class": {...}
},
"userProfile": {...}
}
}
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.reservations.$post({
json: {
classSessionId: 'session_id_uuid'
}
})

Obtener detalles de una reserva específica.

  • URL: /api/v1/reservations/:id
  • Método: GET
  • Autenticación Requerida:

Este endpoint obtiene una reserva específica:

  1. Busca reserva por ID
  2. Incluye userProfile y classSession con clase
  3. Lanza 404 si no existe

Nota: Usuarios normales solo pueden ver sus propias reservas. Admin/Staff pueden ver cualquier reserva.

{
"message": "Reserva obtenida exitosamente",
"data": {
"id": "...",
"status": "CONFIRMED",
"notes": null,
"classSession": {
"id": "...",
"sessionDate": "2023-10-27",
"startTime": "09:00",
"endTime": "10:00",
"class": {
"id": "...",
"name": "Yoga",
"description": "..."
}
},
"userProfile": {
"id": "...",
"name": "John Doe",
"avatar_url": "..."
}
}
}
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.reservations[':id'].$get({
param: { id: 'reservation_id' }
})

Actualizar el status de una reserva.

  • URL: /api/v1/reservations/:id
  • Método: PATCH
  • Autenticación Requerida:Admin Staff

Este endpoint actualiza el estado de una reserva:

  1. Obtiene reserva existente con información de la sesión y clase

  2. Valida transición de estado (isValidStatusTransition):

    • Desde CONFIRMED: puede ir a CANCELLED, COMPLETED, NO_SHOW
    • Desde CANCELLED: no puede cambiar (array vacío)
    • Desde COMPLETED: no puede cambiar (array vacío)
    • Desde NO_SHOW: solo puede ir a COMPLETED
  3. Si la transición es inválida, lanza error 400 con mensaje explicativo

  4. Si se cancela (CONFIRMED → CANCELLED):

    • Decrementa currentBookings de la sesión
  5. Actualiza status y notes opcionales

  6. Retorna reserva actualizada con userProfile y classSession

{
"status": "COMPLETED",
"notes": "Asistió puntualmente"
}
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.reservations[':id'].$patch({
param: { id: 'reservation_id' },
json: {
status: 'COMPLETED'
}
})

Cancelar una reserva.

  • URL: /api/v1/reservations/:id
  • Método: DELETE
  • Autenticación Requerida:

Este endpoint cancela una reserva:

  1. Busca la reserva con información de estado y sesión

  2. Validaciones:

    • No puede cancelar si ya está CANCELLED: “La reserva ya está cancelada”
    • No puede cancelar si está COMPLETED: “No se puede cancelar una reserva completada”
    • No puede cancelar si la fecha de la sesión < ahora: “No se puede cancelar una reserva que ya ha pasado”
  3. Actualiza status a CANCELLED

  4. Decrementa currentBookings de la sesión

  5. Retorna reserva cancelada

import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.reservations[':id'].$delete({
param: { id: 'reservation_id' }
})

Verificar si se puede hacer una reserva sin crearla.

  • URL: /api/v1/reservations/validate
  • Método: POST
  • Autenticación Requerida:

Este endpoint valida si se puede hacer una reserva sin crearla:

  1. Verifica membresía activa: Busca membresía ACTIVE con fechas vigentes

  2. Verifica sesión: Busca la sesión de clase y valida que esté activa (status SCHEDULED)

  3. Verifica capacidad: Compara currentBookings vs maxCapacity de la sesión

  4. Verifica duplicados: Busca reservas existentes no canceladas para esa sesión

  5. Retorna objeto de validación:

    • canReserve: true si todas las validaciones pasan
    • hasActiveMembership: boolean
    • sessionCapacityAvailable: boolean
    • hasExistingReservation: boolean (true = ya tiene reserva)
    • isSessionActive: boolean
    • reason: Mensaje explicativo si no puede reservar
{
"classSessionId": "class_session_id_uuid"
}
{
"data": {
"canReserve": true,
"hasActiveMembership": true,
"sessionCapacityAvailable": true,
"hasExistingReservation": false,
"isSessionActive": true,
"reason": null
}
}
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.reservations.validate.$post({
json: {
classSessionId: 'class_session_id_uuid'
}
})