Files
app-padel/docs/API.md
Ivan 864902df81 docs: add comprehensive documentation
- Add README.md with project overview, features, quick start guide,
  project structure, environment variables, and scripts
- Add docs/API.md with complete API reference for all endpoints
- Add docs/DEPLOYMENT.md with production deployment guide covering
  PM2, Nginx, SSL, and Docker options
- Add docker-compose.yml for containerized deployment
- Add Dockerfile with multi-stage build for optimized production image
- Add .dockerignore for efficient Docker builds

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 07:49:33 +00:00

1272 lines
22 KiB
Markdown

# API Reference - Padel Pro
Documentacion completa de los endpoints REST de la API de Padel Pro.
## Informacion General
- **Base URL:** `http://localhost:3000/api`
- **Autenticacion:** Basada en sesiones (NextAuth.js)
- **Formato:** JSON
- **Codificacion:** UTF-8
### Codigos de Estado HTTP
| Codigo | Descripcion |
|--------|-------------|
| 200 | Exito |
| 201 | Recurso creado |
| 400 | Solicitud invalida |
| 401 | No autorizado |
| 403 | Prohibido (sin permisos) |
| 404 | Recurso no encontrado |
| 409 | Conflicto (ej: duplicado) |
| 500 | Error interno del servidor |
### Roles de Usuario
| Rol | Descripcion | Permisos |
|-----|-------------|----------|
| `SUPER_ADMIN` | Administrador del sistema | Acceso total |
| `ORG_ADMIN` | Administrador de organizacion | Gestion completa de la organizacion |
| `SITE_ADMIN` | Administrador de sede | Gestion de una sede especifica |
| `RECEPTIONIST` | Recepcionista | Reservas, ventas, clientes |
| `COACH` | Entrenador | Acceso limitado |
---
## Autenticacion
### Endpoints de NextAuth
```
GET/POST /api/auth/[...nextauth]
```
NextAuth maneja automaticamente los siguientes endpoints:
| Ruta | Metodo | Descripcion |
|------|--------|-------------|
| `/api/auth/signin` | GET | Pagina de inicio de sesion |
| `/api/auth/signout` | POST | Cerrar sesion |
| `/api/auth/session` | GET | Obtener sesion actual |
| `/api/auth/csrf` | GET | Token CSRF |
| `/api/auth/providers` | GET | Proveedores disponibles |
| `/api/auth/callback/:provider` | GET/POST | Callback de proveedor |
---
## Sedes (Sites)
### Listar Sedes
```http
GET /api/sites
```
**Autenticacion:** Requerida
**Respuesta Exitosa (200):**
```json
[
{
"id": "clxyz123",
"name": "Padel Club Centro",
"slug": "padel-club-centro",
"address": "Calle Principal 123",
"phone": "+34 600 000 000",
"email": "centro@padelclub.com",
"timezone": "Europe/Madrid",
"openTime": "08:00",
"closeTime": "22:00",
"courtCount": 6
}
]
```
---
## Canchas (Courts)
### Listar Canchas
```http
GET /api/courts
```
**Autenticacion:** Requerida
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `siteId` | string | Filtrar por sede |
**Respuesta Exitosa (200):**
```json
[
{
"id": "clxyz456",
"name": "Cancha 1",
"type": "INDOOR",
"status": "AVAILABLE",
"pricePerHour": 30.00,
"description": "Cancha cubierta con iluminacion LED",
"features": ["LED", "Cristal"],
"displayOrder": 1,
"isActive": true,
"site": {
"id": "clxyz123",
"name": "Padel Club Centro",
"openTime": "08:00",
"closeTime": "22:00"
}
}
]
```
### Crear Cancha
```http
POST /api/courts
```
**Autenticacion:** Requerida
**Roles permitidos:** `SUPER_ADMIN`, `SITE_ADMIN`
**Cuerpo de la Solicitud:**
```json
{
"siteId": "clxyz123",
"name": "Cancha 7",
"type": "OUTDOOR",
"pricePerHour": 25.00,
"description": "Cancha exterior",
"features": ["Cesped artificial"],
"displayOrder": 7
}
```
**Campos Requeridos:** `siteId`, `name`, `pricePerHour`
**Respuesta Exitosa (201):**
```json
{
"id": "clxyz789",
"name": "Cancha 7",
"type": "OUTDOOR",
"status": "AVAILABLE",
"pricePerHour": 25.00,
"isActive": true,
"site": { ... }
}
```
### Obtener Cancha por ID
```http
GET /api/courts/{id}
```
**Autenticacion:** Requerida
**Respuesta Exitosa (200):**
```json
{
"id": "clxyz456",
"name": "Cancha 1",
"type": "INDOOR",
"status": "AVAILABLE",
"pricePerHour": 30.00,
"site": {
"id": "clxyz123",
"name": "Padel Club Centro",
"address": "Calle Principal 123",
"phone": "+34 600 000 000"
}
}
```
### Actualizar Cancha
```http
PUT /api/courts/{id}
```
**Autenticacion:** Requerida
**Roles permitidos:** `SUPER_ADMIN`, `SITE_ADMIN`
**Cuerpo de la Solicitud (todos los campos opcionales):**
```json
{
"name": "Cancha 1 VIP",
"type": "INDOOR",
"status": "MAINTENANCE",
"pricePerHour": 35.00,
"description": "Cancha premium renovada",
"features": ["LED", "Cristal", "Climatizada"],
"isActive": true
}
```
### Eliminar Cancha
```http
DELETE /api/courts/{id}
```
**Autenticacion:** Requerida
**Roles permitidos:** `SUPER_ADMIN`, `SITE_ADMIN`
**Respuesta Exitosa (200):**
```json
{
"message": "Court deleted successfully"
}
```
### Obtener Disponibilidad
```http
GET /api/courts/{id}/availability
```
**Autenticacion:** Requerida
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `date` | string | Fecha en formato YYYY-MM-DD |
---
## Reservas (Bookings)
### Listar Reservas
```http
GET /api/bookings
```
**Autenticacion:** Requerida
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `siteId` | string | Filtrar por sede |
| `date` | string | Filtrar por fecha (YYYY-MM-DD) |
| `status` | string | PENDING, CONFIRMED, CANCELLED, COMPLETED, NO_SHOW |
| `clientId` | string | Filtrar por cliente |
| `courtId` | string | Filtrar por cancha |
**Respuesta Exitosa (200):**
```json
[
{
"id": "clxyz001",
"startTime": "2024-01-15T10:00:00.000Z",
"endTime": "2024-01-15T11:30:00.000Z",
"status": "CONFIRMED",
"paymentType": "CARD",
"totalPrice": 45.00,
"paidAmount": 45.00,
"notes": null,
"playerNames": ["Juan", "Maria", "Pedro", "Ana"],
"court": {
"id": "clxyz456",
"name": "Cancha 1",
"type": "INDOOR"
},
"client": {
"id": "clxyz100",
"firstName": "Juan",
"lastName": "Garcia",
"email": "juan@email.com",
"phone": "+34 600 111 222"
},
"site": {
"id": "clxyz123",
"name": "Padel Club Centro"
}
}
]
```
### Crear Reserva
```http
POST /api/bookings
```
**Autenticacion:** Requerida
**Cuerpo de la Solicitud:**
```json
{
"courtId": "clxyz456",
"clientId": "clxyz100",
"startTime": "2024-01-15T10:00:00.000Z",
"endTime": "2024-01-15T11:30:00.000Z",
"paymentType": "card",
"notes": "Reserva para cumpleanos",
"participants": [
{ "name": "Juan" },
{ "name": "Maria" },
{ "name": "Pedro" },
{ "name": "Ana" }
]
}
```
**Respuesta Exitosa (201):**
```json
{
"id": "clxyz002",
"startTime": "2024-01-15T10:00:00.000Z",
"endTime": "2024-01-15T11:30:00.000Z",
"status": "PENDING",
"totalPrice": 45.00,
"paidAmount": 0.00,
"court": { ... },
"client": { ... }
}
```
**Errores Comunes:**
- `409`: Ya existe una reserva en ese horario
- `404`: Cancha o cliente no encontrado
- `400`: La cancha no esta disponible
### Obtener Reserva por ID
```http
GET /api/bookings/{id}
```
### Actualizar Reserva
```http
PUT /api/bookings/{id}
```
**Cuerpo de la Solicitud:**
```json
{
"status": "CONFIRMED",
"notes": "Cliente confirmado por telefono",
"playerNames": ["Juan", "Maria", "Pedro", "Ana"]
}
```
### Cancelar Reserva
```http
DELETE /api/bookings/{id}
```
**Cuerpo Opcional:**
```json
{
"cancelReason": "Cliente solicito cancelacion"
}
```
### Registrar Pago de Reserva
```http
POST /api/bookings/{id}/pay
```
**Cuerpo de la Solicitud:**
```json
{
"amount": 45.00,
"paymentType": "CARD",
"reference": "TRX-123456"
}
```
---
## Clientes (Clients)
### Listar Clientes
```http
GET /api/clients
```
**Autenticacion:** Requerida
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `search` | string | Buscar por nombre, email o telefono |
| `isActive` | boolean | Filtrar por estado activo |
| `limit` | number | Limite de resultados (max 100) |
| `offset` | number | Desplazamiento para paginacion |
**Respuesta Exitosa (200):**
```json
{
"data": [
{
"id": "clxyz100",
"firstName": "Juan",
"lastName": "Garcia",
"email": "juan@email.com",
"phone": "+34 600 111 222",
"level": "INTERMEDIATE",
"tags": ["VIP", "Torneo"],
"isActive": true,
"createdAt": "2024-01-01T00:00:00.000Z",
"memberships": [
{
"id": "clxyz200",
"status": "ACTIVE",
"remainingHours": 8,
"endDate": "2024-06-01T00:00:00.000Z",
"plan": {
"id": "clxyz300",
"name": "Plan Premium",
"discountPercent": 20
}
}
],
"_count": {
"bookings": 15
}
}
],
"pagination": {
"total": 150,
"limit": 50,
"offset": 0,
"hasMore": true
}
}
```
### Crear Cliente
```http
POST /api/clients
```
**Cuerpo de la Solicitud:**
```json
{
"firstName": "Maria",
"lastName": "Lopez",
"email": "maria@email.com",
"phone": "+34 600 333 444",
"dateOfBirth": "1990-05-15",
"address": "Calle Nueva 45",
"notes": "Prefiere canchas interiores",
"tags": ["Principiante"],
"skillLevel": "BEGINNER",
"emergencyContact": {
"name": "Pedro Lopez",
"phone": "+34 600 555 666"
}
}
```
**Campos Requeridos:** `firstName`, `lastName`
### Obtener Cliente por ID
```http
GET /api/clients/{id}
```
### Actualizar Cliente
```http
PUT /api/clients/{id}
```
### Obtener Membresia del Cliente
```http
GET /api/clients/{id}/membership
```
---
## Productos (Products)
### Listar Productos
```http
GET /api/products
```
**Autenticacion:** Requerida
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `siteId` | string | Filtrar por sede |
| `categoryId` | string | Filtrar por categoria |
| `search` | string | Buscar por nombre |
| `isActive` | boolean | Solo productos activos |
**Respuesta Exitosa (200):**
```json
[
{
"id": "clxyz400",
"name": "Pelotas HEAD Pro",
"description": "Bote de 3 pelotas",
"sku": "BALL-HEAD-001",
"price": 6.50,
"costPrice": 4.00,
"stock": 50,
"minStock": 10,
"trackStock": true,
"image": null,
"isActive": true,
"lowStock": false,
"category": {
"id": "clxyz500",
"name": "Pelotas",
"description": "Pelotas de padel"
}
}
]
```
### Crear Producto
```http
POST /api/products
```
**Autenticacion:** Requerida
**Roles permitidos:** `SUPER_ADMIN`, `ORG_ADMIN`, `SITE_ADMIN`
**Cuerpo de la Solicitud:**
```json
{
"siteId": "clxyz123",
"categoryId": "clxyz500",
"name": "Overgrip Wilson",
"description": "Pack de 3 overgrips",
"sku": "GRIP-WILSON-001",
"price": 8.00,
"costPrice": 5.50,
"stock": 30,
"minStock": 5,
"trackStock": true
}
```
### Actualizar Stock
```http
PATCH /api/products/{id}/stock
```
**Cuerpo de la Solicitud:**
```json
{
"adjustment": 10,
"reason": "Reposicion de inventario"
}
```
### Listar Categorias
```http
GET /api/products/categories
```
---
## Ventas (Sales)
### Listar Ventas
```http
GET /api/sales
```
**Autenticacion:** Requerida
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `siteId` | string | Filtrar por sede |
| `startDate` | string | Fecha inicio (YYYY-MM-DD) |
| `endDate` | string | Fecha fin (YYYY-MM-DD) |
| `clientId` | string | Filtrar por cliente |
| `createdBy` | string | Filtrar por vendedor |
**Respuesta Exitosa (200):**
```json
[
{
"id": "clxyz600",
"subtotal": 25.00,
"discount": 0.00,
"tax": 5.25,
"total": 30.25,
"paymentType": "CASH",
"notes": null,
"createdAt": "2024-01-15T14:30:00.000Z",
"items": [
{
"id": "clxyz601",
"quantity": 2,
"unitPrice": 6.50,
"subtotal": 13.00,
"product": {
"id": "clxyz400",
"name": "Pelotas HEAD Pro",
"sku": "BALL-HEAD-001"
}
}
],
"payments": [
{
"id": "clxyz602",
"amount": 30.25,
"paymentType": "CASH",
"reference": null
}
],
"client": null,
"createdBy": {
"id": "clxyz700",
"firstName": "Ana",
"lastName": "Martinez"
},
"cashRegister": {
"id": "clxyz800",
"site": {
"id": "clxyz123",
"name": "Padel Club Centro"
}
}
}
]
```
### Crear Venta
```http
POST /api/sales
```
**Cuerpo de la Solicitud:**
```json
{
"siteId": "clxyz123",
"clientId": "clxyz100",
"items": [
{
"productId": "clxyz400",
"quantity": 2,
"price": 6.50
},
{
"productId": "clxyz401",
"quantity": 1,
"price": 12.00
}
],
"payments": [
{
"amount": 25.00,
"method": "CASH"
}
],
"discount": 0,
"tax": 0,
"notes": "Venta rapida"
}
```
**Nota:** Requiere caja registradora abierta si hay pago en efectivo.
---
## Caja Registradora (Cash Register)
### Obtener Estado de Caja
```http
GET /api/cash-register
```
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `siteId` | string | Sede |
| `status` | string | `open`, `closed`, o `all` |
| `date` | string | Fecha especifica (YYYY-MM-DD) |
**Respuesta Exitosa (200) - Caja Abierta:**
```json
{
"id": "clxyz800",
"openingAmount": 100.00,
"closingAmount": null,
"expectedAmount": null,
"difference": null,
"openedAt": "2024-01-15T08:00:00.000Z",
"closedAt": null,
"site": {
"id": "clxyz123",
"name": "Padel Club Centro"
},
"user": {
"id": "clxyz700",
"firstName": "Ana",
"lastName": "Martinez"
},
"paymentBreakdown": {
"CASH": 250.00,
"CARD": 180.00
},
"totalCashSales": 250.00,
"expectedAmount": 350.00,
"_count": {
"sales": 12,
"payments": 15
}
}
```
### Abrir Caja
```http
POST /api/cash-register
```
**Cuerpo de la Solicitud:**
```json
{
"siteId": "clxyz123",
"openingAmount": 100.00
}
```
### Cerrar Caja
```http
PUT /api/cash-register/{id}
```
**Cuerpo de la Solicitud:**
```json
{
"closingAmount": 345.50,
"notes": "Cuadre correcto"
}
```
### Obtener Transacciones
```http
GET /api/cash-register/{id}/transactions
```
---
## Torneos (Tournaments)
### Listar Torneos
```http
GET /api/tournaments
```
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `siteId` | string | Filtrar por sede |
| `status` | string | DRAFT, REGISTRATION_OPEN, IN_PROGRESS, COMPLETED, CANCELLED |
| `startDate` | string | Fecha inicio del rango |
| `endDate` | string | Fecha fin del rango |
**Respuesta Exitosa (200):**
```json
[
{
"id": "clxyz900",
"name": "Torneo de Verano 2024",
"description": "Torneo amateur categoria B",
"type": "BRACKET",
"status": "REGISTRATION_OPEN",
"startDate": "2024-07-15T09:00:00.000Z",
"endDate": "2024-07-16T20:00:00.000Z",
"maxPlayers": 32,
"entryFee": 25.00,
"site": {
"id": "clxyz123",
"name": "Padel Club Centro"
},
"_count": {
"inscriptions": 18,
"matches": 0
}
}
]
```
### Crear Torneo
```http
POST /api/tournaments
```
**Roles permitidos:** `SUPER_ADMIN`, `ORG_ADMIN`, `SITE_ADMIN`
**Cuerpo de la Solicitud:**
```json
{
"siteId": "clxyz123",
"name": "Torneo de Primavera",
"description": "Torneo para socios",
"date": "2024-04-20T09:00:00.000Z",
"endDate": "2024-04-21T20:00:00.000Z",
"type": "SINGLE_ELIMINATION",
"category": "B",
"maxTeams": 16,
"price": 20.00
}
```
### Obtener Torneo por ID
```http
GET /api/tournaments/{id}
```
### Actualizar Torneo
```http
PUT /api/tournaments/{id}
```
### Listar Inscripciones
```http
GET /api/tournaments/{id}/inscriptions
```
**Respuesta Exitosa (200):**
```json
[
{
"id": "clxyzabc",
"teamName": "Los Campeones",
"seedNumber": 1,
"isPaid": true,
"paidAmount": 25.00,
"registeredAt": "2024-01-10T12:00:00.000Z",
"client": {
"id": "clxyz100",
"firstName": "Juan",
"lastName": "Garcia",
"level": "ADVANCED"
},
"partner": {
"id": "clxyz101",
"firstName": "Pedro",
"lastName": "Martinez",
"level": "ADVANCED"
}
}
]
```
### Inscribir Equipo
```http
POST /api/tournaments/{id}/inscriptions
```
**Cuerpo de la Solicitud:**
```json
{
"player1Id": "clxyz100",
"player2Id": "clxyz101",
"teamName": "Los Campeones"
}
```
### Generar Bracket
```http
POST /api/tournaments/{id}/generate-bracket
```
### Actualizar Resultado de Partido
```http
PUT /api/tournaments/{id}/matches/{matchId}
```
**Cuerpo de la Solicitud:**
```json
{
"score1": "6-4",
"score2": "6-3",
"winnerId": "clxyzabc"
}
```
---
## Planes de Membresia (Membership Plans)
### Listar Planes
```http
GET /api/membership-plans
```
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `includeInactive` | boolean | Incluir planes inactivos |
**Respuesta Exitosa (200):**
```json
[
{
"id": "clxyz300",
"name": "Plan Premium",
"description": "Acceso completo con beneficios exclusivos",
"price": 99.00,
"durationMonths": 1,
"courtHours": 10,
"discountPercent": 20,
"benefits": ["Reserva anticipada", "Descuento en tienda"],
"isActive": true,
"subscriberCount": 45,
"benefitsSummary": {
"freeHours": 10,
"bookingDiscount": 20,
"extraBenefits": ["Reserva anticipada", "Descuento en tienda"]
}
}
]
```
### Crear Plan
```http
POST /api/membership-plans
```
**Roles permitidos:** `SUPER_ADMIN`, `ORG_ADMIN`
**Cuerpo de la Solicitud:**
```json
{
"name": "Plan Basico",
"description": "Plan de entrada",
"price": 29.00,
"durationMonths": 1,
"freeHours": 2,
"bookingDiscount": 10,
"storeDiscount": 5,
"extraBenefits": ["Newsletter exclusivo"]
}
```
### Actualizar Plan
```http
PUT /api/membership-plans/{id}
```
---
## Membresias (Memberships)
### Listar Membresias
```http
GET /api/memberships
```
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `status` | string | ACTIVE, EXPIRED, CANCELLED, SUSPENDED |
| `planId` | string | Filtrar por plan |
| `search` | string | Buscar por nombre/email del cliente |
| `expiring` | boolean | Solo proximas a vencer (7 dias) |
| `limit` | number | Limite de resultados |
| `offset` | number | Desplazamiento |
**Respuesta Exitosa (200):**
```json
{
"data": [
{
"id": "clxyz200",
"startDate": "2024-01-01T00:00:00.000Z",
"endDate": "2024-06-01T00:00:00.000Z",
"status": "ACTIVE",
"remainingHours": 8,
"autoRenew": true,
"notes": null,
"plan": {
"id": "clxyz300",
"name": "Plan Premium",
"price": 99.00,
"durationMonths": 1,
"courtHours": 10,
"discountPercent": 20
},
"client": {
"id": "clxyz100",
"firstName": "Juan",
"lastName": "Garcia",
"email": "juan@email.com"
},
"isExpiring": false,
"daysUntilExpiry": 45,
"benefitsSummary": {
"freeHours": 10,
"hoursRemaining": 8,
"bookingDiscount": 20,
"extraBenefits": []
}
}
],
"pagination": {
"total": 150,
"limit": 50,
"offset": 0,
"hasMore": true
}
}
```
### Crear Membresia
```http
POST /api/memberships
```
**Roles permitidos:** `SUPER_ADMIN`, `ORG_ADMIN`, `SITE_ADMIN`, `RECEPTIONIST`
**Cuerpo de la Solicitud:**
```json
{
"clientId": "clxyz100",
"planId": "clxyz300",
"startDate": "2024-02-01T00:00:00.000Z",
"endDate": "2024-03-01T00:00:00.000Z",
"autoRenew": true,
"notes": "Primera membresia del cliente"
}
```
### Renovar Membresia
```http
POST /api/memberships/{id}/renew
```
**Cuerpo de la Solicitud:**
```json
{
"planId": "clxyz300",
"paymentMethod": "CARD"
}
```
---
## Dashboard
### Obtener Estadisticas
```http
GET /api/dashboard/stats
```
**Parametros de Query:**
| Parametro | Tipo | Descripcion |
|-----------|------|-------------|
| `siteId` | string | Filtrar por sede |
| `date` | string | Fecha especifica (YYYY-MM-DD) |
**Respuesta Exitosa (200):**
```json
{
"stats": {
"todayBookings": 24,
"todayRevenue": 580.50,
"occupancyRate": 75,
"activeMembers": 145,
"pendingBookings": 3,
"upcomingTournaments": 2
},
"courtOccupancy": [
{
"courtId": "clxyz456",
"courtName": "Cancha 1",
"availableHours": 14,
"bookedHours": 11.5,
"occupancyPercent": 82
}
],
"recentBookings": [
{
"id": "clxyz001",
"startTime": "2024-01-15T10:00:00.000Z",
"endTime": "2024-01-15T11:30:00.000Z",
"status": "CONFIRMED",
"court": {
"id": "clxyz456",
"name": "Cancha 1"
},
"client": {
"id": "clxyz100",
"name": "Juan Garcia"
}
}
],
"date": "2024-01-15"
}
```
---
## Errores Comunes
### Error de Autenticacion
```json
{
"error": "No autorizado"
}
```
**Codigo:** 401
### Error de Permisos
```json
{
"error": "Forbidden: Insufficient permissions"
}
```
**Codigo:** 403
### Recurso No Encontrado
```json
{
"error": "Court not found"
}
```
**Codigo:** 404
### Conflicto
```json
{
"error": "Ya existe una reserva en ese horario"
}
```
**Codigo:** 409
### Error de Validacion
```json
{
"error": "Invalid booking data",
"details": {
"courtId": ["Invalid court ID"],
"startTime": ["Required"]
}
}
```
**Codigo:** 400
---
## Notas Adicionales
### Paginacion
Los endpoints que devuelven listas soportan paginacion mediante:
- `limit`: Numero maximo de resultados (default: 50, max: 100)
- `offset`: Numero de registros a saltar
La respuesta incluye metadatos de paginacion:
```json
{
"data": [...],
"pagination": {
"total": 150,
"limit": 50,
"offset": 0,
"hasMore": true
}
}
```
### Filtrado por Sede
La mayoria de endpoints filtran automaticamente por la sede del usuario si tiene una asignada. Para usuarios con acceso a multiples sedes, se puede especificar `siteId` en los parametros de query.
### Zonas Horarias
Las fechas se manejan en UTC. Cada sede tiene configurada su zona horaria para mostrar correctamente los horarios de apertura/cierre.