# Convenciones de la API

## 1. Objetivo

Este documento define el contrato y las convenciones que deben cumplir todos los endpoints de la API de **Promotion Management System**.

Su objetivo es garantizar:

- Respuestas JSON uniformes.
- Manejo consistente de errores.
- Facilidad de consumo desde React.
- Seguridad en la exposición de información.
- Compatibilidad entre módulos.
- Facilidad de mantenimiento.
- Escalabilidad de la API.
- Documentación predecible.
- Pruebas automatizadas consistentes.

Todas las rutas de la API deben cumplir estas reglas, salvo que exista una excepción técnica documentada y aprobada.

---

## 2. Versionado

Todos los endpoints propios de la aplicación deben estar versionados.

Prefijo actual:

```text
/api/v1
```

Ejemplos:

```text
GET  /api/v1/auth/me
POST /api/v1/auth/login
GET  /api/v1/public/pharmacies/by-code/{code}
```

No agregar endpoints propios directamente bajo `/api` sin una versión.

Los endpoints oficiales proporcionados por Laravel o Sanctum pueden conservar sus rutas originales cuando sea necesario.

Ejemplo:

```text
GET /sanctum/csrf-cookie
```

---

## 3. Formato de contenido

La API debe recibir y devolver JSON, salvo endpoints destinados expresamente a:

- Descargar archivos.
- Exportar reportes.
- Mostrar archivos.
- Recibir archivos mediante `multipart/form-data`.

Encabezados recomendados:

```http
Accept: application/json
Content-Type: application/json
```

Para solicitudes autenticadas desde la SPA React:

```http
Accept: application/json
X-XSRF-TOKEN: <token-csrf>
Cookie: <cookie-de-sesion>
```

El frontend debe enviar las solicitudes con credenciales habilitadas.

---

## 4. Infraestructura centralizada de respuestas

Todas las respuestas JSON deben generarse mediante una infraestructura centralizada.

Clase sugerida:

```text
App\Support\ApiResponse
```

No construir manualmente la estructura completa en cada controlador.

Ejemplo que debe evitarse:

```php
return response()->json([
    'success' => true,
    'message' => 'Operación realizada.',
    'data' => $data,
]);
```

Uso esperado:

```php
return ApiResponse::success(
    data: $data,
    message: 'Operación realizada correctamente.',
);
```

La infraestructura debe permitir:

- Respuestas exitosas.
- Recursos creados.
- Respuestas sin contenido relevante.
- Metadatos.
- Errores generales.
- Errores asociados a campos.
- Códigos HTTP personalizados.
- Códigos internos de error.
- Datos provenientes de arrays, DTOs y API Resources.

Firmas orientativas:

```php
ApiResponse::success(
    mixed $data = [],
    string $message = 'Operación realizada correctamente.',
    int $status = 200,
    array $meta = [],
): JsonResponse;
```

```php
ApiResponse::created(
    mixed $data = [],
    string $message = 'Recurso creado correctamente.',
): JsonResponse;
```

```php
ApiResponse::error(
    string $message,
    array $errors,
    string $errorCode,
    int $status,
): JsonResponse;
```

Las firmas concretas pueden cambiar, pero deben mantenerse consistentes en todo el proyecto.

---

## 5. Respuesta exitosa estándar

Toda respuesta exitosa debe utilizar la siguiente estructura:

```json
{
    "success": true,
    "message": "Operación realizada correctamente.",
    "data": {}
}
```

Reglas:

- `success` siempre debe ser booleano.
- En respuestas exitosas, `success` debe ser `true`.
- `message` siempre debe ser string.
- `data` siempre debe estar presente.
- `data` puede contener un objeto, una colección o un valor simple justificado.
- No agregar propiedades arbitrarias fuera de la estructura definida.
- No devolver modelos Eloquent directamente.
- No exponer campos internos o sensibles.

Ejemplo con un recurso:

```json
{
    "success": true,
    "message": "Usuario autenticado.",
    "data": {
        "user": {
            "id": "0f38e5cb-f915-45bc-b669-f42b33c2d589",
            "first_name": "Albert",
            "last_name": "Martínez",
            "username": "albertmat34",
            "email": "albert@example.com"
        }
    }
}
```

Ejemplo con una colección:

```json
{
    "success": true,
    "message": "Promociones obtenidas correctamente.",
    "data": [
        {
            "id": "1e8ce625-4918-4aa4-99e2-6349f3c670c1",
            "name": "Rifa de motor",
            "status": "active"
        }
    ]
}
```

---

## 6. Respuestas sin datos relevantes

Cuando una operación sea exitosa, pero no necesite devolver información adicional, utilizar:

```json
{
    "success": true,
    "message": "Sesión cerrada correctamente.",
    "data": {}
}
```

Para mantener un contrato uniforme, se prefiere una respuesta HTTP `200` con la estructura estándar sobre una respuesta `204`, salvo que exista una razón técnica para no devolver contenido.

No utilizar `null`, `{}` y `[]` de forma arbitraria para el mismo tipo de operación.

Convención:

- Recurso único sin valor: `{}`.
- Colección vacía: `[]`.
- Campo opcional dentro de un recurso: `null`.

---

## 7. Respuestas con metadatos

Cuando una respuesta necesite metadatos, utilizar la propiedad `meta` al mismo nivel que `data`.

```json
{
    "success": true,
    "message": "Consulta realizada correctamente.",
    "data": [],
    "meta": {
        "current_page": 1,
        "per_page": 15,
        "total": 100,
        "last_page": 7
    }
}
```

Reglas:

- `meta` es opcional.
- `meta` no debe estar dentro de cada elemento de `data`.
- No incluir metadatos irrelevantes.
- Los nombres deben mantenerse iguales entre todos los endpoints paginados.

---

## 8. Paginación

Los endpoints que devuelvan colecciones potencialmente grandes deben utilizar paginación.

Parámetros estándar:

```text
?page=1
?per_page=15
```

Ejemplo:

```text
GET /api/v1/admin/promotions?page=2&per_page=25
```

Respuesta esperada:

```json
{
    "success": true,
    "message": "Promociones obtenidas correctamente.",
    "data": [
        {
            "id": "1e8ce625-4918-4aa4-99e2-6349f3c670c1",
            "name": "Rifa de motor",
            "status": "active"
        }
    ],
    "meta": {
        "current_page": 2,
        "per_page": 25,
        "total": 80,
        "last_page": 4,
        "from": 26,
        "to": 50
    }
}
```

El valor de `per_page` debe tener:

- Un valor predeterminado.
- Un valor mínimo.
- Un límite máximo.

Valor inicial recomendado:

```text
Predeterminado: 15
Mínimo: 1
Máximo: 100
```

No permitir que el consumidor solicite cantidades ilimitadas.

---

## 9. Respuesta de error estándar

Toda respuesta de error controlado debe utilizar:

```json
{
    "success": false,
    "message": "No fue posible completar la operación.",
    "errors": {},
    "error_code": "ERROR_CODE"
}
```

Reglas:

- `success` siempre debe ser `false`.
- `message` siempre debe ser string.
- `errors` siempre debe estar presente.
- `error_code` siempre debe estar presente en errores controlados.
- No incluir `data`.
- No devolver detalles técnicos sensibles.
- Los códigos internos deben escribirse en inglés.
- Los códigos internos deben utilizar `UPPER_SNAKE_CASE`.

---

## 10. Errores asociados a campos

Los errores de validación o conflictos asociados a campos deben utilizar arrays de mensajes.

```json
{
    "success": false,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "email": [
            "El correo electrónico no es válido."
        ],
        "password": [
            "La contraseña debe contener al menos ocho caracteres."
        ]
    },
    "error_code": "VALIDATION_ERROR"
}
```

No devolver el error de un campo como string simple:

```json
{
    "errors": {
        "email": "El correo electrónico no es válido."
    }
}
```

Siempre utilizar:

```json
{
    "errors": {
        "email": [
            "El correo electrónico no es válido."
        ]
    }
}
```

Esto permite que un campo contenga múltiples errores.

---

## 11. Errores generales sin campos

Cuando un error no corresponda a un campo específico, `errors` debe ser un objeto vacío:

```json
{
    "success": false,
    "message": "No autenticado.",
    "errors": {},
    "error_code": "UNAUTHENTICATED"
}
```

No utilizar:

```json
"errors": null
```

No utilizar:

```json
"errors": []
```

Para errores generales se debe mantener:

```json
"errors": {}
```

---

## 12. Códigos HTTP

Utilizar códigos HTTP acordes al resultado real de la operación.

| Código | Uso |
|---|---|
| `200` | Consulta, actualización o acción exitosa |
| `201` | Recurso creado |
| `202` | Proceso asíncrono aceptado |
| `204` | Operación exitosa sin cuerpo, solamente cuando esté justificado |
| `400` | Solicitud incorrecta que no corresponde a validación de campos |
| `401` | No autenticado o credenciales inválidas |
| `403` | Usuario autenticado sin autorización |
| `404` | Recurso inexistente |
| `405` | Método HTTP no permitido |
| `409` | Conflicto de estado, integridad, idempotencia o concurrencia |
| `422` | Error de validación o conflicto asociado a campos |
| `429` | Demasiadas solicitudes |
| `500` | Error inesperado |
| `503` | Servicio temporalmente no disponible |

No retornar HTTP `200` para representar errores.

---

## 13. Códigos internos de error

Los códigos internos permiten que React identifique el tipo de error sin depender del texto del mensaje.

Reglas:

- Deben escribirse en inglés.
- Deben utilizar `UPPER_SNAKE_CASE`.
- Deben ser estables.
- No deben cambiar sin una razón justificada.
- El frontend no debe depender de mensajes en español para decidir comportamientos.

Códigos iniciales generales:

```text
VALIDATION_ERROR
UNAUTHENTICATED
INVALID_CREDENTIALS
FORBIDDEN
RESOURCE_NOT_FOUND
ROUTE_NOT_FOUND
METHOD_NOT_ALLOWED
TOO_MANY_REQUESTS
CONFLICT
INTERNAL_SERVER_ERROR
SERVICE_UNAVAILABLE
```

Códigos iniciales del dominio:

```text
PHARMACY_NOT_FOUND
PHARMACY_INACTIVE
REGISTRATION_DATA_ALREADY_EXISTS
REGISTRATION_REQUEST_NOT_FOUND
REGISTRATION_REQUEST_ALREADY_REVIEWED
PROMOTION_NOT_FOUND
PROMOTION_NOT_ACTIVE
PARTICIPANT_NOT_ELIGIBLE
PRODUCT_NOT_ELIGIBLE
TICKET_NOT_FOUND
TRANSACTION_ALREADY_PROCESSED
```

Solo agregar nuevos códigos cuando representen una condición que el consumidor necesite diferenciar.

---

## 14. Error de validación

Los errores producidos por Form Requests deben convertirse automáticamente al contrato estándar.

HTTP:

```text
422 Unprocessable Entity
```

Respuesta:

```json
{
    "success": false,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "username": [
            "El nombre de usuario es obligatorio."
        ]
    },
    "error_code": "VALIDATION_ERROR"
}
```

No es necesario capturar manualmente la validación en cada controlador.

El manejo debe realizarse globalmente.

---

## 15. Credenciales inválidas

El login debe utilizar un mensaje genérico para evitar enumeración de cuentas.

HTTP:

```text
401 Unauthorized
```

Respuesta:

```json
{
    "success": false,
    "message": "Las credenciales proporcionadas no son válidas.",
    "errors": {},
    "error_code": "INVALID_CREDENTIALS"
}
```

No revelar si:

- El usuario no existe.
- El correo no existe.
- El username no existe.
- La contraseña es incorrecta.
- El usuario está bloqueado.
- El usuario está inactivo.

---

## 16. Usuario no autenticado

HTTP:

```text
401 Unauthorized
```

Respuesta:

```json
{
    "success": false,
    "message": "No autenticado.",
    "errors": {},
    "error_code": "UNAUTHENTICATED"
}
```

Todas las rutas protegidas deben retornar esta estructura cuando la sesión no sea válida.

---

## 17. Usuario sin autorización

HTTP:

```text
403 Forbidden
```

Respuesta:

```json
{
    "success": false,
    "message": "No tienes autorización para realizar esta acción.",
    "errors": {},
    "error_code": "FORBIDDEN"
}
```

No confundir:

- `401`: no se ha identificado un usuario.
- `403`: existe un usuario autenticado, pero no tiene permiso.

---

## 18. Recurso no encontrado

HTTP:

```text
404 Not Found
```

Respuesta general:

```json
{
    "success": false,
    "message": "El recurso solicitado no fue encontrado.",
    "errors": {},
    "error_code": "RESOURCE_NOT_FOUND"
}
```

Cuando el dominio requiera un código más específico:

```json
{
    "success": false,
    "message": "No se encontró una farmacia activa con el código indicado.",
    "errors": {},
    "error_code": "PHARMACY_NOT_FOUND"
}
```

No devolver nombres de tablas ni IDs internos en el mensaje.

---

## 19. Ruta no encontrada

Para rutas API inexistentes:

```json
{
    "success": false,
    "message": "La ruta solicitada no fue encontrada.",
    "errors": {},
    "error_code": "ROUTE_NOT_FOUND"
}
```

HTTP:

```text
404 Not Found
```

---

## 20. Método no permitido

Cuando la ruta existe, pero no admite el método HTTP utilizado:

```json
{
    "success": false,
    "message": "El método HTTP utilizado no está permitido.",
    "errors": {},
    "error_code": "METHOD_NOT_ALLOWED"
}
```

HTTP:

```text
405 Method Not Allowed
```

---

## 21. Rate limiting

Cuando se exceda el límite de solicitudes:

```json
{
    "success": false,
    "message": "Has realizado demasiadas solicitudes. Intenta nuevamente más tarde.",
    "errors": {},
    "error_code": "TOO_MANY_REQUESTS"
}
```

HTTP:

```text
429 Too Many Requests
```

Cuando esté disponible, conservar los encabezados estándar relacionados con rate limiting:

```http
Retry-After
X-RateLimit-Limit
X-RateLimit-Remaining
```

No revelar información sensible en la clave utilizada por el rate limiter.

---

## 22. Conflictos de integridad o estado

Utilizar HTTP `409` cuando el conflicto no corresponda directamente a la validación de un campo.

Ejemplos:

- Una transacción ya fue procesada.
- Una solicitud ya fue aprobada.
- Una promoción ya fue finalizada.
- Una operación no puede ejecutarse por el estado actual.
- Se repite una clave de idempotencia.

Respuesta:

```json
{
    "success": false,
    "message": "La operación entra en conflicto con el estado actual del recurso.",
    "errors": {},
    "error_code": "CONFLICT"
}
```

Cuando el conflicto esté asociado claramente a campos del formulario, se permite HTTP `422`.

---

## 23. Errores inesperados

HTTP:

```text
500 Internal Server Error
```

Respuesta pública:

```json
{
    "success": false,
    "message": "Ocurrió un error inesperado.",
    "errors": {},
    "error_code": "INTERNAL_SERVER_ERROR"
}
```

Nunca devolver al cliente:

- Stack traces.
- Consultas SQL.
- Nombres de tablas.
- Rutas del servidor.
- Código fuente.
- Variables de entorno.
- Credenciales.
- Contraseñas.
- Tokens.
- Cookies.
- Mensajes internos completos de excepciones.

El error original debe registrarse utilizando los mecanismos oficiales de Laravel.

No ocultar silenciosamente errores inesperados.

---

## 24. Errores de servicios externos

Cuando un servicio externo no esté disponible temporalmente:

HTTP recomendado:

```text
503 Service Unavailable
```

Respuesta:

```json
{
    "success": false,
    "message": "El servicio requerido no está disponible temporalmente.",
    "errors": {},
    "error_code": "SERVICE_UNAVAILABLE"
}
```

No devolver directamente al consumidor:

- Claves del proveedor.
- URL internas.
- Payloads sensibles.
- Respuestas completas del proveedor.
- Detalles de autenticación.

Los detalles técnicos pueden registrarse internamente con los datos sensibles redactados.

---

## 25. Excepciones de negocio

Los errores controlados del dominio deben poder representarse mediante una excepción reutilizable.

Clase sugerida:

```text
App\Exceptions\BusinessException
```

Debe permitir definir:

- Mensaje público.
- Código interno.
- Código HTTP.
- Errores asociados a campos.
- Excepción anterior opcional.

Ejemplo:

```php
throw new BusinessException(
    message: 'No se encontró una farmacia activa con el código indicado.',
    errorCode: 'PHARMACY_NOT_FOUND',
    status: 404,
);
```

Ejemplo con campos:

```php
throw new BusinessException(
    message: 'No fue posible completar la solicitud de registro.',
    errorCode: 'REGISTRATION_DATA_ALREADY_EXISTS',
    status: 422,
    errors: [
        'username' => [
            'Este nombre de usuario no está disponible.',
        ],
    ],
);
```

El manejador global debe convertir la excepción al contrato estándar.

No utilizar excepciones para reemplazar innecesariamente las validaciones normales de Form Requests.

---

## 26. Manejo global de excepciones

El manejo global debe adaptarse al mecanismo oficial de la versión de Laravel instalada.

Debe transformar al contrato estándar, como mínimo:

- `ValidationException`.
- `AuthenticationException`.
- `AuthorizationException`.
- `ModelNotFoundException`.
- `NotFoundHttpException`.
- `MethodNotAllowedHttpException`.
- `ThrottleRequestsException`.
- `BusinessException`.
- Excepciones inesperadas.

El formato estándar debe aplicarse a:

- Rutas bajo `/api/*`.
- Solicitudes con encabezado `Accept: application/json`.
- Solicitudes que Laravel identifique como `expectsJson()`.

No debe romper:

- Comandos Artisan.
- Ejecuciones desde consola.
- Respuestas HTML no pertenecientes a la API.
- El endpoint `/sanctum/csrf-cookie`.
- Procesamiento interno de queues.

---

## 27. API Resources

Utilizar API Resources para controlar los campos expuestos.

Ejemplo:

```php
return ApiResponse::success(
    data: UserResource::make($user),
    message: 'Usuario autenticado.',
);
```

Debe evitarse:

```php
return ApiResponse::success(
    data: $user,
);
```

Los Resources deben impedir la exposición accidental de:

- Password.
- Hash del password.
- `remember_token`.
- Cookies.
- Tokens.
- Identificadores de sesión.
- Campos internos.
- Información privada de otros usuarios.

---

## 28. Evitar doble envoltorio de datos

Debe evitarse esta estructura:

```json
{
    "success": true,
    "message": "Operación realizada correctamente.",
    "data": {
        "data": {
            "id": "..."
        }
    }
}
```

La estructura correcta es:

```json
{
    "success": true,
    "message": "Operación realizada correctamente.",
    "data": {
        "id": "..."
    }
}
```

Al integrar API Resources con `ApiResponse`, convertirlos o resolverlos de forma compatible con la solicitud actual cuando sea necesario.

La solución debe probarse automáticamente.

---

## 29. Identificadores públicos

Cuando un recurso pueda exponerse fuera del sistema, se recomienda utilizar UUID o ULID como identificador público.

Ejemplo:

```json
{
    "id": "0f38e5cb-f915-45bc-b669-f42b33c2d589"
}
```

No exponer IDs numéricos internos salvo que exista una razón justificada.

Las respuestas deben utilizar consistentemente la propiedad:

```text
id
```

Aunque internamente provenga de una columna `uuid`.

Ejemplo en un Resource:

```php
'id' => $this->uuid,
```

No mezclar arbitrariamente:

```text
id
uuid
public_id
resource_id
```

para representar el mismo concepto.

---

## 30. Fechas y horas

Todas las fechas y horas expuestas por la API deben utilizar formato ISO 8601.

Ejemplo:

```json
{
    "created_at": "2026-07-14T18:30:00Z"
}
```

Reglas:

- Almacenar preferiblemente en UTC.
- Incluir información de zona horaria.
- No devolver formatos ambiguos como `14/07/2026`.
- No depender del formato visual usado por React.
- React será responsable de mostrar las fechas según la zona del usuario.

Para campos que representan solamente fecha:

```json
{
    "starts_on": "2026-07-14"
}
```

---

## 31. Valores booleanos

Los valores booleanos deben devolverse como booleanos JSON reales.

Correcto:

```json
{
    "is_active": true
}
```

Incorrecto:

```json
{
    "is_active": "true"
}
```

Incorrecto:

```json
{
    "is_active": 1
}
```

---

## 32. Valores nulos

Utilizar `null` cuando un campo exista en el contrato, pero no tenga valor.

```json
{
    "email": null
}
```

No utilizar:

```json
{
    "email": ""
}
```

salvo que una cadena vacía sea un valor válido del negocio.

No eliminar arbitrariamente propiedades opcionales si el frontend necesita un contrato estable.

---

## 33. Nombres de propiedades

Las propiedades JSON deben escribirse en `snake_case`.

Ejemplos:

```json
{
    "first_name": "Albert",
    "last_name": "Martínez",
    "created_at": "2026-07-14T18:30:00Z"
}
```

No mezclar:

```text
firstName
first_name
FirstName
```

Los nombres de propiedades deben estar en inglés.

Los mensajes de usuario pueden estar en español.

---

## 34. Filtros

Los filtros deben enviarse como parámetros de consulta.

Ejemplo:

```text
GET /api/v1/admin/promotions?status=active&search=motor
```

Reglas:

- Validar los filtros.
- Ignorar filtros desconocidos solamente si esa política está documentada.
- Preferiblemente rechazar valores inválidos.
- No concatenar valores directamente en SQL.
- Utilizar scopes, Query Builder o mecanismos seguros.

---

## 35. Ordenamiento

Parámetros recomendados:

```text
?sort=created_at
?direction=desc
```

Ejemplo:

```text
GET /api/v1/admin/promotions?sort=starts_at&direction=asc
```

Reglas:

- Mantener una lista blanca de columnas permitidas.
- Permitir solamente `asc` y `desc`.
- No utilizar directamente una columna enviada por el cliente sin validación.
- Definir un ordenamiento predeterminado.

---

## 36. Búsquedas

Parámetro recomendado:

```text
?search=texto
```

Ejemplo:

```text
GET /api/v1/admin/pharmacies?search=farmacia
```

Las búsquedas deben:

- Validar longitud mínima y máxima cuando sea necesario.
- Aplicar rate limiting en endpoints públicos.
- No exponer información sensible.
- Utilizar índices cuando el volumen lo requiera.
- Evitar consultas excesivamente costosas.

---

## 37. Inclusión de relaciones

No permitir carga arbitraria de relaciones Eloquent desde parámetros enviados por el cliente.

Cuando se admita inclusión de relaciones, utilizar una lista blanca.

Ejemplo:

```text
?include=pharmacy
```

No permitir que el cliente solicite relaciones internas o sensibles.

La API puede devolver relaciones necesarias sin que el cliente deba solicitarlas cuando sean parte natural del recurso.

---

## 38. Creación de recursos

Cuando un recurso sea creado:

- Utilizar HTTP `201`.
- Devolver el recurso creado o una representación útil.
- Incluir un mensaje claro.

Ejemplo:

```json
{
    "success": true,
    "message": "Solicitud de registro creada correctamente.",
    "data": {
        "request_id": "0f38e5cb-f915-45bc-b669-f42b33c2d589",
        "status": "pending"
    }
}
```

No devolver información sensible almacenada durante la creación.

---

## 39. Actualización de recursos

Para actualizaciones completas se puede utilizar:

```text
PUT
```

Para actualizaciones parciales se puede utilizar:

```text
PATCH
```

No utilizar `POST` para todas las actualizaciones sin una razón de dominio.

La respuesta debe indicar claramente el resultado:

```json
{
    "success": true,
    "message": "Promoción actualizada correctamente.",
    "data": {
        "id": "1e8ce625-4918-4aa4-99e2-6349f3c670c1",
        "name": "Promoción actualizada"
    }
}
```

---

## 40. Eliminación de recursos

Utilizar:

```text
DELETE
```

La eliminación física o lógica depende de las reglas del dominio.

Cuando la operación no devuelva el recurso:

```json
{
    "success": true,
    "message": "Recurso eliminado correctamente.",
    "data": {}
}
```

No eliminar físicamente registros necesarios para auditoría o trazabilidad.

---

## 41. Acciones de dominio

Las acciones que no correspondan claramente a CRUD pueden utilizar endpoints explícitos.

Ejemplos:

```text
POST /api/v1/admin/registration-requests/{id}/approve
POST /api/v1/admin/registration-requests/{id}/reject
POST /api/v1/admin/promotions/{id}/activate
POST /api/v1/admin/promotions/{id}/suspend
POST /api/v1/admin/transactions/{id}/reverse
```

Los nombres de acciones deben estar en inglés.

No forzar acciones complejas dentro de un `update` genérico si eso reduce claridad o auditoría.

---

## 42. Autenticación desde React

La SPA React utilizará Sanctum stateful.

Flujo:

```text
GET  /sanctum/csrf-cookie
POST /api/v1/auth/login
GET  /api/v1/auth/me
POST /api/v1/auth/logout
```

El login no devuelve Bearer Token.

Respuesta de login:

```json
{
    "success": true,
    "message": "Inicio de sesión exitoso.",
    "data": {
        "user": {
            "id": "0f38e5cb-f915-45bc-b669-f42b33c2d589",
            "username": "albertmat34"
        }
    }
}
```

La autenticación de solicitudes posteriores se realiza mediante la cookie de sesión HttpOnly.

Axios debe configurarse con credenciales:

```javascript
import axios from 'axios';

const api = axios.create({
    baseURL: 'http://localhost:8000',
    withCredentials: true,
    withXSRFToken: true,
});
```

React no debe:

- Leer directamente la cookie de sesión.
- Guardar la sesión en `localStorage`.
- Guardar tokens de autenticación en `sessionStorage`.
- Enviar un `user_id` para indicar quién está autenticado.

---

## 43. Propiedad de los recursos

Los endpoints privados deben determinar el usuario mediante:

```php
$request->user();
```

o:

```php
Auth::user();
```

Nunca mediante un identificador enviado por React.

Incorrecto:

```php
Ticket::where('user_id', $request->input('user_id'))->get();
```

Correcto:

```php
$request->user()
    ->tickets()
    ->paginate();
```

Las Policies deben utilizarse cuando un usuario acceda a un recurso individual.

---

## 44. Idempotencia

Los endpoints susceptibles a solicitudes duplicadas deben considerar idempotencia.

Ejemplos:

- Registro de transacciones.
- Generación de boletos.
- Procesamiento de pagos.
- Importaciones.
- Webhooks.
- Reversiones.

Encabezado recomendado:

```http
Idempotency-Key: identificador-unico
```

Cuando una solicitud con la misma clave ya haya sido procesada, la API debe:

- Devolver el resultado previo, o
- Devolver un conflicto controlado.

Nunca procesar dos veces una operación crítica sin intención explícita.

Código sugerido:

```text
TRANSACTION_ALREADY_PROCESSED
```

---

## 45. Archivos

Para cargar archivos utilizar:

```text
multipart/form-data
```

Las respuestas deben conservar el contrato estándar.

Ejemplo:

```json
{
    "success": true,
    "message": "Archivo cargado correctamente.",
    "data": {
        "id": "79429f87-2ad4-4bf5-a463-2088e985c16d",
        "name": "documento.pdf",
        "size": 250000,
        "mime_type": "application/pdf"
    }
}
```

Validar:

- Extensión.
- MIME type.
- Tamaño.
- Nombre.
- Autorización.
- Contenido cuando aplique.

No confiar únicamente en la extensión enviada por el cliente.

---

## 46. Descargas

Los endpoints de descarga pueden devolver una respuesta de archivo en lugar del contrato JSON cuando la operación sea exitosa.

En caso de error, deben retornar el contrato JSON estándar.

Ejemplo:

```text
GET /api/v1/admin/exports/{export}/download
```

El acceso debe estar autorizado.

No exponer rutas físicas del servidor.

---

## 47. Seguridad de los mensajes

Los mensajes de error deben ser útiles para el usuario, pero no deben facilitar:

- Enumeración de cuentas.
- Descubrimiento de tablas.
- Descubrimiento de IDs internos.
- Acceso a stack traces.
- Obtención de rutas internas.
- Identificación de reglas sensibles.
- Exposición de datos de terceros.

Los logs internos pueden contener más contexto, siempre que los datos sensibles sean redactados.

---

## 48. Información sensible

Nunca devolver:

- Password.
- Hashes.
- `remember_token`.
- Cookies.
- Tokens.
- Claves API.
- Encabezados de autorización.
- Variables de entorno.
- Datos privados de otros usuarios.
- IPs internas sin necesidad.
- User-Agent administrativo sin necesidad.
- Campos de auditoría sensibles.

Los API Resources deben definir explícitamente los campos permitidos.

---

## 49. Correlation ID

Cuando se implemente un identificador de correlación, la API debe devolverlo preferiblemente mediante un encabezado:

```http
X-Correlation-ID: uuid
```

En errores inesperados puede incluirse también en el cuerpo:

```json
{
    "success": false,
    "message": "Ocurrió un error inesperado.",
    "errors": {},
    "error_code": "INTERNAL_SERVER_ERROR",
    "correlation_id": "41f1a0a2-a26f-4695-9d27-e628fac6dd07"
}
```

No incluir `correlation_id` de forma inconsistente. Una vez implementado, debe aplicarse de manera uniforme.

---

## 50. Compatibilidad retroactiva

Dentro de una misma versión de la API no se debe:

- Renombrar propiedades existentes sin transición.
- Eliminar propiedades utilizadas.
- Cambiar tipos de datos.
- Cambiar códigos internos arbitrariamente.
- Modificar el significado de estados existentes.
- Alterar la semántica de endpoints en producción.

Cuando sea necesario introducir cambios incompatibles, evaluar una nueva versión:

```text
/api/v2
```

---

## 51. Documentación de endpoints

Cada endpoint debe documentar:

- Método HTTP.
- Ruta.
- Autenticación requerida.
- Roles o permisos.
- Parámetros.
- Payload.
- Reglas de validación.
- Respuesta exitosa.
- Respuestas de error.
- Códigos internos.
- Rate limiting.
- Efectos secundarios.
- Consideraciones de idempotencia.

No documentar funcionalidades no implementadas como si estuvieran disponibles.

---

## 52. Pruebas del contrato

Las pruebas automatizadas deben verificar, según corresponda:

- Presencia de `success`.
- Presencia de `message`.
- Presencia de `data` en éxitos.
- Presencia de `errors` y `error_code` en errores.
- Ausencia de `data` en errores.
- Código HTTP correcto.
- Tipos de datos correctos.
- Ausencia de campos sensibles.
- Ausencia de doble envoltorio `data`.
- Manejo global de excepciones.
- Formato de validaciones.
- Formato de no autenticado.
- Formato de no autorizado.
- Formato de rate limiting.
- Formato de recursos no encontrados.

Ejemplo:

```php
$response
    ->assertStatus(422)
    ->assertJsonStructure([
        'success',
        'message',
        'errors',
        'error_code',
    ])
    ->assertJson([
        'success' => false,
        'error_code' => 'VALIDATION_ERROR',
    ])
    ->assertJsonMissingPath('data');
```

---

## 53. Ejemplo completo de endpoint exitoso

Solicitud:

```http
GET /api/v1/public/pharmacies/by-code/FAR001
Accept: application/json
```

Respuesta:

```json
{
    "success": true,
    "message": "Farmacia encontrada.",
    "data": {
        "code": "FAR001",
        "name": "Farmacia Ejemplo",
        "city": "Santiago"
    }
}
```

---

## 54. Ejemplo completo de endpoint con error

Solicitud:

```http
GET /api/v1/public/pharmacies/by-code/INVALID
Accept: application/json
```

Respuesta:

```json
{
    "success": false,
    "message": "No se encontró una farmacia activa con el código indicado.",
    "errors": {},
    "error_code": "PHARMACY_NOT_FOUND"
}
```

HTTP:

```text
404 Not Found
```

---
## Documentación para integración frontend

Todo grupo de endpoints que vaya a ser consumido por React debe contar con documentación dentro de:

```text
docs/frontend/
```
Los archivos deben organizarse por dominio o funcionalidad.

Ejemplos:

docs/frontend/authentication-api.md
docs/frontend/registration-api.md
docs/frontend/promotions-api.md
docs/frontend/tickets-api.md
docs/frontend/reports-api.md

No crear necesariamente un archivo por endpoint. Agrupar endpoints relacionados dentro de un mismo contrato de integración.

Objetivo

Cada archivo debe permitir que un desarrollador o agente frontend implemente la integración sin inspeccionar el código interno de Laravel.

La documentación debe describir únicamente el comportamiento realmente implementado y probado.

Contenido obligatorio

Cada documento debe incluir:

Objetivo y alcance.
URL base y versionado.
Modelo de autenticación.
Configuración necesaria del cliente HTTP.
Tabla resumida de endpoints.
Documentación individual de cada endpoint.
Payloads completos.
Tipos de datos.
Campos obligatorios y opcionales.
Reglas de validación.
Normalización aplicada por el backend.
Respuestas exitosas.
Respuestas de error.
Códigos internos de error.
Rate limiting.
Efectos secundarios.
Comportamiento esperado del frontend.
Ejemplos funcionales con Axios o Fetch.
Pruebas manuales sugeridas.
Estado real de implementación.

Tabla resumida de endpoints

Cada documento debe incluir una tabla similar a:

Método	Ruta	Autenticación	Rate limit	Descripción
POST	/api/v1/auth/login	Pública	5/min	Inicia una sesión
GET	/api/v1/auth/me	auth:sanctum	General	Obtiene el usuario autenticado
Documentación individual

Para cada endpoint documentar:

Nombre funcional.
Método HTTP.
Ruta.
Autenticación requerida.
Roles o permisos, cuando correspondan.
Parámetros de ruta.
Parámetros de consulta.
Payload.
Campos obligatorios.
Campos opcionales.
Tipos de datos.
Reglas de validación.
Normalización.
Código HTTP exitoso.
Respuesta exitosa.
Errores posibles.
Códigos internos de error.
Rate limiting.
Efectos secundarios.
Consideraciones para React.
Campos de formularios

Los formularios deben documentarse mediante una tabla:

Campo	Tipo	Obligatorio	Nullable	Confirmación	Reglas	Observaciones
username	string	Sí	No	No	Único, minúsculas	No permitir espacios
password	string	Sí	No	Sí	Política vigente	Enviar junto a password_confirmation

También deben indicarse expresamente los campos que el frontend no debe enviar cuando puedan afectar seguridad o integridad.

Ejemplos:

status
user_id
pharmacy_id
approved_at
reviewed_by
request_ip
Ejemplos para React

Los ejemplos deben utilizar la configuración real esperada por el frontend.

Para Sanctum stateful:

import axios from 'axios';

export const api = axios.create({
    baseURL: import.meta.env.VITE_API_URL,
    withCredentials: true,
    withXSRFToken: true,
    headers: {
        Accept: 'application/json',
    },
});

No incluir un encabezado Bearer cuando la autenticación se realice mediante sesión y cookies.

Manejo de errores

Debe explicarse cómo mapear los errores del contrato estándar:

try {
    await api.post('/api/v1/example', payload);
} catch (error) {
    const response = error.response?.data;

    if (response?.error_code === 'VALIDATION_ERROR') {
        const fieldErrors = response.errors;
    }
}

Para cada código de error debe indicarse la acción recomendada:

Código	HTTP	Comportamiento sugerido
VALIDATION_ERROR	422	Mostrar errores junto a los campos
INVALID_CREDENTIALS	401	Mostrar un mensaje general
UNAUTHENTICATED	401	Limpiar el estado local y redirigir al login
FORBIDDEN	403	Mostrar que el usuario no tiene permiso
TOO_MANY_REQUESTS	429	Evitar reintentos inmediatos
Estado de autenticación

Cuando se utilice Sanctum stateful, la documentación debe aclarar:

React no recibe un token durante el login.
La sesión se conserva mediante una cookie HttpOnly.
React debe utilizar withCredentials.
La aplicación debe consultar /api/v1/auth/me al iniciar o recargar.
React no debe intentar leer o eliminar manualmente la cookie HttpOnly.
Un HTTP 401 indica que no existe una sesión válida.
Calidad y sincronización

La documentación debe:

Coincidir con las rutas registradas.
Coincidir con los Form Requests.
Coincidir con los API Resources.
Coincidir con los códigos de error implementados.
Coincidir con las pruebas automatizadas.
Usar ejemplos válidos.
No contener secretos.
No presentar funcionalidades pendientes como disponibles.

Cuando cambie un endpoint, su documento frontend debe actualizarse en el mismo requerimiento.

## 55. Lista de verificación para nuevos endpoints

Antes de considerar terminado un endpoint, verificar:

- La ruta está bajo `/api/v1`.
- Utiliza el método HTTP apropiado.
- Utiliza Form Request cuando recibe datos.
- Utiliza autorización cuando corresponde.
- Utiliza API Resource para exponer modelos.
- Utiliza la infraestructura centralizada de respuestas.
- Devuelve el código HTTP correcto.
- Devuelve un código interno estable en los errores.
- No expone campos sensibles.
- Aplica rate limiting cuando corresponde.
- Evita consultas N+1.
- Tiene pruebas automatizadas.
- Está documentado.
- Mantiene compatibilidad con el contrato definido en este documento.

---

## 56. Regla final

Toda nueva API debe ser predecible para React y consistente con el resto del sistema.

No se debe crear un formato diferente para resolver rápidamente un endpoint.

Cuando una necesidad no esté contemplada en este documento:

1. Evaluar su impacto.
2. Elegir una convención reutilizable.
3. Documentarla.
4. Actualizar las pruebas.
5. Aplicarla consistentemente en los endpoints relacionados.