Referencia de API

Table of Contents

Este documento describe la API REST de UnoSportClub implementada en functions/server.js con Express.js.

Base URL

Producción (API Público)

La API está disponible en producción en los siguientes subdominios:

Desarrollo Local

Para desarrollo local, la API está disponible en:

O usando el proxy de Angular (configurado en proxy.conf.json):

/api

Nota: * El servidor backend en producción se ejecuta con PM2 y está balanceado por Caddy. * La documentación interactiva de la API (Swagger UI) está disponible en: - Producción: https://app.unosportclub.com.co/api/api-docs - QA: https://panel.qa.unosportclub.com.co/api/api-docs - Desarrollo: http://localhost:6100/api/api-docs * El esquema OpenAPI (JSON) está disponible en: - Producción: https://app.unosportclub.com.co/api/api-docs.json - QA: https://panel.qa.unosportclub.com.co/api/api-docs.json - Desarrollo: http://localhost:6100/api/api-docs.json * Los endpoints con localhost son exclusivamente para desarrollo local. * Los endpoints con subdominios de unosportclub.com.co son el API público en producción. * Esta documentación está basada en el esquema OpenAPI oficial (versión 3.0.0) y las interfaces TypeScript definidas en @cortex-ia-com-co/common. * Recomendación: Para la documentación más actualizada y completa, consulta el esquema OpenAPI JSON directamente o usa Swagger UI para explorar la API de forma interactiva.

Autenticación

La API soporta dos métodos de autenticación:

Autenticación con OAuth2 local IdP (Usuarios)

Para usuarios autenticados mediante OAuth2 local IdP, el token debe enviarse en el header:

Authorization: Bearer [firebase-id-token]

Este método se usa para usuarios humanos que se autentican mediante OAuth2 local authentication.

Autenticación con API Key (Bots)

Para bots y sistemas automatizados, se puede usar autenticación mediante API Key enviando el valor de BOT_API_KEYS en el header:

X-Auth: [BOT_API_KEYS]

Características de la autenticación con API Key: * El header debe ser X-Auth (no Authorization) * El valor debe coincidir exactamente con la variable de entorno BOT_API_KEYS configurada en el servidor * Otorga permisos completos: operator, admin, sudo, trainer, y bot * Disponible en las rutas: /account/, /admin/, /trainer/, y /sudo/ * El usuario autenticado tendrá uid: "bot-api-key" y bot: true

Ejemplo de uso:

curl -H "X-Auth: tu-api-key-aqui" https://app.unosportclub.com.co/api/admin/clients

Nota: Algunos endpoints públicos (/public/*) no requieren autenticación.

OAuth2 authorization_code (MCP / Cursor)

Flujo PKCE para clientes públicos (sin client_secret). El login usa la pantalla del panel.

| Método | Ruta | Descripción | |--------|------|-------------| | GET | /authorize | Inicia autorización; redirige a {UNOSPORT_PANEL_URL}/login?oauth_state=…​ | | POST | /oauth/authorize/complete | Tras login panel; body { "oauth_state": "uuid" }; requiere Bearer usuario con permiso panel | | POST | /oauth/token | grant_type=authorization_code con code, code_verifier, client_id | | GET | /.well-known/oauth-protected-resource/mcp | Metadatos RFC 9728 del recurso MCP |

Variables servidor: MCP_OAUTH_CLIENT_ID, MCP_OAUTH_REDIRECT_URIS, UNOSPORT_PANEL_URL.

MCP Streamable HTTP

| Método | Ruta | Auth | Descripción | |--------|------|------|-------------| | POST | /mcp | Bearer usuario (scope mcp, permiso panel) | JSON-RPC (p. ej. initialize, tools/list, tools/call); header Accept: application/json, text/event-stream | | GET | /mcp | Bearer usuario | SSE; header mcp-session-id | | DELETE | /mcp | Bearer usuario | Cierra sesión MCP |

URL local de desarrollo: http://localhost:5000/mcp (no usar el proxy del panel en /api/mcp).

Requisitos:

  • Token OAuth2 de usuario con scope mcp y permiso panel (ver OAuth2 authorization_code (MCP / Cursor)).

  • Tras initialize, enviar notificación notifications/initialized antes de tools/list o tools/call.

  • Las respuestas pueden llegar como JSON o como SSE (event: message + data: {…​}); los clientes deben parsear ambos formatos.

Rechaza tokens M2M (sub con prefijo client:). Las máquinas sudo (client_credentials vía /sudo/machines) no aplican a MCP.

Tools disponibles

Las tools devuelven texto JSON en result.content[0].text. Los errores de negocio incluyen { error, message, code } sin lanzar excepción HTTP.

Tool Parámetros Descripción

client_search

search, limit, offset, sortBy, sortOrder (opcionales)

Busca clientes (GET /admin/clients).

client_read

id (requerido)

Detalle de cliente con contratos (GET /admin/clients/:id).

client_create

phone, first_name, last_name, document, document_type_id; email, address (opcionales)

Alta de cliente (POST /admin/clients).

court_list

search (opcional)

Lista canchas con tipo (GET /admin/courts).

court_type_list

search (opcional)

Catálogo de tipos de cancha (GET /admin/court-types).

reservation_type_list

search, gestion (opcionales)

Catálogo de tipos de reserva (GET /admin/reservation-types).

reservation_status_list

search (opcional)

Catálogo de estados de reserva (GET /admin/reservation-statuses).

payment_type_list

search (opcional)

Catálogo de tipos de pago (GET /admin/payment-types).

booking_list

client_id, court_id, start_date, end_date, limit, offset (opcionales)

Lista reservas con filtros (GET /admin/booking).

booking_validate

court_id, checking, checkout; reservation_id (opcional)

Valida disponibilidad de slot (POST /admin/booking/validate).

booking_calculate_price

court_id, date, checking, checkout

Calcula precio antes de crear (GET /admin/booking/calculate-price).

booking_get_price

id (requerido)

Precio de reserva existente con cupón/convenio (GET /admin/booking/:id/price).

booking_create

court_id, checking, checkout; reservation_type_id, client_id, reservation_status_id, notes (opcionales)

Crea reserva pendiente (POST /admin/booking). Usa operator_id del usuario MCP.

booking_read

id (requerido)

Detalle de reserva con cliente, cancha y pagos (GET /admin/booking/:id).

booking_update

id + campos opcionales (client_id, court_id, reservation_type_id, reservation_status_id, checking, checkout, notes)

Actualiza reserva (PATCH /admin/booking/:id). Asignar cliente o confirmar con reservation_status_id de estado Confirmada.

booking_payment_list

id (requerido)

Pagos de una reserva (GET /admin/booking/:id/payment).

booking_add_payment

id, amount, transaction_id; payment_type_id, status, description, gateway_response (opcionales)

Registra pago (POST /admin/booking/:id/payment). Sin carga de foto vía MCP.

Flujo recomendado (operador panel — 4 pasos)

  1. Disponibilidad: court_list, reservation_type_listbooking_validatebooking_calculate_pricebooking_create con court_id, checking, checkout (ISO 8601, hora mural Bogotá).

  2. Cliente: client_search → si no existe client_createbooking_update con client_id.

  3. Pago: payment_type_listbooking_add_payment con amount y transaction_idbooking_payment_list para verificar.

  4. Confirmación: booking_read + booking_get_pricereservation_status_list (obtener id Confirmada) → booking_update con reservation_status_id cuando el total pagado cubre el precio.

Horarios en hora mural Bogotá (ver Fechas y Horas (hora mural)). No enviar checking_date, checking_time, checkout_date, checkout_time; usar checking y checkout en ISO 8601.

Ejemplo (tools/call)

POST /mcp
Authorization: Bearer <access_token>
Accept: application/json, text/event-stream
Mcp-Session-Id: <session_id>

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "booking_validate",
    "arguments": {
      "court_id": 1,
      "checking": "2026-05-27T18:00:00",
      "checkout": "2026-05-27T19:00:00"
    }
  }
}

Formatos de Datos

Fechas y Horas (hora mural)

La convención principal para reservas y eventos del panel es la hora mural de América/Bogotá, no UTC.

Almacenamiento en base de datos

  • Columnas reservation.checking y reservation.checkout: TIMESTAMP WITHOUT TIME ZONE (sin offset; valor mural).

  • Entrada en POST/PUT: ISO 8601 o YYYY-MM-DD HH:mm:ss normalizado por normalizeTimestampForDb en el backend.

Respuesta API (reservas y eventos admin)

Priorizar campos murales devueltos con TO_CHAR:

  • checking_date, checking_time, checkout_date, checkout_time (YYYY-MM-DD y HH24:MI:SS)

  • checking, checkout opcionales como YYYY-MM-DD HH24:MI:SS sin sufijo Z

Advertencia UI: no interpretar cadenas con Z como UTC al mostrar horarios de cancha. Usar reservation-wall-time en @cortex-ia-com-co/common.

Otros contextos

  • Solo fecha: YYYY-MM-DD (filtros, tarifas).

  • Solo hora: HH:MM o HH:MM:SS (cotización de precio).

  • Metadatos del sistema (created_at, tokens OAuth): pueden seguir ISO 8601 con zona según el origen.

Ver xref:data-model.adoc#_2_8_reservation y xref:events-tournaments-module.adoc.

Estructura de Endpoints

La API está organizada por roles y funcionalidades:

  • /account/*: Endpoints para usuarios autenticados (clientes)

  • /admin/*: Endpoints para administradores y operadores

  • /trainer/*: Endpoints para entrenadores

  • /public/*: Endpoints públicos (sin autenticación)

  • /sudo/*: Endpoints para super administradores

Endpoints por Rol

Account (Usuario Autenticado)

Los endpoints bajo /account/* permiten a los usuarios autenticados gestionar sus propios recursos.

Reservas (Account)

GET /reservations

Obtiene la lista de reservas.

Query Parameters: * client_id (opcional): Filtrar por cliente * operator_id (opcional): Filtrar por operador * court_id (opcional): Filtrar por cancha * status (opcional): Filtrar por estado * from (opcional): Fecha desde (ISO 8601) * to (opcional): Fecha hasta (ISO 8601)

Response 200:

{
  "data": [
    {
      "id": 1,
      "operator_id": 1,
      "client_id": 1,
      "court_id": 1,
      "reservation_type_id": 1,
      "checking": "2024-01-15T10:00:00Z",
      "checkout": "2024-01-15T12:00:00Z",
      "notes": "Cancha 1",
      "created_at": "2024-01-10T08:00:00Z",
      "updated_at": "2024-01-10T08:00:00Z"
    }
  ],
  "total": 1
}

GET /reservations/:id

Obtiene una reserva específica.

Response 200:

{
  "data": {
    "id": 1,
    "operator_id": 1,
    "client_id": 1,
    "court_id": 1,
    "reservation_type_id": 1,
    "checking": "2024-01-15T10:00:00Z",
    "checkout": "2024-01-15T12:00:00Z",
    "notes": "Cancha 1",
    "created_at": "2024-01-10T08:00:00Z",
    "updated_at": "2024-01-10T08:00:00Z"
  }
}

POST /reservations

Crea una nueva reserva.

Request Body:

{
  "operator_id": 1,
  "client_id": 1,
  "court_id": 1,
  "reservation_type_id": 1,
  "checking": "2024-01-15T10:00:00Z",
  "checkout": "2024-01-15T12:00:00Z",
  "notes": "Cancha 1"
}

Response 201:

{
  "data": {
    "id": 1,
    "operator_id": 1,
    "client_id": 1,
    "court_id": 1,
    "reservation_type_id": 1,
    "checking": "2024-01-15T10:00:00Z",
    "checkout": "2024-01-15T12:00:00Z",
    "notes": "Cancha 1",
    "created_at": "2024-01-10T08:00:00Z",
    "updated_at": "2024-01-10T08:00:00Z"
  }
}

PUT /reservations/:id

Actualiza una reserva existente.

Request Body:

{
  "checking": "2024-01-15T10:30:00Z",
  "checkout": "2024-01-15T12:30:00Z",
  "notes": "Cancha 1 - Actualizado"
}

Response 200:

{
  "data": {
    "id": 1,
    "operator_id": 1,
    "client_id": 1,
    "court_id": 1,
    "reservation_type_id": 1,
    "checking": "2024-01-15T10:30:00Z",
    "checkout": "2024-01-15T12:30:00Z",
    "notes": "Cancha 1 - Actualizado",
    "created_at": "2024-01-10T08:00:00Z",
    "updated_at": "2024-01-10T09:00:00Z"
  }
}

DELETE /reservations/:id

Elimina una reserva.

Response 204: No Content

Nota importante: * Estos endpoints solo devuelven reservas del usuario autenticado * Las reservas incluyen información agregada de pagos (total_paid, total_amount, is_payment_complete, can_add_payment) * Los pagos dentro de las reservas incluyen tanto status (string) como statusBoolean (boolean) para compatibilidad * Las reservas incluyen campos checking y checkout para mostrar fecha y hora de inicio y fin * Los horarios están disponibles en slots de 30 minutos * Las interfaces TypeScript correspondientes están en AccountBookingInterface y AccountBookingPaymentInterface del paquete @cortex-ia-com-co/common

GET /account/bookings

Obtiene las reservas del usuario autenticado con información detallada de pagos y paginación.

Query Parameters: * page (opcional, default: 1): Número de página * limit (opcional, default: 10): Cantidad de resultados por página

Response 200:

{
  "bookings": [
    {
      "id": 1,
      "client_id": 1,
      "court_id": 1,
      "reservation_type_id": 1,
      "checking": "2024-01-15T10:00:00Z",
      "checkout": "2024-01-15T12:00:00Z",
      "notes": "Cancha 1",
      "created_at": "2024-01-10T08:00:00Z",
      "updated_at": "2024-01-10T08:00:00Z",
      "court_name": "Cancha 1",
      "reservation_type_name": "Libre",
      "total_paid": 50000.00,
      "total_amount": 50000.00,
      "is_payment_complete": true,
      "can_add_payment": false,
      "payments": [
        {
          "id": 1,
          "reservation_id": 1,
          "payment_type_id": 1,
          "payment_type_name": "Efectivo",
          "amount": 50000.00,
          "transaction_id": "TXN-123456",
          "gateway_response": null,
          "status": "completed",
          "statusBoolean": true,
          "date": "2024-01-10T08:00:00Z",
          "description": "Pago de reserva"
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 15,
    "totalPages": 2
  }
}

Campos de información de pagos: * total_paid: Suma total de pagos completados asociados a la reserva * total_amount: Monto total requerido para la reserva * is_payment_complete: Indica si el pago está completo (total_paid >= total_amount) * can_add_payment: Indica si se pueden agregar más pagos a la reserva * payments: Array de pagos asociados con información detallada

Formato dual de status en pagos: * status: String con valores "pending" o "completed" (para compatibilidad con frontend) * statusBoolean: Boolean equivalente (false = pending, true = completed)

Response 401:

{
  "error": "No autenticado"
}

POST /account/bookings

Crea una nueva reserva para el cliente autenticado.

Request Body:

{
  "court_id": 1,
  "reservation_type_id": 1,
  "checking": "2024-01-15T10:00:00Z",
  "checkout": "2024-01-15T12:00:00Z",
  "notes": "Cancha 1"
}

Campos requeridos: * court_id: ID de la cancha (integer) * reservation_type_id: ID del tipo de reserva (integer) * checking: Fecha y hora de inicio (ISO 8601) * checkout: Fecha y hora de fin (ISO 8601)

Campos opcionales: * notes: Notas adicionales (string, nullable)

Response 201:

{
  "id": 1,
  "client_id": 1,
  "court_id": 1,
  "reservation_type_id": 1,
  "checking": "2024-01-15T10:00:00Z",
  "checkout": "2024-01-15T12:00:00Z",
  "notes": "Cancha 1",
  "created_at": "2024-01-10T08:00:00Z",
  "updated_at": "2024-01-10T08:00:00Z"
}

Response 400: Datos incompletos, fecha inválida o referencia inválida Response 401: No autenticado Response 404: Cliente no encontrado Response 409: Conflicto de reserva (cancha ocupada)

GET /account/bookings/calculate-price

Calcula el precio de una reserva con ajustes de tarifas usando función PostgreSQL.

Query Parameters: * date (requerido): Fecha de la reserva (YYYY-MM-DD) * checking (requerido): Hora de inicio (HH:MM) * checkout (requerido): Hora de fin (HH:MM) * court_id (requerido): ID de la cancha * reservation_type_id (opcional): ID del tipo de reserva * reservation_id (opcional): ID de la reserva (para edición)

Response 200:

{
  "court": {
    "id": 1,
    "name": "Cancha 1",
    "base_price": 50000.00
  },
  "date": "2024-01-15",
  "checking": "10:00",
  "checkout": "12:00",
  "available": true,
  "hours": 2.0,
  "final_price": 50000.00,
  "price_per_hour": 25000.00,
  "applied_adjustments": [
    {
      "tariff_id": 1,
      "description": "Descuento nocturno",
      "adjustment": "-10%",
      "adjusted_price": 45000.00
    }
  ]
}

Campos de respuesta: * court: Información de la cancha (id, name, base_price) * date: Fecha de la reserva (YYYY-MM-DD) * checking: Hora de inicio (HH:MM) * checkout: Hora de fin (HH:MM) * available: Indica si la cancha está disponible en ese horario * hours: Número de horas de la reserva (decimal) * final_price: Precio final calculado después de aplicar tarifas * price_per_hour: Precio por hora calculado * applied_adjustments: Array de ajustes de tarifa aplicados con información detallada

Nota: La función calcula el precio por periodos de 30 minutos aplicando tarifas según rangos de fechas y horas. La interfaz TypeScript correspondiente es CalculatePriceResponseInterface del paquete @cortex-ia-com-co/common.

GET /account/bookings/{bookingId}

Obtiene una reserva específica del usuario autenticado.

Response 200: Similar a GET /account/bookings pero con un solo objeto en lugar de array.

Response 400: ID de reserva inválido Response 401: No autenticado Response 404: Reserva no encontrada o no pertenece al usuario

GET /account/courts

Obtiene canchas disponibles con verificación de disponibilidad.

Query Parameters: * checking (opcional): Fecha y hora de inicio para verificar disponibilidad (ISO 8601) * checkout (opcional): Fecha y hora de fin para verificar disponibilidad (ISO 8601)

Response 200: Array de canchas con información de disponibilidad.

GET /account/reservation-types

Obtiene todos los tipos de reserva disponibles.

Response 200: Array de tipos de reserva (ReservationTypeInterface).

GET /account/payment-types

Obtiene todos los tipos de pago disponibles para el cliente.

Response 200: Array de tipos de pago (PaymentTypeInterface).

GET /account/stats

Obtiene estadísticas del usuario autenticado.

Response 200: Objeto con estadísticas del usuario (reservas, pagos, etc.). Response 401: No autenticado Response 403: Acceso denegado

Perfil (Account)

GET /account/profile

Obtiene el perfil del usuario autenticado.

Response 200: Perfil del usuario obtenido exitosamente (OauthUserProfileInterface). Response 401: No autenticado Response 403: Acceso denegado

PUT /account/profile

Actualiza el perfil del usuario autenticado.

Request Body:

{
  "displayName": "Juan Pérez",
  "phone": "+573001234567"
}

Campos opcionales: * displayName: Nombre de visualización (string) * phone: Teléfono de contacto (string)

Response 200: Perfil actualizado exitosamente. Response 401: No autenticado Response 403: Acceso denegado

Cliente (Account)

GET /account/client

Obtiene los datos del cliente del usuario autenticado.

Response 200: Objeto ClientInterface del cliente autenticado.

Response 401: No autenticado Response 404: Cliente no encontrado

PATCH /account/client

Actualiza los datos del cliente del usuario autenticado.

Request Body:

{
  "firstName": "Juan",
  "lastName": "Pérez",
  "document": "1234567890",
  "documentTypeId": 1,
  "address": "Calle 123",
  "phone": "+573001234567"
}

Response 200: Cliente actualizado exitosamente.

POST /account/client

Crea un nuevo cliente para el usuario autenticado.

Request Body:

{
  "email": "cliente@example.com",
  "first_name": "Juan",
  "last_name": "Pérez",
  "document": "1234567890",
  "document_type_id": 1,
  "phone": "+573001234567",
  "address": "Calle 123"
}

Campos requeridos: * email: Email del cliente * first_name: Nombre del cliente * last_name: Apellido del cliente * document: Número de documento * document_type_id: ID del tipo de documento

Response 201: Cliente creado exitosamente. Response 400: Datos incompletos o usuario no encontrado Response 409: Cliente ya existe para este usuario

Clases (Account)

GET /account/classes

Obtiene lista de clases disponibles para evento.

Query Parameters: * reservation_type_id (opcional): Filtrar por tipo de reserva. Siempre filtra por gestion=2.

Response 200: Lista de clases disponibles (filtradas por gestion=2).

Nota: Las clases disponibles son aquellas con reservation_type.gestion = 2 (clases de entrenamiento).

GET /account/classes/my-enrollments

Obtiene las inscripciones del cliente actual.

Response 200: Lista de inscripciones del cliente (EventInterface[]).

GET /account/classes/{id}

Obtiene detalles de una clase específica.

Response 200: Detalles de la clase (TrainingClassInterface). Response 404: Clase no encontrada

POST /account/classes/{id}/enroll

Inscribirse en una clase.

Request Body:

{
  "payments": [
    {
      "paymentTypeId": 1,
      "amount": 50000.00,
      "transaction_id": "TXN-123456"
    }
  ]
}

Response 201: Evento exitosa. Response 400: Error de validación Response 404: Clase no encontrada Response 409: Ya está inscrito en esta clase

Admin (Administradores y Operadores)

Los endpoints bajo /admin/* permiten la gestión completa del sistema para administradores y operadores.

Tickets (Panel)

Módulo de paquetes de boletos (catálogo, emisión e historial). Sin caja/till en v1.

Catálogo inicial: migración 083_package_strip.sql crea tablas; el seeder estructural 010_tickets.js inserta el programa y 12 paquetes (unit_price). En panel, la matriz de precios se edita en Tarifas → Tarifas de Paquetes (/tariffs/package); los cambios se persisten con PUT /admin/tickets/packages (no se sobrescriben al re-ejecutar el seeder).

  • GET /admin/tickets/programs — programas con catálogo de paquetes

  • PUT /admin/tickets/packages — actualizar unit_price de paquetes ({ packages: [{ id, unit_price }] })

  • POST /admin/tickets/quote — cotización (package_id, ticket_count)

  • POST /admin/tickets/sales — emitir paquete (comprador, teléfonos, pago)

  • GET /admin/tickets/sales — historial (headers X-Total-Count, X-Limit, X-Offset)

  • GET /admin/tickets/sales/{id} — detalle con tirillas y boletos

Reservas (Admin)

Los endpoints de reservas permiten la gestión completa (CRUD) de reservas desde el panel de administración.

Nota importante: * Las reservas pueden tener un client_id opcional (null para reservas de mantenimiento) * Las reservas incluyen información de estado (reservation_status_id) y tipo (reservation_type_id) * Los campos checking y checkout son fechas y horas en formato ISO 8601 * La interfaz TypeScript correspondiente es BookingInterface y BookingDetailInterface del paquete @cortex-ia-com-co/common

GET /admin/booking

Obtiene todas las reservas con filtros opcionales.

Query Parameters: * client_id (opcional): Filtrar por ID de cliente * court_id (opcional): Filtrar por ID de cancha * limit (opcional, default: 100): Límite de resultados * offset (opcional, default: 0): Offset para paginación

Response 200: Array de reservas (BookingInterface[]).

GET /admin/booking/full

Obtiene reservas con información completa de pagos y precios.

Query Parameters: * client_id (opcional): Filtrar por ID de cliente * court_id (opcional): Filtrar por ID de cancha * start_date (opcional): Filtrar por fecha de inicio (YYYY-MM-DD) * end_date (opcional): Filtrar por fecha de fin (YYYY-MM-DD) * active (opcional): Filtrar solo reservas activas (checkout > ahora) * confirmed (opcional): Filtrar por estado confirmado (true = confirmadas, false = pendientes) * limit (opcional, default: 1000): Límite de resultados * offset (opcional, default: 0): Offset para paginación

Response 200: Array de reservas con información completa incluyendo total_amount, total_paid, total_debt.

POST /admin/booking

Crea una nueva reserva.

Request Body:

{
  "client_id": 1,
  "court_id": 1,
  "reservation_type_id": 1,
  "reservation_status_id": 1,
  "checking": "2024-01-15T18:00:00Z",
  "checkout": "2024-01-15T19:00:00Z",
  "notes": "Notas adicionales"
}

Campos requeridos: * court_id: ID de la cancha (integer) * reservation_type_id: ID del tipo de reserva (integer) * checking: Fecha y hora de inicio (ISO 8601) * checkout: Fecha y hora de fin (ISO 8601)

Campos opcionales: * client_id: ID del cliente (integer, nullable, opcional para reservas de mantenimiento) * reservation_status_id: ID del estado de reserva (integer, nullable) * notes: Notas adicionales (string, nullable)

Response 201: Reserva creada exitosamente (BookingInterface). Response 400: Datos inválidos, incompletos o referencia inválida Response 401: No autenticado Response 409: Conflicto de reserva (cancha ocupada)

GET /admin/booking/{id}

Obtiene una reserva por ID con detalles completos.

Response 200: Reserva obtenida exitosamente con detalles completos (BookingDetailInterface). Response 404: Reserva no encontrada

Nota: BookingDetailInterface incluye información adicional del cliente (client_first_name, client_last_name, client_email, client_phone, client_document, client_address) y de la cancha (court_base_price, court_type_name).

PATCH /admin/booking/{id}

Edita una reserva por ID.

Request Body:

{
  "client_id": 1,
  "court_id": 1,
  "reservation_type_id": 1,
  "reservation_status_id": 1,
  "checking": "2024-01-15T18:00:00Z",
  "checkout": "2024-01-15T19:00:00Z",
  "notes": "Notas actualizadas"
}

Response 200: Reserva actualizada exitosamente (BookingInterface). Response 400: Datos inválidos Response 401: No autenticado Response 404: Reserva no encontrada

DELETE /admin/booking/{id}

Elimina una reserva por ID.

Response 204: Reserva eliminada exitosamente. Response 400: ID de reserva inválido Response 401: No autenticado Response 404: Reserva no encontrada

DELETE /admin/booking

Elimina varias reservas.

Request Body:

{
  "ids": ["1", "2", "3"]
}

Response 200: Reservas eliminadas exitosamente.

GET /admin/booking/calculate-price

Calcula el precio de una reserva con ajustes de tarifas.

Query Parameters: * date (requerido): Fecha de la reserva (YYYY-MM-DD) * checking (requerido): Hora de inicio (HH:MM) * checkout (requerido): Hora de fin (HH:MM) * court_id (requerido): ID de la cancha * reservation_type_id (opcional): ID del tipo de reserva

Response 200:

{
  "court": {
    "id": 1,
    "name": "Cancha 1",
    "base_price": 50000.00
  },
  "checking_date": "2024-01-15",
  "checking_time": "18:00:00",
  "checkout_date": "2024-01-15",
  "checkout_time": "19:00:00",
  "available": true,
  "hours": 1.0,
  "final_price": 50000.00,
  "price_per_hour": 50000.00,
  "applied_adjustments": []
}

Campos de respuesta: * court: Información de la cancha (id, name, base_price) * checking_date: Fecha de inicio (YYYY-MM-DD) * checking_time: Hora de inicio (HH:MM:SS) * checkout_date: Fecha de fin (YYYY-MM-DD) * checkout_time: Hora de fin (HH:MM:SS) * available: Indica si la cancha está disponible * hours: Número de horas de la reserva (decimal) * final_price: Precio final calculado después de aplicar tarifas * price_per_hour: Precio por hora calculado * applied_adjustments: Array de ajustes de tarifa aplicados

Response 400: Parámetros inválidos Response 404: Cancha no encontrada Response 500: Error interno del servidor

GET /admin/booking/{id}/price

Calcula el precio de una reserva existente por su ID.

Response 200: Ficha detallada con precio calculado. Estructura similar a GET /admin/booking/calculate-price con campos checking_date, checking_time, checkout_date, checkout_time. Response 404: Reserva no encontrada Response 500: Error interno del servidor

POST /admin/booking/validate

Valida fecha y disponibilidad de una reserva.

Request Body:

{
  "court_id": 1,
  "checking": "2024-01-15T18:00:00Z",
  "checkout": "2024-01-15T19:00:00Z",
  "reservation_id": null
}

Campos requeridos: * court_id: ID de la cancha (integer) * checking: Fecha y hora de inicio (ISO 8601) * checkout: Fecha y hora de fin (ISO 8601)

Campos opcionales: * reservation_id: ID de reserva existente (integer, nullable, excluir en validación)

Response 204: Validación exitosa (sin contenido). Response 400: Datos inválidos Response 409: Conflicto - fecha inválida o cancha no disponible

GET /admin/booking/coupons

Listado solo lectura de cupones aplicados a reservas (auditoría de booking_coupon).

Query Parameters: * page (opcional, default: 1): Número de página (base 1) * limit (opcional, default: 50, máx. 200): Registros por página

Response 200: Array en el cuerpo (BookingCouponAppliedInterface[]). Metadatos en cabeceras:

  • X-Total-Count: total de registros

  • X-Page: índice de página (base 0)

  • X-Per-Page: tamaño de página

[
  {
    "id": 1,
    "reservation_id": 82,
    "coupon_id": 5,
    "coupon_code": "PROMO10",
    "coupon_type": "generic",
    "applied": 2500.5,
    "created_at": "2026-01-15T12:00:00.000Z"
  }
]

Interfaz TypeScript: BookingCouponAppliedInterface, BookingCouponListResponseInterface (@cortex-ia-com-co/common).

POST /admin/booking/{id}/coupon

Aplica un cupón a una reserva (exclusivo con convenio). Ver OpenAPI (POST /admin/booking/{id}/coupon).

DELETE /admin/booking/{id}/coupon

Quita el cupón aplicado de una reserva. Devuelve la ficha de precio actualizada.

PATCH /admin/booking/{id}/contract

Asigna o quita el convenio de una reserva (exclusivo con cupón). Ver OpenAPI.

Pagos de Reservas (Admin)

Los endpoints de pagos permiten la gestión completa (CRUD) de pagos desde el panel de administración.

Nota importante: * El campo status es un boolean: false = pendiente, true = completado * Los pagos deben estar asociados a una reserva (reservation_id) o a un evento (enrollment_id) al crearse; la vinculación ocurre en el flujo de reserva/inscripción * El campo payment_type_id es opcional y puede ser null * La interfaz TypeScript correspondiente es PaymentInterface del paquete @cortex-ia-com-co/common * Campo foto (string | null): referencia al objeto en S3/R2; no usar como URL pública en el cliente

Comprobantes de pago (captura en reserva)

Almacenamiento privado S3/R2 gestionado por PaymentReceiptStorage (functions/services/payment-receipt-storage.js). El campo payment.foto guarda la referencia interna; el cliente nunca debe abrir esa URL directamente.

Operación Endpoint

Subir al crear pago

POST /admin/booking/{bookingId}/payment (JSON o multipart)

Subir o reemplazar

PATCH /admin/booking/{bookingId}/payment/{paymentId} (JSON o multipart)

Descargar (proxy)

GET /admin/booking/{bookingId}/payment/{paymentId}/foto

Eliminar comprobante (bucket + BD)

DELETE /admin/booking/{bookingId}/payment/{paymentId}/foto

Eliminar pago (y comprobante si existe)

DELETE /admin/booking/{bookingId}/payment/{paymentId}

Panel: en /booking/payment/:id, botón verde Subir (sin comprobante), Ver (modal) y Eliminar comprobante (rojo en el modal). Integraciones usan los mismos endpoints con multipart/form-data.

Reglas multipart:

  • Campo foto obligatorio cuando Content-Type es multipart/form-data

  • Tipos: image/jpeg, image/png, image/webp; máximo 5 MB

  • Clave en bucket: payments/booking-{bookingId}/{hash}.{ext}

  • Errores: 413 (tamaño), 422 (sin foto o MIME inválido), 503 (S3 no configurado)

Ejemplo curl (POST con comprobante):

curl -X POST "http://localhost:6000/api/admin/booking/82/payment" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -b "XSRF-TOKEN=$CSRF" \
  -F "amount=1000" \
  -F "transaction_id=34375884" \
  -F "payment_type_id=26" \
  -F "status=false" \
  -F "foto=@/ruta/comprobante.jpg;type=image/jpeg"

Ejemplo curl (eliminar comprobante):

curl -X DELETE "http://localhost:6000/api/admin/booking/82/payment/116/foto" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -b "XSRF-TOKEN=$CSRF"

Ejemplo curl (eliminar pago):

curl -X DELETE "http://localhost:6000/api/admin/booking/82/payment/116" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -b "XSRF-TOKEN=$CSRF"

GET /admin/payments

Obtiene la lista de pagos con filtros y paginación.

Query Parameters: * search (opcional): Búsqueda por transaction_id o description (búsqueda ILIKE) * status (opcional): Filtrar por estado del pago (boolean: false = pendiente, true = completado) * reservation_id (opcional): Filtrar por ID de reservación * limit (opcional, default: 100): Límite de resultados * offset (opcional, default: 0): Offset para paginación

Nota: Los pagos pueden estar asociados a reservas (reservation_id) o eventos (enrollment_id).

Ordenamiento: Los resultados se ordenan primero por estado (pendientes primero) y luego por fecha descendente.

Response 200:

[
  {
    "id": 1,
    "reservation_id": 1,
    "enrollment_id": null,
    "payment_type_id": 1,
    "amount": 50000.00,
    "transaction_id": "TXN-123456",
    "gateway_response": "{\"status\":\"completed\"}",
    "status": true,
    "date": "2024-01-10T08:00:00Z",
    "description": "Pago de reserva"
  },
  {
    "id": 2,
    "reservation_id": 2,
    "enrollment_id": null,
    "payment_type_id": 1,
    "amount": 30000.00,
    "transaction_id": "TXN-789012",
    "gateway_response": null,
    "status": false,
    "date": "2024-01-11T10:00:00Z",
    "description": "Pago pendiente de validación"
  }
]

GET /admin/payments/stats

Obtiene estadísticas agregadas de pagos.

Response 200:

{
  "total": 150,
  "pending": 10,
  "completed": 130,
  "total_amount": 7500000.00,
  "completed_amount": 6500000.00,
  "by_status": {
    "completed": 130,
    "pending": 10,
    "failed": 0,
    "refunded": 0
  }
}

Campos de respuesta: * total: Total de pagos en el sistema * pending: Cantidad de pagos pendientes (status = false) * completed: Cantidad de pagos completados (status = true) * total_amount: Suma total de todos los pagos * completed_amount: Suma total de pagos completados * by_status: Desglose por estado (nota: failed y refunded no están implementados actualmente)

GET /admin/payments/:id

Obtiene un pago específico por ID.

Response 200:

{
  "id": 1,
  "reservation_id": 1,
  "payment_type_id": 1,
  "amount": 50000.00,
  "transaction_id": "TXN-123456",
  "gateway_response": "{\"status\":\"completed\"}",
  "status": true,
  "date": "2024-01-10T08:00:00Z",
  "description": "Pago de reserva"
}

Response 404:

{
  "error": "Pago no encontrado"
}

GET /admin/payments/transaction/:transaction_id

Obtiene un pago por transaction_id del gateway de pago.

Response 200:

  {
    "id": 1,
    "reservation_id": 1,
    "enrollment_id": null,
    "payment_type_id": 1,
    "amount": 50000.00,
    "transaction_id": "TXN-123456",
    "gateway_response": "{\"status\":\"completed\"}",
    "status": true,
    "date": "2024-01-10T08:00:00Z",
    "description": "Pago de reserva"
  }

Response 404:

{
  "error": "Pago no encontrado"
}

GET /admin/booking/{bookingId}/payment

Lista todos los pagos de una reserva.

Response 200: Array de pagos de la reserva (PaymentInterface[]; cada elemento incluye foto). Response 404: Reserva no encontrada Response 500: Error interno del servidor

POST /admin/booking/{bookingId}/payment

Crea un nuevo pago para una reserva.

Content-Type: application/json o multipart/form-data.

Request Body (JSON):

{
  "payment_type_id": 1,
  "amount": 50000.00,
  "transaction_id": "TXN-123456",
  "status": false,
  "description": "Pago de reserva",
  "gateway_response": null
}

Request Body (multipart):

Campos de formulario equivalentes a JSON más el archivo:

  • foto (file, requerido en multipart): comprobante JPEG, PNG o WebP; máximo 5 MB.

La API sube el archivo a S3/R2 (payments/booking-{id}/{hash}.{ext}) y guarda la URL resultante en payment.foto. Sin archivo (JSON), foto queda null.

Campos requeridos: * amount: Monto del pago (number, decimal) * transaction_id: ID de transacción del gateway de pago (string)

Campos opcionales: * payment_type_id: ID del tipo de pago (integer, nullable) * status: Estado del pago (boolean, default: false) * description: Descripción del pago (string, nullable) * gateway_response: Respuesta completa del gateway de pago (string, nullable)

Response 201: Pago creado exitosamente (PaymentInterface, incluye foto si se subió archivo). Response 400: Datos de pago inválidos (JSON) Response 413: Archivo de comprobante supera 5 MB Response 422: Multipart sin foto, tipo MIME no permitido u otros datos inválidos Response 503: Almacenamiento S3/R2 no configurado Response 404: Reserva no encontrada Response 500: Error interno del servidor

Nota: El reservation_id se toma automáticamente del parámetro de ruta bookingId. Diagrama: Subida de comprobante.

GET /admin/booking/{bookingId}/payment/{paymentId}

Obtiene un pago específico de una reserva.

Response 200: Pago obtenido exitosamente (PaymentInterface, incluye foto). Response 404: Reserva o pago no encontrado Response 500: Error interno del servidor

PATCH /admin/booking/{bookingId}/payment/{paymentId}

Actualiza un pago de una reserva.

Request Body (JSON):

{
  "payment_type_id": 1,
  "amount": 50000.00,
  "transaction_id": "TXN-123456",
  "status": true,
  "description": "Pago actualizado",
  "gateway_response": "{\"status\":\"completed\"}"
}

Request Body (multipart):

  • foto (file, requerido en multipart): comprobante JPEG, PNG o WebP; máximo 5 MB. Añade o reemplaza payment.foto.

  • Otros campos del pago opcionales en la misma petición (mismos nombres que JSON).

Response 200: Pago actualizado exitosamente (PaymentInterface, incluye foto si se subió archivo). Response 400: Datos inválidos Response 413: Archivo de comprobante supera 5 MB Response 422: Multipart sin foto, tipo MIME no permitido o sin campos para actualizar (JSON) Response 503: Almacenamiento S3/R2 no configurado Response 404: Reserva o pago no encontrado Response 500: Error interno del servidor

GET /admin/booking/{bookingId}/payment/{paymentId}/foto

Descarga el comprobante de pago asociado al registro. Requiere autenticación (Bearer) y token CSRF (X-XSRF-TOKEN, cookie XSRF-TOKEN). El bucket S3/R2 no es público; este endpoint actúa como proxy autorizado.

Response 200: Cuerpo binario de la imagen (image/jpeg, image/png o image/webp). Cabeceras Cache-Control: private, no-store. Response 404: Reserva, pago o comprobante no encontrado Response 503: Almacenamiento S3/R2 no configurado Response 500: Error al leer el objeto o error interno

DELETE /admin/booking/{bookingId}/payment/{paymentId}

Elimina un pago de una reserva. Si payment.foto tiene valor, también elimina el comprobante en S3/R2 antes de borrar el registro.

Response 200: Pago eliminado exitosamente. Response 404: Reserva o pago no encontrado Response 422: ID de reserva o pago inválido Response 503: Almacenamiento S3/R2 no configurado (cuando el pago tiene comprobante y storage no está disponible) Response 500: Error interno del servidor

DELETE /admin/booking/{bookingId}/payment/{paymentId}/foto

Elimina el objeto en el bucket S3/R2 (DeleteObject) y pone payment.foto = null. Si el objeto ya no existe en storage, la operación sigue y limpia la referencia en BD.

Response 204: Comprobante eliminado (sin cuerpo). Response 404: Reserva, pago o comprobante no encontrado Response 503: Almacenamiento S3/R2 no configurado Response 500: Error interno del servidor

POST /admin/payments

Crea un nuevo pago. Útil para registrar pagos manuales o sincronizar pagos desde sistemas externos.

Request Body:

{
  "reservation_id": 1,
  "enrollment_id": null,
  "payment_type_id": 1,
  "amount": 50000.00,
  "transaction_id": "TXN-123456",
  "status": true,
  "description": "Pago de reserva",
  "gateway_response": "{\"status\":\"completed\"}"
}

Campos requeridos: * amount: Monto del pago (number) * transaction_id: ID de transacción del gateway (string) * status: Estado del pago (boolean)

Campos opcionales: * reservation_id: ID de la reservación asociada (integer, nullable, mutuamente exclusivo con enrollment_id) * enrollment_id: ID de la evento asociada (integer, nullable, mutuamente exclusivo con reservation_id) * payment_type_id: ID del tipo de pago (integer, nullable) * description: Descripción del pago (string, nullable) * gateway_response: Respuesta completa del gateway (string, nullable)

Validaciones: * Debe proporcionarse reservation_id O enrollment_id, pero no ambos * Si se proporciona reservation_id, debe existir en la tabla reservation * Si se proporciona enrollment_id, debe existir en la tabla enrollment * El transaction_id debe ser una cadena no vacía

Response 201:

  {
    "id": 1,
    "reservation_id": 1,
    "enrollment_id": null,
    "payment_type_id": 1,
    "amount": 50000.00,
    "transaction_id": "TXN-123456",
    "gateway_response": "{\"status\":\"completed\"}",
    "status": true,
    "date": "2024-01-10T08:00:00Z",
    "description": "Pago de reserva"
  }

Response 400:

{
  "error": "Datos incompletos",
  "message": "amount y transaction_id son requeridos"
}

PATCH /admin/payments/:id

Actualiza un pago existente. Permite actualizar campos operativos; no admite cambiar reservation_id ni enrollment_id (la afiliación se hace en el flujo de reserva/inscripción).

Request Body:

{
  "status": true,
  "payment_type_id": 1,
  "description": "Pago actualizado"
}

Campos actualizables: * payment_type_id: Cambiar el tipo de pago (integer, nullable) * status: Actualizar el estado del pago (boolean) * description: Actualizar la descripción (string, nullable)

Validaciones: * Si el body incluye reservation_id o enrollment_id, responde 422 * Al menos un campo actualizable debe ser proporcionado

Response 200:

  {
    "id": 1,
    "reservation_id": 2,
    "enrollment_id": null,
    "payment_type_id": 1,
    "amount": 50000.00,
    "transaction_id": "TXN-123456",
    "gateway_response": "{\"status\":\"completed\"}",
    "status": true,
    "date": "2024-01-10T08:00:00Z",
    "description": "Pago actualizado"
  }

Response 400:

{
  "error": "Datos incompletos",
  "message": "Debe proporcionar al menos un campo para actualizar"
}

Response 404:

{
  "error": "Pago no encontrado"
}

DELETE /admin/payments/:id

Elimina un pago del sistema. Use con precaución, ya que esto elimina permanentemente el registro del pago.

Response 204: No Content (éxito)

Response 404:

{
  "error": "Pago no encontrado"
}

Clases (Admin)

GET /admin/classes

Obtiene lista de clases (admin).

Response 200: Lista de clases (TrainingClassInterface[]).

POST /admin/classes

Crea una nueva clase (admin).

Request Body:

{
  "user_id": 1,
  "title": "Clase de Fútbol",
  "description": "Clase de entrenamiento",
  "start_time": "2024-01-15T10:00:00Z",
  "end_time": "2024-01-15T12:00:00Z",
  "start_date": "2024-01-15",
  "end_date": "2024-03-15",
  "class_start_time": "10:00:00",
  "recurrence_days": ["MO", "WE", "FR"]
}

Campos requeridos: * title: Título de la clase * start_time: Fecha y hora de inicio * end_time: Fecha y hora de fin * start_date: Fecha de inicio del período * end_date: Fecha de fin del período * class_start_time: Hora de inicio de cada clase * recurrence_days: Array de días de recurrencia (MO, TU, WE, TH, FR, SA, SU)

Campos opcionales: * user_id: ID del entrenador (opcional, usa admin actual) * description: Descripción de la clase

Response 201: Clase creada exitosamente.

GET /admin/classes/{id}

Obtiene una clase específica (admin).

Response 200: Datos de la clase (TrainingClassInterface). Response 404: Clase no encontrada

PUT /admin/classes/{id}

Actualiza una clase (admin).

Response 200: Clase actualizada exitosamente.

DELETE /admin/classes/{id}

Elimina una clase (admin).

Response 200: Clase eliminada exitosamente.

PATCH /admin/classes/{id}/cancel

Cancela una clase (admin).

Response 200: Clase cancelada exitosamente.

PATCH /admin/classes/{id}/complete

Marca una clase como completada (admin).

Response 200: Clase marcada como completada exitosamente.

Eventos (Admin — panel PMV)

Eventos deportivos del panel: reserva de cancha + metadatos + participantes por casilla. No usar los endpoints obsoletos de inscripción (accept/reject/attendance); no existen en v0.4.1.

Desambiguación: xref:api-reference.adoc#_eventos_trainer_inscripciones describe inscripciones a clases (enrollment), no este módulo.

GET /admin/events

Lista eventos ordenados por checking descendente.

Response 200: Array EventInterface[] (cuerpo sin envoltorio).

[
  {
    "id": 12,
    "reservation_id": 340,
    "title": "Torneo amistoso",
    "event_type_id": 1,
    "event_type_name": "Evento",
    "capacity": 8,
    "status": "scheduled",
    "court_id": 2,
    "court_name": "Cancha 2",
    "checking_date": "2026-06-16",
    "checking_time": "19:00:00",
    "checkout_date": "2026-06-16",
    "checkout_time": "21:00:00",
    "checking": "2026-06-16 19:00:00",
    "checkout": "2026-06-16 21:00:00",
    "participant_count": 3
  }
]

POST /admin/events

Crea reserva tipo Evento (estado Pendiente) y fila event.

Request Body:

{
  "court_id": 2,
  "checking": "2026-06-16 19:00:00",
  "checkout": "2026-06-16 21:00:00",
  "title": "Torneo amistoso",
  "description": "Opcional",
  "capacity": 8,
  "event_type_id": 1,
  "notes": "Notas de reserva"
}

Campos requeridos: court_id, checking, checkout, title, capacity (>= 1), event_type_id.

Response 201: EventInterface con participants: []. Response 404: Cancha o tipo de evento inexistente. Response 409: Slot ocupado o solapamiento (validateBookingSlot). Response 422: Formato de fecha/hora inválido.

GET /admin/events/{id}

Detalle del evento con array participants.

Response 200: EventInterface incluyendo participants: EventParticipantInterface[]. Response 404: Evento no encontrado.

DELETE /admin/events/{id}

Elimina el evento y la reserva asociada (CASCADE).

Response 204: Sin contenido. Response 404: Evento no encontrado.

GET /admin/events/{id}/participants

Lista participantes del evento.

Response 200: EventParticipantInterface[].

POST /admin/events/{id}/participants

Asigna cliente a una casilla consumiendo un boleto.

Request Body:

{
  "slot": 1,
  "client_id": 42
}

Alternativa con boleto explícito: ticket_id en lugar de client_id (el backend resuelve cliente desde la tirilla).

Response 201: Participante creado. Response 409: Cliente duplicado, casilla ocupada o ticket ya usado. Response 422: client_id o ticket_id requerido; slot fuera de capacidad; cliente sin boleto disponible.

DELETE /admin/events/{id}/participants/{participantId}

Quita participante y libera el boleto (status: available).

Response 204: Sin contenido. Response 404: Participante no encontrado.

Tickets (panel)

El listado de emisiones y el wizard de venta están en la ruta panel /tickets, alineados con GET /admin/tickets/sales y POST /admin/tickets/sales (ver sección Tickets más arriba).

Eventos (Admin) — obsoleto

Los siguientes endpoints no existen en v0.4.1 y corresponden al modelo antiguo de inscripciones: PATCH /admin/events/{id}/accept, reject, attendance. Usar participantes y tickets según esta sección.

Notificaciones (Admin)

POST /admin/notifications/token

Registra o actualiza token FCM para usuario (admin).

Request Body:

{
  "userId": "firebase-uid-123",
  "token": "fcm-token-abc123"
}

Campos requeridos: * userId: OAuth2 local IdP UID del usuario * token: Token FCM del dispositivo

Response 200: Token FCM registrado exitosamente. Response 400: Datos incompletos

DELETE /admin/notifications/token

Elimina token FCM del usuario autenticado (admin).

Response 200: Token FCM eliminado exitosamente. Response 404: Token no encontrado

POST /admin/notifications/send

Envia notificación push a un cliente específico.

Request Body:

{
  "clientId": 1,
  "title": "Título de la notificación",
  "body": "Cuerpo de la notificación",
  "url": "/reservations/1"
}

Campos requeridos: * clientId: ID del cliente al que se enviará la notificación

Campos opcionales: * title: Título de la notificación * body: Cuerpo de la notificación * url: URL a la que navegar al hacer clic

Response 200: Notificación enviada exitosamente. Response 400: Datos incompletos Response 404: Cliente o usuario no encontrado

Nota: La interfaz TypeScript correspondiente es PushNotificationPayload del paquete @cortex-ia-com-co/common.

Trainer (Inscripciones — enrollment)

Los endpoints bajo /trainer/* gestionan clases, alumnos e inscripciones (enrollment). No confundir con eventos del panel (EventInterface); ver Módulo de Eventos (Panel).

Clases (Trainer)

GET /trainer/classes

Obtiene lista de clases del entrenador.

Response 200: Lista de clases (TrainingClassInterface[]).

POST /trainer/classes

Crea una nueva clase.

Request Body: Similar a POST /admin/classes pero sin user_id (usa el entrenador autenticado).

Response 201: Clase creada exitosamente.

GET /trainer/classes/{id}

Obtiene una clase específica.

Response 200: Datos de la clase (TrainingClassInterface). Response 404: Clase no encontrada

PUT /trainer/classes/{id}

Actualiza una clase.

Response 200: Clase actualizada exitosamente.

DELETE /trainer/classes/{id}

Elimina una clase.

Response 200: Clase eliminada exitosamente.

PATCH /trainer/classes/{id}/cancel

Cancela una clase.

Response 200: Clase cancelada exitosamente.

PATCH /trainer/classes/{id}/complete

Marca una clase como completada.

Response 200: Clase marcada como completada exitosamente.

Reservas (Trainer)

GET /trainer/bookings

Obtiene lista de reservas del entrenador.

Query Parameters: * court_id (opcional): Filtrar por cancha específica * from (opcional): Fecha de inicio para filtrar reservas (ISO 8601) * to (opcional): Fecha de fin para filtrar reservas (ISO 8601)

Response 200: Lista de reservas (excluye canceladas).

POST /trainer/bookings

Crea una reserva para el entrenador.

Request Body: Similar a POST /account/bookings.

Response 201: Reserva creada exitosamente.

GET /trainer/bookings/calculate-price

Calcula el precio de una reserva con ajustes de tarifas.

Query Parameters: * date (requerido): Fecha de la reserva (YYYY-MM-DD) * checking (requerido): Hora de inicio (HH:MM) * checkout (requerido): Hora de fin (HH:MM) * court_id (requerido): ID de la cancha * reservation_type_id (opcional): ID del tipo de reserva

Response 200: Ficha detallada con precio calculado (CalculatePriceResponseInterface). Response 400: Parámetros inválidos Response 404: Cancha no encontrada Response 500: Error interno del servidor

Eventos (Trainer — inscripciones)

En trainer, «eventos» son inscripciones (enrollment) a clases. La interfaz histórica en respuestas puede aparecer como inscripción; no confundir con EventInterface del panel (xref:api-reference.adoc#_eventos_admin_panel).

GET /trainer/events

Obtiene lista de eventos del entrenador.

Query Parameters: * class_id (opcional): Filtrar eventos por clase * status (opcional): Filtrar eventos por estado (boolean)

Response 200: Lista de eventos (EventInterface[]).

POST /trainer/events

Crea una nueva evento.

Request Body:

{
  "class_id": 1,
  "client_id": 1
}

Response 201: Evento creada exitosamente. Response 400: Datos inválidos Response 404: Clase no encontrada

GET /trainer/events/{id}

Obtiene una evento específica.

Response 200: Datos de la evento (EventInterface). Response 404: Evento no encontrada

DELETE /trainer/events/{id}

Elimina una evento.

Response 200: Evento eliminada exitosamente. Response 400: No se puede eliminar (fecha pasada o no creada por el trainer)

PATCH /trainer/events/{id}/accept

Acepta una evento.

Response 200: Evento aceptada exitosamente.

PATCH /trainer/events/{id}/reject

Rechaza una evento.

Request Body:

{
  "reason": "Cupo lleno"
}

Response 200: Evento rechazada exitosamente.

PATCH /trainer/events/{id}/attendance

Registra asistencia de un alumno.

Request Body:

{
  "attendance_status": "present"
}

Response 200: Asistencia registrada exitosamente.

Alumnos (Trainer)

GET /trainer/students

Obtiene lista de alumnos del entrenador.

Query Parameters: * class_id (opcional): Filtrar alumnos por clase específica

Response 200: Lista de alumnos (StudentInterface[]).

GET /trainer/students/{id}

Obtiene un alumno específico.

Response 200: Datos del alumno (StudentInterface). Response 404: Alumno no encontrado

POST /trainer/students/{id}/assign

Asigna un alumno a una clase.

Request Body:

{
  "class_id": 1
}

Response 201: Alumno asignado exitosamente. Response 400: Datos inválidos Response 404: Clase o alumno no encontrado

Clientes (Trainer)

GET /trainer/clients

Obtiene lista de clientes con búsqueda.

Query Parameters: * search (opcional): Búsqueda por email, display_name, first_name, last_name, document o phone * limit (opcional, default: 20): Límite de resultados * offset (opcional, default: 0): Offset para paginación

Response 200: Lista de clientes (ClientInterface[]).

POST /trainer/clients

Crea un nuevo cliente.

Request Body: Similar a POST /admin/clients.

Response 201: Cliente creado exitosamente.

GET /trainer/clients/{id}

Obtiene un cliente específico.

Response 200: Datos del cliente (ClientInterface). Response 404: Cliente no encontrado

Canchas (Trainer)

GET /trainer/courts

Obtiene lista de canchas disponibles.

Response 200: Lista de canchas (CourtInterface[]).

Dashboard (Trainer)

GET /trainer/dashboard/stats

Obtiene estadísticas del dashboard del entrenador.

Response 200: Estadísticas del dashboard.

Tipos de Documento (Trainer)

GET /trainer/document-types

Obtiene todos los tipos de documento disponibles.

Response 200: Lista de tipos de documento (DocumentTypeInterface[]).

Tipos de Pago (Trainer)

GET /trainer/payment-types

Obtiene todos los tipos de pago disponibles.

Response 200: Lista de tipos de pago (PaymentTypeInterface[]).

Tipos de Reserva (Trainer)

GET /trainer/reservation-types

Obtiene todos los tipos de reserva disponibles.

Response 200: Lista de tipos de reserva (ReservationTypeInterface[]).

Tarifas de Clase (Trainer)

GET /trainer/tariff-classes

Obtiene lista de tarifas de clase.

Query Parameters: * court_type_id (opcional): Filtrar por tipo de cancha

Response 200: Lista de tarifas de clase (TariffClassInterface[]).

GET /trainer/tariff-classes/{id}

Obtiene una tarifa de clase específica.

Response 200: Tarifa de clase encontrada (TariffClassInterface). Response 404: Tarifa de clase no encontrada

Public (Público)

Los endpoints bajo /public/* no requieren autenticación.

GET /public/document-types

Obtiene todos los tipos de documento (público).

Response 200: Lista de tipos de documento (DocumentTypeInterface[]).

Sudo (Super Administradores)

Los endpoints bajo /sudo/* son exclusivos para super administradores con permisos completos.

Usuarios (Sudo)

GET /sudo/users

Obtiene todos los usuarios de OAuth2 local IdP (solo para administradores).

Query Parameters: * maxResults (opcional, default: 1000): Número máximo de resultados (máximo 1000) * pageToken (opcional): Token para paginación

Response 200: Lista de usuarios con paginación (UsersListResponseInterface). Response 403: No tienes permisos de administrador

POST /sudo/users

Crea un nuevo usuario en OAuth2 local IdP.

Request Body:

{
  "email": "usuario@example.com",
  "password": "password123",
  "displayName": "Usuario",
  "disabled": false,
  "emailVerified": false,
  "role": "admin",
  "trainer": false
}

Campos requeridos: * email: Email del usuario * password: Contraseña del usuario

Campos opcionales: * displayName: Nombre para mostrar * disabled: Si el usuario está deshabilitado * emailVerified: Si el email está verificado * role: Rol principal ("admin", "operator", "sudo", "trainer") * trainer: Indica si el usuario tendrá rol de entrenador (boolean)

Response 201: Usuario creado exitosamente (OauthUserProfileInterface). Response 400: Datos inválidos (email duplicado, formato inválido, etc.)

GET /sudo/users/{uid}

Obtiene un usuario específico de OAuth2 local IdP.

Response 200: Usuario obtenido exitosamente (OauthUserProfileInterface). Response 404: Usuario no encontrado

PUT /sudo/users/{uid}

Actualiza un usuario de OAuth2 local IdP.

Request Body: Similar a POST pero con campos opcionales.

Response 200: Usuario actualizado exitosamente.

DELETE /sudo/users/{uid}

Elimina un usuario de OAuth2 local IdP.

Response 204: Usuario eliminado exitosamente. Response 404: Usuario no encontrado

POST /sudo/users/{uid}/enable

Habilita un usuario de OAuth2 local IdP.

Response 200: Usuario habilitado exitosamente.

POST /sudo/users/{uid}/disable

Deshabilitar un usuario de OAuth2 local IdP.

Response 200: Usuario deshabilitado exitosamente.

Pagos (Cuenta de Usuario)

Los endpoints de pagos de cuenta permiten a los usuarios autenticados gestionar sus propios pagos asociados a sus reservas.

Nota importante: * Estos endpoints solo devuelven pagos asociados a reservas del usuario autenticado * El campo status es un boolean: false = pendiente, true = completado * Los pagos incluyen información adicional de la reserva asociada (court_name, checking, checkout) * Validación de pagos para confirmar reserva: Para confirmar una reserva, todos los pagos asociados deben tener status: true (completados) y el monto total pagado debe cubrir el precio total de la reserva

GET /account/payments

Obtiene el historial de pagos del usuario autenticado con paginación.

Query Parameters: * page (opcional, default: 1): Número de página * limit (opcional, default: 10): Cantidad de resultados por página

Response 200:

{
  "payments": [
    {
      "id": 1,
      "reservation_id": 1,
      "amount": 50000.00,
      "transaction_id": "TXN-123456",
      "gateway_response": "{\"status\":\"completed\"}",
      "status": true,
      "date": "2024-01-10T08:00:00Z",
      "description": "Pago de reserva",
      "court_name": "Cancha 1",
      "checking": "2024-01-15T10:00:00Z",
      "checkout": "2024-01-15T12:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 25,
    "totalPages": 3
  }
}

Campos adicionales en respuesta: * court_name: Nombre de la cancha de la reserva asociada * checking: Fecha y hora de inicio de la reserva * checkout: Fecha y hora de fin de la reserva

GET /account/payments/:id

Obtiene un pago específico del usuario autenticado.

Response 200:

{
  "id": 1,
  "reservation_id": 1,
  "amount": 50000.00,
  "transaction_id": "TXN-123456",
  "gateway_response": "{\"status\":\"completed\"}",
  "status": true,
  "date": "2024-01-10T08:00:00Z",
  "description": "Pago de reserva",
  "court_name": "Cancha 1",
  "checking": "2024-01-15T10:00:00Z",
  "checkout": "2024-01-15T12:00:00Z"
}

Response 403:

{
  "error": "Acceso denegado",
  "message": "Este pago no pertenece al usuario autenticado"
}

Response 404:

{
  "error": "Pago no encontrado"
}

POST /account/payments

Crea un nuevo pago asociado a una reserva del cliente autenticado. El frontend debe asegurarse de que la reserva ya exista y pertenezca al cliente actual.

Request Body:

{
  "reservation_id": 1,
  "payment_type_id": 1,
  "amount": 50000.00,
  "transaction_id": "TXN-123456",
  "description": "Pago de reserva"
}

Campos requeridos: * reservation_id: ID de la reserva asociada (integer) * payment_type_id: ID del tipo de pago (integer) * amount: Monto del pago (number, debe ser mayor a 0) * transaction_id: ID de transacción del gateway (string, no vacío)

Campos opcionales: * description: Descripción del pago (string, nullable)

Validaciones: * La reserva debe existir y pertenecer al cliente del usuario autenticado * El transaction_id debe ser una cadena no vacía * El amount debe ser un número mayor a 0 * Los IDs deben ser numéricos válidos

Response 201:

{
  "id": 1,
  "reservation_id": 1,
  "payment_type_id": 1,
  "amount": 50000.00,
  "transaction_id": "TXN-123456",
  "gateway_response": null,
  "status": false,
  "date": "2024-01-10T08:00:00Z",
  "description": "Pago de reserva"
}

Response 400:

{
  "error": "Datos incompletos",
  "message": "reservation_id, payment_type_id, amount y transaction_id son requeridos"
}

Response 403:

{
  "error": "Acceso denegado",
  "message": "La reserva no pertenece al usuario autenticado"
}

Clientes (Admin)

GET /admin/clients

Obtiene todos los clientes.

Query Parameters: * search (opcional): Búsqueda por email o display_name * limit (opcional, default: 100): Límite de resultados * offset (opcional, default: 0): Offset para paginación

Response 200: Lista de clientes (ClientInterface[]).

POST /admin/clients

Crea un nuevo cliente.

Request Body:

{
  "user_id": 1,
  "email": "cliente@example.com",
  "display_name": "Juan Pérez",
  "first_name": "Juan",
  "last_name": "Pérez",
  "phone": "+573001234567",
  "document": "1234567890",
  "document_type_id": 1,
  "address": "Calle 123"
}

Campos requeridos: * first_name: Nombre del cliente * last_name: Apellido del cliente * document: Número de documento * document_type_id: ID del tipo de documento

Campos opcionales: * user_id: ID del usuario existente en la base de datos (user.id). Opcional si se proporciona email * email: Email del nuevo usuario (opcional si se proporciona user_id). Se creará el usuario en OAuth2 local IdP con contraseña aleatoria y se enviará un email con link para establecer contraseña * display_name: Nombre para mostrar del usuario (solo si se proporciona email) * phone: Teléfono del cliente (opcional). Se vinculará al usuario en OAuth2 local IdP * address: Dirección del cliente

Response 201: Cliente creado exitosamente (ClientInterface). Response 400: Datos inválidos

GET /admin/clients/{id}

Obtiene un cliente por ID.

Response 200: Cliente encontrado (ClientInterface). Response 404: Cliente no encontrado

PATCH /admin/clients/{id}

Edita un cliente por ID.

Request Body:

{
  "first_name": "Juan",
  "last_name": "Pérez",
  "document": "1234567890",
  "document_type_id": 1,
  "address": "Calle 123"
}

Response 200: Cliente actualizado exitosamente.

DELETE /admin/clients/{id}

Elimina un cliente por ID.

Response 200: Cliente eliminado exitosamente.

Canchas (Admin)

GET /admin/courts

Obtiene todas las canchas.

Query Parameters: * search (opcional): Búsqueda por nombre

Response 200: Lista de canchas (CourtInterface[]).

POST /admin/courts

Crea una nueva cancha.

Request Body:

{
  "name": "Cancha 1",
  "court_type_id": 1,
  "cost": 10000.00,
  "price": 50000.00
}

Campos requeridos: * name: Nombre de la cancha * court_type_id: ID del tipo de cancha

Response 201: Cancha creada exitosamente (CourtInterface). Response 400: Datos inválidos

GET /admin/courts/{id}

Obtiene una cancha por ID.

Response 200: Cancha encontrada (CourtInterface). Response 404: Cancha no encontrada

PATCH /admin/courts/{id}

Actualiza una cancha.

Response 200: Cancha actualizada exitosamente.

DELETE /admin/courts/{id}

Elimina una cancha.

Response 204: Cancha eliminada exitosamente.

Tipos de Cancha (Admin)

GET /admin/court-types

Obtiene todos los tipos de cancha.

Query Parameters: * search (opcional): Búsqueda por nombre o descripción

Response 200: Lista de tipos de cancha (CourtTypeInterface[]).

POST /admin/court-types

Crea un nuevo tipo de cancha.

Request Body:

{
  "name": "Fútbol",
  "description": "Cancha de fútbol"
}

Campos requeridos: * name: Nombre del tipo de cancha

Response 201: Tipo de cancha creado exitosamente (CourtTypeInterface). Response 400: Datos inválidos

GET /admin/court-types/{id}

Obtiene un tipo de cancha por ID.

Response 200: Tipo de cancha encontrado (CourtTypeInterface). Response 404: Tipo de cancha no encontrado

PATCH /admin/court-types/{id}

Actualiza un tipo de cancha.

Response 200: Tipo de cancha actualizado exitosamente.

DELETE /admin/court-types/{id}

Elimina un tipo de cancha.

Response 204: Tipo de cancha eliminado exitosamente.

Tipos de Documento (Admin)

GET /admin/document-types

Obtiene todos los tipos de documento.

Query Parameters: * search (opcional): Búsqueda por nombre o descripción

Response 200: Lista de tipos de documento (DocumentTypeInterface[]).

POST /admin/document-types

Crea un nuevo tipo de documento.

Request Body:

{
  "name": "Cédula de Ciudadanía",
  "description": "Documento de identidad colombiano"
}

Campos requeridos: * name: Nombre del tipo de documento

Response 201: Tipo de documento creado exitosamente (DocumentTypeInterface). Response 400: Datos inválidos

GET /admin/document-types/{id}

Obtiene un tipo de documento por ID.

Response 200: Tipo de documento encontrado (DocumentTypeInterface). Response 404: Tipo de documento no encontrado

PATCH /admin/document-types/{id}

Actualiza un tipo de documento.

Response 200: Tipo de documento actualizado exitosamente.

DELETE /admin/document-types/{id}

Elimina un tipo de documento.

Response 204: Tipo de documento eliminado exitosamente.

Tipos de Pago (Admin)

GET /admin/payment-types

Obtiene todos los tipos de pago.

Query Parameters: * search (opcional): Búsqueda por nombre o descripción

Response 200: Lista de tipos de pago (PaymentTypeInterface[]).

POST /admin/payment-types

Crea un nuevo tipo de pago.

Request Body:

{
  "name": "Efectivo",
  "description": "Pago en efectivo"
}

Campos requeridos: * name: Nombre del tipo de pago

Response 201: Tipo de pago creado exitosamente (PaymentTypeInterface). Response 400: Datos inválidos

GET /admin/payment-types/{id}

Obtiene un tipo de pago por ID.

Response 200: Tipo de pago encontrado (PaymentTypeInterface). Response 404: Tipo de pago no encontrado

PATCH /admin/payment-types/{id}

Actualiza un tipo de pago.

Response 200: Tipo de pago actualizado exitosamente.

DELETE /admin/payment-types/{id}

Elimina un tipo de pago.

Response 204: Tipo de pago eliminado exitosamente.

Estados de Reserva (Admin)

GET /admin/reservation-statuses

Obtiene todos los estados de reserva.

Query Parameters: * search (opcional): Búsqueda por nombre o descripción

Response 200: Lista de estados de reserva (ReservationStatusInterface[]).

POST /admin/reservation-statuses

Crea un nuevo estado de reserva.

Request Body:

{
  "name": "Confirmada",
  "description": "Reserva confirmada",
  "color": "#10b981"
}

Campos requeridos: * name: Nombre del estado * color: Color hexadecimal para representación visual

Response 201: Estado de reserva creado exitosamente (ReservationStatusInterface). Response 400: Datos inválidos

GET /admin/reservation-statuses/{id}

Obtiene un estado de reserva por ID.

Response 200: Estado de reserva encontrado (ReservationStatusInterface). Response 404: Estado de reserva no encontrado

PATCH /admin/reservation-statuses/{id}

Actualiza un estado de reserva.

Response 200: Estado de reserva actualizado exitosamente.

DELETE /admin/reservation-statuses/{id}

Elimina un estado de reserva.

Response 204: Estado de reserva eliminado exitosamente.

Tipos de Reserva (Admin)

GET /admin/reservation-types

Obtiene todos los tipos de reserva.

Query Parameters: * search (opcional): Búsqueda por nombre o descripción

Response 200: Lista de tipos de reserva (ReservationTypeInterface[]).

POST /admin/reservation-types

Crea un nuevo tipo de reserva.

Request Body:

{
  "name": "Libre",
  "description": "Reserva libre"
}

Campos requeridos: * name: Nombre del tipo de reserva

Response 201: Tipo de reserva creado exitosamente (ReservationTypeInterface). Response 400: Datos inválidos

GET /admin/reservation-types/{id}

Obtiene un tipo de reserva por ID.

Response 200: Tipo de reserva encontrado (ReservationTypeInterface). Response 404: Tipo de reserva no encontrado

PATCH /admin/reservation-types/{id}

Actualiza un tipo de reserva.

Response 200: Tipo de reserva actualizado exitosamente.

DELETE /admin/reservation-types/{id}

Elimina un tipo de reserva.

Response 204: Tipo de reserva eliminado exitosamente.

Tarifas (Admin)

GET /admin/tariffs

Obtiene todas las tarifas.

Query Parameters: * search (opcional): Búsqueda por descripción * court_id (opcional): Filtrar por cancha * reservation_type_id (opcional): Filtrar por tipo de reserva

Response 200: Lista de tarifas (TariffInterface[]).

POST /admin/tariffs

Crea una nueva tarifa.

Request Body:

{
  "court_id": 1,
  "reservation_type_id": 1,
  "rrule": null,
  "start_date": "2024-01-01",
  "end_date": "2024-12-31",
  "start_time": "18:00:00",
  "end_time": "22:00:00",
  "adjustment": "-10%",
  "description": "Descuento nocturno",
  "is_active": true
}

Campos requeridos: * adjustment: Ajuste (ej: "-10%", "5000", "-2000", "15%") * is_active: Si la tarifa está activa (boolean)

Campos opcionales: * court_id: ID de la cancha (null = todas las canchas) * reservation_type_id: ID del tipo de reserva (null = todos los tipos) * rrule: Regla recurrente según RFC 5545 (RRULE) * start_date: Fecha de inicio del período (YYYY-MM-DD) * end_date: Fecha de fin del período (YYYY-MM-DD) * start_time: Hora de inicio del rango horario (HH:MM:SS) * end_time: Hora de fin del rango horario (HH:MM:SS) * description: Descripción de la tarifa

Response 201: Tarifa creada exitosamente (TariffInterface). Response 400: Datos inválidos

GET /admin/tariffs/{id}

Obtiene una tarifa por ID.

Response 200: Tarifa encontrada (TariffInterface). Response 404: Tarifa no encontrada

PATCH /admin/tariffs/{id}

Actualiza una tarifa.

Response 200: Tarifa actualizada exitosamente.

DELETE /admin/tariffs/{id}

Elimina una tarifa.

Response 204: Tarifa eliminada exitosamente.

Tarifas de Clase (Admin)

GET /admin/tariff-classes

Obtiene todas las tarifas de clase.

Query Parameters: * court_type_id (opcional): Filtrar por tipo de cancha * reservation_type_id (opcional): Filtrar por tipo de reserva

Response 200: Lista de tarifas de clase (TariffClassInterface[]).

POST /admin/tariff-classes

Crea una nueva tarifa de clase.

Request Body:

{
  "court_type_id": 1,
  "reservation_type_id": 2,
  "single_price": 25000.00,
  "total_price": 50000.00,
  "max_capacity": 20
}

Campos requeridos: * court_type_id: ID del tipo de cancha * reservation_type_id: ID del tipo de reserva * single_price: Precio por persona * total_price: Precio total de la clase * max_capacity: Capacidad máxima de la clase

Response 201: Tarifa de clase creada exitosamente (TariffClassInterface). Response 400: Datos inválidos

GET /admin/tariff-classes/{id}

Obtiene una tarifa de clase por ID.

Response 200: Tarifa de clase encontrada (TariffClassInterface). Response 404: Tarifa de clase no encontrada

PATCH /admin/tariff-classes/{id}

Actualiza una tarifa de clase.

Response 200: Tarifa de clase actualizada exitosamente.

DELETE /admin/tariff-classes/{id}

Elimina una tarifa de clase.

Response 204: Tarifa de clase eliminada exitosamente.

Dashboard (Admin)

GET /admin/dashboard/stats

Obtiene estadísticas del dashboard.

Response 200: Estadísticas del dashboard (DashboardStatsWithRecentInterface).

Campos de respuesta: * clients: Estadísticas de clientes (total, verified, unverified) * courts: Estadísticas de canchas (total) * reservations: Estadísticas de reservas (total, today, this_week, this_month) * payments: Estadísticas de pagos (total, total_amount, completed_amount, pending) * recent_reservations: Array de reservas recientes * recent_payments: Array de pagos recientes

Usuarios (Admin)

GET /admin/users

Obtiene usuarios de OAuth2 local IdP (para operadores y administradores).

Query Parameters: * maxResults (opcional, default: 50): Número máximo de resultados (máximo 1000) * pageToken (opcional): Token para paginación

Response 200: Lista de usuarios con paginación (UsersListResponseInterface).

GET /admin/users/{uid}

Obtiene un usuario específico de OAuth2 local IdP.

Response 200: Usuario obtenido exitosamente (OauthUserProfileInterface). Response 404: Usuario no encontrado

Interfaces TypeScript

Todas las interfaces TypeScript están definidas en el paquete @cortex-ia-com-co/common y están disponibles para importar:

import {
  BookingInterface,
  BookingDetailInterface,
  BookingFullInterface,
  AccountBookingInterface,
  AccountBookingPaymentInterface,
  AccountPaymentInterface,
  PaymentInterface,
  ClientInterface,
  TrainingClassInterface,
  EventInterface,
  TariffClassInterface,
  ReservationTypeInterface,
  ReservationStatusInterface,
  PaymentTypeInterface,
  DocumentTypeInterface,
  CourtInterface,
  CourtTypeInterface,
  TariffInterface,
  CalculatePriceResponseInterface,
  PaginationInterface,
  PaginatedResponseInterface,
  StudentInterface,
  OauthUserProfileInterface,
  UserInterface,
  UsersListResponseInterface,
  DashboardStatsWithRecentInterface,
  PushNotificationPayload,
  ErrorResponseInterface
} from '@cortex-ia-com-co/common';

Interfaces principales:

Reservas

  • BookingInterface: Estructura base de reservas con campos básicos (id, client_id, court_id, reservation_type_id, checking, checkout, notes, etc.)

  • BookingDetailInterface: Extiende BookingInterface con información completa del cliente (client_first_name, client_last_name, client_email, client_phone, client_document, client_address) y de la cancha (court_base_price, court_type_name), además de pagos asociados

  • BookingFullInterface: Reservas con información completa de pagos y precios (total_amount, total_paid, total_debt)

  • AccountBookingInterface: Reservas con información de pagos para usuarios autenticados, incluye total_paid, total_amount, is_payment_complete, can_add_payment y array de payments

  • AccountBookingPaymentInterface: Estructura de pagos dentro de reservas de cuenta, incluye status (string: "pending" | "completed") y statusBoolean (boolean) para compatibilidad

Pagos

  • PaymentInterface: Estructura de pagos (puede tener reservation_id o enrollment_id, pero no ambos). El campo status es boolean (false = pendiente, true = completado). Campo opcional foto (string | null): URL interna del comprobante en S3/R2; la descarga al cliente usa GET …​/payment/{paymentId}/foto, no la URL almacenada

  • AccountPaymentInterface: Extiende PaymentInterface con información adicional de la reserva asociada (court_name, checking, checkout)

Clientes y Usuarios

  • ClientInterface: Estructura de clientes con información completa (id, user_id, email, display_name, first_name, last_name, phone, document, document_type_id, address, email_verified, is_available)

  • StudentInterface: Estructura de alumnos del entrenador (id, user_id, email, display_name, first_name, last_name, phone, email_verified, created_at)

  • OauthUserProfileInterface: Estructura de usuarios de OAuth2 local IdP con claims de roles (uid, email, emailVerified, displayName, disabled, role, trainer, operator, admin, sudo, metadata)

  • UserInterface: Estructura de usuarios en la base de datos (id, local_id, email, email_verified, display_name, phone, disabled, creation_time, last_sign_in_time, etc.)

  • UsersListResponseInterface: Respuesta de lista de usuarios con paginación (users, pageToken, total)

Clases y eventos

  • TrainingClassInterface: Clases de entrenamiento (id, user_id, reservation_id, title, horarios, recurrencia, status, etc.)

  • EventInterface: Evento deportivo del panel (reservation_id, event_type_id, title, capacity, status, campos mural checking_*/checkout_*, participants opcional)

  • EventParticipantInterface: Participante por casilla (event_id, client_id, ticket_id, strip_id, slot, datos de cliente)

  • CreateEventPayload: Cuerpo de POST /admin/events (court_id, checking, checkout, title, capacity, event_type_id, …)

  • EnrollmentEventInterface (nombre histórico en trainer): inscripción a clase (class_id, client_id, status, attendance_status, pagos asociados)

Canchas y Tipos

  • CourtInterface: Estructura de canchas (id, court_type_id, name, cost, price, court_type_name)

  • CourtTypeInterface: Estructura de tipos de cancha (id, name, description, created_at)

Tarifas

  • TariffInterface: Estructura de tarifas con ajustes dinámicos (id, court_id, reservation_type_id, rrule, start_date, end_date, start_time, end_time, adjustment, description, is_active, created_at, updated_at)

  • TariffClassInterface: Estructura de tarifas de clase (id, court_type_id, reservation_type_id, single_price, total_price, max_capacity, created_at, updated_at, court_type_name, reservation_type_name)

Tipos y Estados

  • ReservationTypeInterface: Estructura de tipos de reserva (id, name, description)

  • ReservationStatusInterface: Estructura de estados de reserva (id, name, color, description). El campo color es un color hexadecimal para representación visual en calendarios

  • PaymentTypeInterface: Estructura de tipos de pago (id, name, description)

  • DocumentTypeInterface: Estructura de tipos de documento (id, name, description)

Utilidades

  • CalculatePriceResponseInterface: Respuesta del cálculo de precio con información detallada (court, date, checking, checkout, available, hours, price_per_hour, final_price, applied_adjustments)

  • PaginationInterface: Información de paginación (page, limit, total, totalPages)

  • PaginatedResponseInterface<T>: Respuesta paginada genérica con datos y paginación

  • DashboardStatsWithRecentInterface: Estadísticas del dashboard con datos recientes (clients, courts, reservations, payments, recent_reservations, recent_payments)

  • PushNotificationPayload: Payload para notificaciones push (clientId, title, body, url)

  • ErrorResponseInterface: Estructura de respuestas de error (error, message, stack)

Nota: Todas las interfaces están completamente tipadas y documentadas en el paquete common. Consulta la documentación del paquete para más detalles sobre cada interfaz. Las interfaces están alineadas con el esquema OpenAPI disponible en /api/api-docs.json.

Códigos de Estado HTTP

  • 200 OK: Petición exitosa

  • 201 Created: Recurso creado exitosamente

  • 204 No Content: Recurso eliminado exitosamente

  • 400 Bad Request: Error en la petición (validación fallida, datos mal formados)

  • Ejemplos: user_id debe ser un número válido, email inválido, se requiere user_id o email

  • 401 Unauthorized: No autenticado, token inválido, o API key inválida

    • Para OAuth2 local IdP: Token de Firebase inválido o expirado

    • Para API Key: Header X-Auth faltante o valor de BOT_API_KEYS incorrecto

  • 403 Forbidden: No autorizado para realizar la acción

  • 404 Not Found: Recurso no encontrado

  • 409 Conflict: Conflicto de recursos (recurso ya existe o está en conflicto)

  • Ejemplos: Email ya en uso, teléfono ya en uso, usuario ya es cliente, usuario no puede convertirse en cliente

  • 500 Internal Server Error: Error del servidor

Nota sobre códigos de estado: * Los códigos 400 se usan para solicitudes mal formadas o datos inválidos * Los códigos 409 se usan específicamente para conflictos de recursos (email/teléfono en uso, usuario ya es cliente, etc.) * Esta separación permite a los clientes distinguir entre errores de validación y conflictos de recursos

Manejo de Errores

Todas las respuestas de error siguen el formato:

{
  "error": "Mensaje de error",
  "message": "Descripción detallada del error (opcional)"
}

Ejemplo:

{
  "error": "Datos incompletos",
  "message": "Los campos requeridos no están presentes: court_id, checking"
}

Nota: La interfaz TypeScript correspondiente es ErrorResponseInterface del paquete @cortex-ia-com-co/common.

Webhooks

El sistema expone webhooks para notificaciones de eventos:

POST /webhooks/payment

Webhook para recibir notificaciones de pagos del gateway.

Request Body:

{
  "transaction_id": "TXN-123456",
  "amount": 50000.00,
  "status": "completed",
  "gateway_response": "{\"status\":\"completed\"}",
  "date": "2024-01-10T08:00:00Z"
}

Nota: El webhook puede recibir status como string desde el gateway (ej: "completed", "pending", "failed"). El sistema convertirá internamente estos valores a boolean según corresponda.

El sistema procesará el webhook y: 1. Buscará si existe un pago con ese transaction_id 2. Si no existe, creará o actualizará el pago en el contexto del flujo de reserva/inscripción 3. Si existe, actualizará el estado del pago según corresponda

Response 200:

{
  "status": "processed",
  "payment_id": 1
}

Campos de respuesta: * status: Estado del procesamiento del webhook ("processed", "error", etc.) * payment_id: ID del pago creado o actualizado