Skip to content

Entrenadores

Endpoints para encontrar entrenadores, verificar horarios, gestionar sesiones de entrenamiento y reseñas.

Los siguientes modelos de Prisma pertenecen al schema gym:

model Trainer {
id String @id @default(cuid())
userProfileId String @unique @map("user_profile_id")
specializations String[]
certifications String[]
experience Int
hourlyRate Float @map("hourly_rate")
bio String?
isActive Boolean @default(true) @map("is_active")
userProfile UserProfile @relation(fields: [userProfileId], references: [id], onDelete: Cascade)
classTrainers Class[] @relation("ClassTrainers")
trainingSessions TrainingSession[]
reviews TrainerReview[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("trainers")
@@schema("gym")
}
model TrainingSession {
id String @id @default(cuid())
userProfileId String @map("user_profile_id")
trainerId String @map("trainer_id")
scheduledDate DateTime @map("scheduled_date")
duration Int
price Float
notes String?
status ReservationStatus @default(CONFIRMED)
userProfile UserProfile @relation(fields: [userProfileId], references: [id], onDelete: Cascade)
trainer Trainer @relation(fields: [trainerId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("training_sessions")
@@schema("gym")
}

El modelo TrainerReview pertenece al schema reviews:

model TrainerReview {
id String @id @default(cuid())
userProfileId String @map("user_profile_id")
trainerId String @map("trainer_id")
rating Int @db.SmallInt
comment String?
isVisible Boolean @default(true) @map("is_visible")
isVerified Boolean @default(false) @map("is_verified")
userProfile UserProfile @relation(fields: [userProfileId], references: [id], onDelete: Cascade)
trainer Trainer @relation(fields: [trainerId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([userProfileId, trainerId])
@@map("trainer_reviews")
@@schema("reviews")
}

Obtener una lista de entrenadores con filtros.

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

Este endpoint obtiene entrenadores con filtrado avanzado:

  1. Filtros disponibles:

    • specialization: Filtra por especialización (usando hasSome en array)
    • minExperience: Años mínimos de experiencia
    • maxHourlyRate: Tarifa máxima por hora
    • isActive: Solo entrenadores activos
    • search: Búsqueda en nombre, especializaciones y bio
  2. Incluye:

    • userProfile con información del usuario
    • Contadores de trainingSessions y classTrainers
  3. Ordenamiento: isActive desc → experience desc → name asc

  • specialization: Filtrar por especialización
  • search: Buscar por nombre, especialización, bio
  • minExperience: Años mínimos de experiencia
  • maxHourlyRate: Tarifa máxima por hora
  • isActive: Filtrar entrenadores activos
  • page: Número de página
  • limit: Resultados por página
{
"data": [
{
"id": "...",
"specializations": ["Yoga", "Pilates"],
"bio": "Expert instructor",
"experience": 5,
"hourlyRate": 50,
"isActive": true,
"userProfile": {...},
"_count": {
"trainingSessions": 120,
"classTrainers": 3
}
}
],
"pagination": {...}
}
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.trainers.$get({
query: {
specialization: 'Yoga'
}
})

Obtener detalles de un entrenador específico.

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

Este endpoint obtiene un entrenador específico:

  1. Busca por ID usando findUniqueOrThrow
  2. Incluye:
    • userProfile completo
    • classTrainers (clases que imparte)
    • Contadores de sesiones y clases
{
"data": {
"id": "...",
"specializations": ["Yoga"],
"bio": "Expert yoga instructor",
"experience": 5,
"hourlyRate": 50,
"certifications": ["RYT-200"],
"userProfile": {...},
"classTrainers": [...],
"_count": {...}
}
}
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.trainers[':id'].$get({
param: { id: 'trainer_id' }
})

Obtener el horario de un entrenador para un rango de fechas.

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

Este endpoint obtiene el horario completo de un entrenador:

  1. Obtiene sesiones de entrenamiento personal:

    • Filtradas por trainerId y rango de fechas
    • Solo CONFIRMED y COMPLETED
    • Incluye userProfile del cliente
  2. Obtiene clases grupales:

    • Busca ClassSchedules donde el trainer esté asignado
    • Solo clases activas y schedules activos
    • Incluye información de la clase
  3. Retorna ambos tipos para visualización del horario

  • dateFrom: Fecha de inicio (YYYY-MM-DD) - requerido
  • dateTo: Fecha de fin (YYYY-MM-DD) - requerido
{
"data": {
"trainingSessions": [
{
"id": "...",
"scheduledDate": "2023-10-27T10:00:00Z",
"duration": 60,
"status": "CONFIRMED",
"userProfile": {...}
}
],
"classSchedules": [
{
"id": "...",
"dayOfWeek": 1,
"startTime": "09:00",
"endTime": "10:00",
"class": {
"name": "Yoga",
"type": "YOGA"
}
}
],
"period": {
"from": "2023-10-01",
"to": "2023-10-31"
}
}
}
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.trainers[':id'].schedule.$get({
param: { id: 'trainer_id' },
query: {
dateFrom: '2023-10-01',
dateTo: '2023-10-31'
}
})

Encontrar entrenadores disponibles en un horario específico.

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

Este endpoint busca entrenadores disponibles:

  1. Valida formato de hora (HH:MM)

  2. Calcula ventana de tiempo:

    • startTime: fecha + hora especificada
    • endTime: startTime + duración
  3. Busca entrenadores donde:

    • isActive: true
    • No tengan trainingSessions CONFIRMED que se solapen
  4. Incluye userProfile de cada entrenador disponible

  • date: Fecha (YYYY-MM-DD)
  • startTime: Hora de inicio (HH:MM)
  • duration: Duración en minutos
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.trainers.available.$get({
query: {
date: '2023-10-27',
startTime: '10:00',
duration: '60'
}
})

Registrar un nuevo entrenador.

  • URL: /api/v1/trainers
  • Método: POST
  • Autenticación Requerida: Sí (Admin/Staff)

Este endpoint crea un nuevo entrenador:

  1. Verifica que el usuario existe y no es ya entrenador

  2. Crea en transacción:

    • Registro Trainer con datos proporcionados
    • Actualiza UserProfile.role a TRAINER
  3. Retorna entrenador con userProfile y user

  • userProfileId: ID del perfil de usuario (requerido)
  • specializations: Array de especializaciones
  • bio: Biografía/descripción
  • experience: Años de experiencia
  • hourlyRate: Tarifa por hora
  • certifications: Array de certificaciones
{
"userProfileId": "user_profile_id",
"specializations": ["Yoga", "Pilates"],
"bio": "Expert yoga instructor with 5 years experience",
"experience": 5,
"hourlyRate": 50,
"certifications": ["RYT-200"]
}
  • 400 Bad Request: El usuario ya es un entrenador
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.trainers.$post({
json: {
userProfileId: 'user_profile_id',
specializations: ['Yoga'],
bio: 'Expert yoga instructor',
hourlyRate: 50
}
})

Reservar una sesión de entrenamiento con un entrenador.

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

Este endpoint crea una sesión de entrenamiento:

  1. Verifica que el entrenador existe y está activo

  2. Verifica conflictos de horario:

    • Busca sesiones existentes del usuario o entrenador
    • Calcula solapamiento con la nueva sesión
    • Si hay conflicto con CONFIRMED, lanza error
  3. Calcula precio: (hourlyRate / 60) × duration

  4. Crea la sesión con status inicial (según lógica de negocio)

TODO: Enviar notificación al entrenador y usuario

{
"trainerId": "trainer_id",
"scheduledDate": "2023-10-27T10:00:00Z",
"duration": 60,
"type": "PERSONAL",
"notes": "Focus on flexibility"
}
{
"data": {
"id": "...",
"scheduledDate": "2023-10-27T10:00:00Z",
"duration": 60,
"price": 50,
"status": "CONFIRMED",
"trainer": {...},
"userProfile": {...}
}
}
  • 400 Bad Request: Ya existe una sesión confirmada en ese horario
  • 404 Not Found: Entrenador inactivo
import { hcWithType } from '@vitality-gym/api/client'
const client = hcWithType('http://localhost:3000')
const res = await client.api.v1.trainers.sessions.$post({
json: {
trainerId: 'trainer_id',
scheduledDate: '2023-10-27T10:00:00Z',
duration: 60
}
})

Obtener sesiones de entrenamiento con filtros.

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

Este endpoint obtiene sesiones de entrenamiento:

  1. Filtros disponibles:

    • trainerId: Por entrenador
    • userProfileId: Por usuario
    • status: Estado de la sesión
    • dateFrom/dateTo: Rango de fechas
  2. Incluye:

    • trainer con userProfile
    • userProfile del cliente
  3. Ordenado por scheduledDate descendente


Cancelar una sesión de entrenamiento.

  • URL: /api/v1/trainers/sessions/:id/cancel
  • Método: POST
  • Autenticación Requerida:

Este endpoint cancela una sesión:

  1. Verifica permisos:

    • El usuario debe ser el cliente o el entrenador
    • isTrainer flag indica si es el entrenador cancelando
  2. Valida estado:

    • No puede cancelar COMPLETED o CANCELLED
  3. Actualiza status a CANCELLED

TODO: Enviar notificación de cancelación


Obtener estadísticas para un entrenador.

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

Este endpoint calcula estadísticas del entrenador:

  1. Calcula fecha inicio según período (week, month, quarter, year)

  2. Ejecuta consultas en transacción:

    • Total de sesiones
    • Sesiones completadas
    • Sesiones canceladas
    • Ingresos totales (suma de prices de COMPLETED)
    • Clientes únicos
  3. Calcula tasas:

    • completionRate: (completadas / total) × 100
    • cancellationRate: (canceladas / total) × 100
  • period: Período de tiempo (week, month, quarter, year)

Enviar una reseña para un entrenador.

  • URL: /api/v1/trainers/:id/reviews
  • Método: POST
  • Autenticación Requerida:

Este endpoint crea una valoración:

  1. Validaciones:

    • Entrenador existe y está activo
    • No puede valorarse a sí mismo
    • No debe tener review existente para este entrenador
  2. Verifica sesión completada:

    • Busca si el usuario tuvo sesión COMPLETED con el trainer
    • Si la tuvo: isVerified: true
  3. Crea la review con rating, comment y flags

TODO: Notificar al entrenador sobre nueva valoración

{
"rating": 5,
"comment": "Excellent trainer!"
}
  • 400 Bad Request: Ya has valorado a este entrenador
  • 400 Bad Request: No puedes valorarte a ti mismo

Obtener reseñas de un entrenador.

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

Este endpoint obtiene valoraciones de un entrenador:

  1. Filtro base: Solo reviews con isVisible: true

  2. Filtros opcionales:

    • rating: Filtrar por rating específico
    • hasComment: Solo reviews con comentario
    • hasTrainerResponse: Solo reviews con respuesta del trainer
    • sortBy: newest, oldest, highest, lowest
  3. Incluye userProfile y trainer con userProfile


Obtener Estadísticas de Valoración del Entrenador

Section titled “Obtener Estadísticas de Valoración del Entrenador”

Obtener estadísticas de valoraciones de un entrenador.

  • URL: /api/v1/trainers/:id/rating-stats
  • Método: GET
  • Autenticación Requerida:

Este endpoint calcula estadísticas de ratings:

  1. Agrupa reviews por rating (1-5)
  2. Calcula:
    • Distribución de ratings (count por cada valor)
    • Total de reviews
    • Rating promedio ponderado
{
"data": {
"averageRating": 4.5,
"totalReviews": 50,
"ratingDistribution": [
{ "rating": 5, "count": 30 },
{ "rating": 4, "count": 15 },
{ "rating": 3, "count": 5 }
]
}
}