680 lines
11 KiB
Markdown
680 lines
11 KiB
Markdown
|
|
# 🔌 ESPECIFICACIÓN API REST - SERVIDOR DE CONTENIDO TES
|
||
|
|
|
||
|
|
**Versión:** 1.0.0
|
||
|
|
**Fecha:** 2025-01-06
|
||
|
|
**Base URL:** `https://servidor-tes.com/api` (ejemplo)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📋 ÍNDICE
|
||
|
|
|
||
|
|
1. [API Pública (App PWA)](#api-pública-app-pwa)
|
||
|
|
2. [API Admin (Panel Admin)](#api-admin-panel-admin)
|
||
|
|
3. [Códigos de Estado](#códigos-de-estado)
|
||
|
|
4. [Formato de Respuestas](#formato-de-respuestas)
|
||
|
|
5. [Rate Limiting](#rate-limiting)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
# API PÚBLICA (APP PWA)
|
||
|
|
|
||
|
|
## Endpoints de Solo Lectura
|
||
|
|
|
||
|
|
### 1. Health Check
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/health
|
||
|
|
```
|
||
|
|
|
||
|
|
**Descripción:** Verifica estado del servidor y base de datos.
|
||
|
|
|
||
|
|
**Autenticación:** No requerida
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "ok",
|
||
|
|
"timestamp": "2025-01-06T12:00:00Z",
|
||
|
|
"database": "connected",
|
||
|
|
"latest_pack_version": "1.0.0",
|
||
|
|
"latest_pack_hash": "sha256:abc123...",
|
||
|
|
"server_version": "1.0.0"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 503 (si BD desconectada):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "error",
|
||
|
|
"timestamp": "2025-01-06T12:00:00Z",
|
||
|
|
"database": "disconnected",
|
||
|
|
"message": "Base de datos no disponible"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. Content Pack Latest
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/content/pack/latest
|
||
|
|
```
|
||
|
|
|
||
|
|
**Descripción:** Obtiene el Content Pack más reciente (solo contenido publicado).
|
||
|
|
|
||
|
|
**Autenticación:** No requerida
|
||
|
|
|
||
|
|
**Headers:**
|
||
|
|
- `If-None-Match: <hash>` - Si el hash coincide, retorna 304 Not Modified
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"metadata": {
|
||
|
|
"version": "1.0.0",
|
||
|
|
"generated_at": "2025-01-06T12:00:00Z",
|
||
|
|
"hash": "sha256:abc123...",
|
||
|
|
"total_items": 45,
|
||
|
|
"total_resources": 120
|
||
|
|
},
|
||
|
|
"content": { ... },
|
||
|
|
"media": { ... },
|
||
|
|
"links": { ... }
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Headers de Respuesta:**
|
||
|
|
- `ETag: <hash>` - Hash del pack
|
||
|
|
- `Content-Type: application/json`
|
||
|
|
- `Cache-Control: public, max-age=3600` - Cache 1 hora
|
||
|
|
|
||
|
|
**Respuesta 304:** Not Modified (si `If-None-Match` coincide)
|
||
|
|
|
||
|
|
**Respuesta 404:** No hay pack disponible
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. Content Pack por Versión
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/content/pack/:version
|
||
|
|
```
|
||
|
|
|
||
|
|
**Descripción:** Obtiene un Content Pack específico por versión.
|
||
|
|
|
||
|
|
**Parámetros:**
|
||
|
|
- `version` (path) - Versión semántica (ej: "1.0.0")
|
||
|
|
|
||
|
|
**Ejemplo:**
|
||
|
|
```
|
||
|
|
GET /api/content/pack/1.0.0
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 200:** Igual que `/pack/latest`
|
||
|
|
|
||
|
|
**Respuesta 404:** Versión no encontrada
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. Protocolo Individual
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/content/protocol/:slug
|
||
|
|
```
|
||
|
|
|
||
|
|
**Descripción:** Obtiene un protocolo específico con sus recursos asociados.
|
||
|
|
|
||
|
|
**Parámetros:**
|
||
|
|
- `slug` (path) - Slug del protocolo (ej: "rcp-adulto-svb")
|
||
|
|
|
||
|
|
**Ejemplo:**
|
||
|
|
```
|
||
|
|
GET /api/content/protocol/rcp-adulto-svb
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"type": "protocol",
|
||
|
|
"slug": "rcp-adulto-svb",
|
||
|
|
"title": "RCP Adulto - Soporte Vital Básico",
|
||
|
|
"short_title": "RCP Adulto SVB",
|
||
|
|
"description": "Protocolo de reanimación cardiopulmonar básica en adultos",
|
||
|
|
"clinical_context": "RCP",
|
||
|
|
"level": "operativo",
|
||
|
|
"priority": "critica",
|
||
|
|
"status": "published",
|
||
|
|
"version": "1.0.0",
|
||
|
|
"content": {
|
||
|
|
"steps": [...],
|
||
|
|
"checklist": {...},
|
||
|
|
"warnings": [...],
|
||
|
|
"key_points": [...]
|
||
|
|
},
|
||
|
|
"resources": {
|
||
|
|
"images": [
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"title": "Posición de Manos - RCP Adulto",
|
||
|
|
"url": "https://servidor-tes.com/media/images/rcp/...",
|
||
|
|
"alt_text": "...",
|
||
|
|
"section": "pasos",
|
||
|
|
"position": 7
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"videos": [
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"title": "RCP Adulto SVB - Técnica Completa",
|
||
|
|
"url": "https://servidor-tes.com/media/videos/rcp/...",
|
||
|
|
"duration_seconds": 45,
|
||
|
|
"section": "pasos",
|
||
|
|
"position": 0
|
||
|
|
}
|
||
|
|
]
|
||
|
|
},
|
||
|
|
"related": {
|
||
|
|
"guide_id": "550e8400-...",
|
||
|
|
"guide_slug": "rcp-adulto-svb-formativo",
|
||
|
|
"manual_ids": ["..."],
|
||
|
|
"related_protocol_ids": ["..."]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 404:** Protocolo no encontrado o no publicado
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 5. Guía Individual
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/content/guide/:slug
|
||
|
|
```
|
||
|
|
|
||
|
|
**Descripción:** Obtiene una guía formativa completa con sus 8 secciones.
|
||
|
|
|
||
|
|
**Parámetros:**
|
||
|
|
- `slug` (path) - Slug de la guía (ej: "abcde-operativo")
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"type": "guide",
|
||
|
|
"slug": "abcde-operativo",
|
||
|
|
"title": "ABCDE Operativo",
|
||
|
|
"level": "formativo",
|
||
|
|
"content": {
|
||
|
|
"sections": [
|
||
|
|
{
|
||
|
|
"numero": 1,
|
||
|
|
"titulo": "Introducción y Contexto",
|
||
|
|
"markdown": "# Introducción...",
|
||
|
|
"resources": {
|
||
|
|
"images": [...],
|
||
|
|
"videos": [...]
|
||
|
|
}
|
||
|
|
},
|
||
|
|
// ... 7 secciones más
|
||
|
|
],
|
||
|
|
"related_protocol_id": "...",
|
||
|
|
"related_manual_ids": [...]
|
||
|
|
},
|
||
|
|
"resources": {
|
||
|
|
"images": [...],
|
||
|
|
"videos": [...]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 6. Recurso Multimedia
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/content/media/:id
|
||
|
|
```
|
||
|
|
|
||
|
|
**Descripción:** Obtiene información de un recurso multimedia o redirige al archivo.
|
||
|
|
|
||
|
|
**Parámetros:**
|
||
|
|
- `id` (path) - UUID del recurso
|
||
|
|
|
||
|
|
**Query Params:**
|
||
|
|
- `download` (boolean) - Si true, fuerza descarga
|
||
|
|
|
||
|
|
**Respuesta 200 (JSON si no download):**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"type": "image",
|
||
|
|
"title": "Posición de Manos - RCP Adulto",
|
||
|
|
"url": "https://servidor-tes.com/media/images/rcp/rcp_posicion_manos_adulto.png",
|
||
|
|
"alt_text": "...",
|
||
|
|
"width": 1200,
|
||
|
|
"height": 800,
|
||
|
|
"file_size": 245678
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 302 (Redirect si download):**
|
||
|
|
- Redirige a URL del archivo
|
||
|
|
|
||
|
|
**Respuesta 404:** Recurso no encontrado
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
# API ADMIN (PANEL ADMIN)
|
||
|
|
|
||
|
|
## Autenticación
|
||
|
|
|
||
|
|
### Login
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /api/admin/auth/login
|
||
|
|
```
|
||
|
|
|
||
|
|
**Body:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"email": "admin@emerges-tes.local",
|
||
|
|
"password": "Admin123!"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||
|
|
"user": {
|
||
|
|
"id": "...",
|
||
|
|
"email": "admin@emerges-tes.local",
|
||
|
|
"username": "admin",
|
||
|
|
"role": "super_admin"
|
||
|
|
},
|
||
|
|
"expiresIn": 86400
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 401:** Credenciales inválidas
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Me (Usuario Actual)
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/admin/auth/me
|
||
|
|
```
|
||
|
|
|
||
|
|
**Autenticación:** Requerida (Bearer Token)
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"user": {
|
||
|
|
"id": "...",
|
||
|
|
"email": "...",
|
||
|
|
"username": "...",
|
||
|
|
"role": "...",
|
||
|
|
"is_active": true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Contenido
|
||
|
|
|
||
|
|
### Listar Contenido
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/admin/content
|
||
|
|
```
|
||
|
|
|
||
|
|
**Query Params:**
|
||
|
|
- `type` - Filtrar por tipo (protocol, guide, manual, drug, checklist)
|
||
|
|
- `status` - Filtrar por estado (draft, in_review, approved, published)
|
||
|
|
- `priority` - Filtrar por prioridad (critica, alta, media, baja)
|
||
|
|
- `search` - Búsqueda de texto
|
||
|
|
- `page` - Número de página (default: 1)
|
||
|
|
- `limit` - Items por página (default: 20)
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"items": [...],
|
||
|
|
"total": 45,
|
||
|
|
"page": 1,
|
||
|
|
"limit": 20,
|
||
|
|
"totalPages": 3
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Obtener Contenido
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/admin/content/:id
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 200:** ContentItem completo
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Crear Contenido
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /api/admin/content
|
||
|
|
```
|
||
|
|
|
||
|
|
**Body:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"type": "protocol",
|
||
|
|
"slug": "rcp-adulto-svb",
|
||
|
|
"title": "RCP Adulto - Soporte Vital Básico",
|
||
|
|
"clinical_context": "RCP",
|
||
|
|
"level": "operativo",
|
||
|
|
"priority": "critica",
|
||
|
|
"source_guideline": "ERC",
|
||
|
|
"content": {
|
||
|
|
"steps": [...],
|
||
|
|
"checklist": {...}
|
||
|
|
},
|
||
|
|
"tags": ["rcp", "svb", "adulto"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 201:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"message": "Contenido creado correctamente"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Actualizar Contenido
|
||
|
|
|
||
|
|
```
|
||
|
|
PUT /api/admin/content/:id
|
||
|
|
```
|
||
|
|
|
||
|
|
**Body:** Mismo formato que crear
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"message": "Contenido actualizado correctamente",
|
||
|
|
"version": "1.1.0"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Publicar Contenido
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /api/admin/content/:id/publish
|
||
|
|
```
|
||
|
|
|
||
|
|
**Descripción:** Cambia status a 'published' y genera nueva versión.
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"status": "published",
|
||
|
|
"version": "1.0.0",
|
||
|
|
"message": "Contenido publicado correctamente"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Solicitar Validación
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /api/admin/content/:id/validate
|
||
|
|
```
|
||
|
|
|
||
|
|
**Body:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"comments": "Revisado y aprobado",
|
||
|
|
"validator_role": "tes"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"status": "approved",
|
||
|
|
"validated_by": "...",
|
||
|
|
"validated_at": "2025-01-06T12:00:00Z"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Recursos Multimedia
|
||
|
|
|
||
|
|
### Listar Recursos
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/admin/media
|
||
|
|
```
|
||
|
|
|
||
|
|
**Query Params:**
|
||
|
|
- `type` - image | video
|
||
|
|
- `status` - draft | approved | published
|
||
|
|
- `block` - Filtrar por bloque
|
||
|
|
- `search` - Búsqueda
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Upload Recurso
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /api/admin/media/upload
|
||
|
|
```
|
||
|
|
|
||
|
|
**Content-Type:** `multipart/form-data`
|
||
|
|
|
||
|
|
**Body:**
|
||
|
|
- `file` - Archivo (imagen o vídeo)
|
||
|
|
- `title` - Título
|
||
|
|
- `alt_text` - Texto alternativo
|
||
|
|
- `description` - Descripción
|
||
|
|
- `tags` - Tags (JSON array)
|
||
|
|
- `block` - Bloque temático
|
||
|
|
- `priority` - critica | alta | media | baja
|
||
|
|
- `usage_type` - operativo | formativo | referencia (puede ser array)
|
||
|
|
|
||
|
|
**Respuesta 201:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "...",
|
||
|
|
"filename": "rcp_posicion_manos_adulto.png",
|
||
|
|
"path": "/media/images/rcp/rcp_posicion_manos_adulto.png",
|
||
|
|
"url": "https://servidor-tes.com/media/images/rcp/...",
|
||
|
|
"file_size": 245678,
|
||
|
|
"message": "Recurso subido correctamente"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Asociar Recurso a Contenido
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /api/admin/media/:id/associate
|
||
|
|
```
|
||
|
|
|
||
|
|
**Body:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"content_item_id": "...",
|
||
|
|
"section": "pasos",
|
||
|
|
"position": 7,
|
||
|
|
"placement": "inline",
|
||
|
|
"caption": "Posición correcta de manos",
|
||
|
|
"is_critical": true,
|
||
|
|
"priority": "critica"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Content Pack
|
||
|
|
|
||
|
|
### Generar Pack
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /api/admin/pack/generate
|
||
|
|
```
|
||
|
|
|
||
|
|
**Body:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"version": "1.0.0",
|
||
|
|
"include_draft": false,
|
||
|
|
"notes": "Primera versión del pack"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"version": "1.0.0",
|
||
|
|
"file_path": "/storage/packs/pack-v1.0.0.json",
|
||
|
|
"file_url": "https://servidor-tes.com/api/content/pack/1.0.0",
|
||
|
|
"hash": "sha256:abc123...",
|
||
|
|
"size_bytes": 5242880,
|
||
|
|
"total_items": 45,
|
||
|
|
"total_resources": 120,
|
||
|
|
"generated_at": "2025-01-06T12:00:00Z"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Listar Versiones
|
||
|
|
|
||
|
|
```
|
||
|
|
GET /api/admin/pack/versions
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respuesta 200:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"versions": [
|
||
|
|
{
|
||
|
|
"version": "1.0.0",
|
||
|
|
"generated_at": "2025-01-06T12:00:00Z",
|
||
|
|
"size_bytes": 5242880,
|
||
|
|
"total_items": 45,
|
||
|
|
"is_latest": true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"version": "0.9.0",
|
||
|
|
"generated_at": "2025-01-05T10:00:00Z",
|
||
|
|
"size_bytes": 4890123,
|
||
|
|
"total_items": 42,
|
||
|
|
"is_latest": false
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
# CÓDIGOS DE ESTADO
|
||
|
|
|
||
|
|
| Código | Significado | Uso |
|
||
|
|
|--------|-------------|-----|
|
||
|
|
| 200 | OK | Operación exitosa |
|
||
|
|
| 201 | Created | Recurso creado |
|
||
|
|
| 304 | Not Modified | Pack sin cambios (If-None-Match) |
|
||
|
|
| 400 | Bad Request | Datos inválidos |
|
||
|
|
| 401 | Unauthorized | No autenticado o token inválido |
|
||
|
|
| 403 | Forbidden | Sin permisos |
|
||
|
|
| 404 | Not Found | Recurso no encontrado |
|
||
|
|
| 409 | Conflict | Conflicto (ej: slug duplicado) |
|
||
|
|
| 422 | Unprocessable Entity | Validación fallida |
|
||
|
|
| 429 | Too Many Requests | Rate limit excedido |
|
||
|
|
| 500 | Internal Server Error | Error del servidor |
|
||
|
|
| 503 | Service Unavailable | Servicio no disponible (BD desconectada) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
# FORMATO DE RESPUESTAS
|
||
|
|
|
||
|
|
## Respuesta Exitosa
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"data": { ... },
|
||
|
|
"meta": {
|
||
|
|
"timestamp": "2025-01-06T12:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Respuesta de Error
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": {
|
||
|
|
"code": "CONTENT_NOT_FOUND",
|
||
|
|
"message": "Contenido no encontrado",
|
||
|
|
"details": {
|
||
|
|
"id": "...",
|
||
|
|
"slug": "rcp-adulto-svb"
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"meta": {
|
||
|
|
"timestamp": "2025-01-06T12:00:00Z"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
# RATE LIMITING
|
||
|
|
|
||
|
|
## API Pública (App)
|
||
|
|
|
||
|
|
- **Límite:** 100 requests/minuto por IP
|
||
|
|
- **Headers:**
|
||
|
|
- `X-RateLimit-Limit: 100`
|
||
|
|
- `X-RateLimit-Remaining: 95`
|
||
|
|
- `X-RateLimit-Reset: 1641475200`
|
||
|
|
|
||
|
|
## API Admin
|
||
|
|
|
||
|
|
- **Límite:** 50 requests/minuto por usuario
|
||
|
|
- **Headers:** Mismo formato
|
||
|
|
|
||
|
|
**Respuesta 429:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"error": {
|
||
|
|
"code": "RATE_LIMIT_EXCEEDED",
|
||
|
|
"message": "Demasiadas solicitudes. Intenta de nuevo en 60 segundos."
|
||
|
|
},
|
||
|
|
"retryAfter": 60
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Fin de la Especificación**
|
||
|
|
|