Reservas
Endpoints para crear y gestionar reservas de sesiones de clases.
Modelos de Datos
Section titled “Modelos de Datos”Los siguientes modelos de Prisma pertenecen al schema gym:
Reservation
Section titled “Reservation”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")}ReservationStatus (Enum)
Section titled “ReservationStatus (Enum)”enum ReservationStatus { CONFIRMED CANCELLED COMPLETED NO_SHOW
@@schema("gym")}Endpoints
Section titled “Endpoints”Obtener Reservas
Section titled “Obtener Reservas”Obtener las reservas del usuario actual o todas las reservas (Admin/Staff).
- URL:
/api/v1/reservations - Método:
GET - Autenticación Requerida: Sí
Descripción Interna
Section titled “Descripción Interna”Este endpoint obtiene reservas con filtrado:
-
Filtros disponibles:
userId: Filtrar por usuario (solo Admin/Staff pueden ver otros usuarios)classSessionId: Filtrar por sesión específicaclassId: Filtrar por clase (busca en las sesiones relacionadas)status: CONFIRMED, CANCELLED, COMPLETED, NO_SHOWdateFrom/dateTo: Rango de fechas de la sesión
-
Incluye:
- userProfile del reservante (id, name, avatar_url)
- classSession con información de la clase
-
Ordenado por fecha de creación descendente
-
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 StaffclassSessionId: Filtrar por sesión específicaclassId: Filtrar por clasepage: Número de página (por defecto: 1)limit: Resultados por página (por defecto: 10)
Respuesta
Section titled “Respuesta”{ "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 }}Ejemplo de Cliente Hono
Section titled “Ejemplo de Cliente Hono”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' }})Obtener Mis Reservas
Section titled “Obtener Mis Reservas”Obtener las reservas del usuario autenticado.
- URL:
/api/v1/reservations/me - Método:
GET - Autenticación Requerida: Sí
Descripción Interna
Section titled “Descripción Interna”Este endpoint obtiene todas las reservas del usuario actuál.
Ejemplo de Cliente Hono
Section titled “Ejemplo de Cliente Hono”import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.reservations.me.$get()Crear Reserva
Section titled “Crear Reserva”Crear una nueva reserva para una sesión de clase.
- URL:
/api/v1/reservations - Método:
POST - Autenticación Requerida: Sí
Descripción Interna
Section titled “Descripción Interna”Este endpoint crea una nueva reserva:
-
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
currentBookingsconmaxCapacityde la sesión (o de la clase) - Sin duplicados: Verifica que no exista reserva activa del usuario para esa sesión
-
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”
-
En una transacción:
- Crea reserva con userProfileId, classSessionId y status: CONFIRMED
- Incrementa
currentBookingsde la sesión
-
Retorna reserva con userProfile y classSession incluidos
Cuerpo de Solicitud
Section titled “Cuerpo de Solicitud”{ "classSessionId": "session_id_uuid", "notes": "Notas opcionales"}Respuesta (Éxito)
Section titled “Respuesta (Éxito)”{ "message": "Reserva creada exitosamente", "data": { "id": "...", "status": "CONFIRMED", "notes": null, "classSession": { "id": "...", "sessionDate": "2023-10-27", "startTime": "09:00", "class": {...} }, "userProfile": {...} }}Ejemplo de Cliente Hono
Section titled “Ejemplo de Cliente Hono”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 Reserva por ID
Section titled “Obtener Reserva por ID”Obtener detalles de una reserva específica.
- URL:
/api/v1/reservations/:id - Método:
GET - Autenticación Requerida: Sí
Descripción Interna
Section titled “Descripción Interna”Este endpoint obtiene una reserva específica:
- Busca reserva por ID
- Incluye userProfile y classSession con clase
- Lanza 404 si no existe
Nota: Usuarios normales solo pueden ver sus propias reservas. Admin/Staff pueden ver cualquier reserva.
Respuesta
Section titled “Respuesta”{ "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": "..." } }}Ejemplo de Cliente Hono
Section titled “Ejemplo de Cliente Hono”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 Reserva (Admin/Staff)
Section titled “Actualizar Reserva (Admin/Staff)”Actualizar el status de una reserva.
- URL:
/api/v1/reservations/:id - Método:
PATCH - Autenticación Requerida: Sí Admin Staff
Descripción Interna
Section titled “Descripción Interna”Este endpoint actualiza el estado de una reserva:
-
Obtiene reserva existente con información de la sesión y clase
-
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
-
Si la transición es inválida, lanza error 400 con mensaje explicativo
-
Si se cancela (CONFIRMED → CANCELLED):
- Decrementa
currentBookingsde la sesión
- Decrementa
-
Actualiza status y notes opcionales
-
Retorna reserva actualizada con userProfile y classSession
Cuerpo de Solicitud
Section titled “Cuerpo de Solicitud”{ "status": "COMPLETED", "notes": "Asistió puntualmente"}Ejemplo de Cliente Hono
Section titled “Ejemplo de Cliente Hono”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 Reserva
Section titled “Cancelar Reserva”Cancelar una reserva.
- URL:
/api/v1/reservations/:id - Método:
DELETE - Autenticación Requerida: Sí
Descripción Interna
Section titled “Descripción Interna”Este endpoint cancela una reserva:
-
Busca la reserva con información de estado y sesión
-
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”
-
Actualiza status a CANCELLED
-
Decrementa
currentBookingsde la sesión -
Retorna reserva cancelada
Ejemplo de Cliente Hono
Section titled “Ejemplo de Cliente Hono”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' }})Reactivar Reserva
Section titled “Reactivar Reserva”Reactivar una reserva cancelada.
- URL:
/api/v1/reservations/:id/reactivate - Método:
PATCH - Autenticación Requerida: Sí
Descripción Interna
Section titled “Descripción Interna”Este endpoint permite reactivar una reserva previamente cancelada, siempre y cuando sea del propio usuario.
Ejemplo de Cliente Hono
Section titled “Ejemplo de Cliente Hono”import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.reservations[':id'].reactivate.$patch({ param: { id: 'reservation_id' }})Eliminar Reserva Permanentemente
Section titled “Eliminar Reserva Permanentemente”Eliminar una reserva de forma permanente.
- URL:
/api/v1/reservations/:id/permanent - Método:
DELETE - Autenticación Requerida: Sí
Descripción Interna
Section titled “Descripción Interna”Este endpoint elimina una reserva de la base de datos permanentemente. Solo permitido para reservas propias.
Ejemplo de Cliente Hono
Section titled “Ejemplo de Cliente Hono”import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.reservations[':id'].permanent.$delete({ param: { id: 'reservation_id' }})Validar Reserva
Section titled “Validar Reserva”Verificar si se puede hacer una reserva sin crearla.
- URL:
/api/v1/reservations/validate - Método:
POST - Autenticación Requerida: Sí
Descripción Interna
Section titled “Descripción Interna”Este endpoint valida si se puede hacer una reserva sin crearla:
-
Verifica membresía activa: Busca membresía ACTIVE con fechas vigentes
-
Verifica sesión: Busca la sesión de clase y valida que esté activa (status SCHEDULED)
-
Verifica capacidad: Compara
currentBookingsvsmaxCapacityde la sesión -
Verifica duplicados: Busca reservas existentes no canceladas para esa sesión
-
Retorna objeto de validación:
canReserve: true si todas las validaciones pasanhasActiveMembership: booleansessionCapacityAvailable: booleanhasExistingReservation: boolean (true = ya tiene reserva)isSessionActive: booleanreason: Mensaje explicativo si no puede reservar
Cuerpo de Solicitud
Section titled “Cuerpo de Solicitud”{ "classSessionId": "class_session_id_uuid"}Respuesta
Section titled “Respuesta”{ "data": { "canReserve": true, "hasActiveMembership": true, "sessionCapacityAvailable": true, "hasExistingReservation": false, "isSessionActive": true, "reason": null }}Ejemplo de Cliente Hono
Section titled “Ejemplo de Cliente Hono”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' }})