Clientes
Clientes
Prefijo: /api/clientes
Requiere Auth: Sí
Permiso requerido: clientes, *
Módulo de clientes para gestionar el directorio de clientes del POS. Incluye búsqueda por documento y nombre.
Nota: Los clientes utilizan soft delete — al eliminar un cliente se marca como eliminado sin borrarse físicamente.
Nota de rendimiento: El listado y las búsquedas (/search/nombre, /search/dni) se sirven desde una caché en memoria (TTL 10s). Al crear/actualizar/eliminar un cliente, la caché se invalida al instante. Ver Caché y Rendimiento.
Paginación y Total Count
Todos los endpoints de listado y búsqueda que retornan arrays incluyen el header HTTP X-Total-Count con el número total de registros que cumplen los filtros sin paginación.
Cómo funciona
GET /api/clientes/?skip=0&limit=20HTTP/1.1 200 OKX-Total-Count: 1247 ← Total de clientes activos (sin paginar)Content-Type: application/json
[{"id":1,"nombre":"Juan",...}, ... 20 items ...]El frontend calcula:
totalPages = Math.ceil(X-Total-Count / limit)currentPage = (skip / limit) + 1hasNext = currentPage < totalPages
Filtros que afectan X-Total-Count
| Endpoint | Qué cuenta X-Total-Count |
|---|---|
GET /api/clientes/ | Todos los clientes activos del tenant |
GET /api/clientes/search/nombre?nombre=juan | Clientes con “juan” en el nombre |
GET /api/clientes/
Descripción: Lista todos los clientes con paginación.
Requiere Auth: Sí
Permiso requerido: clientes, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
skip | int | ❌ | Registros a saltar (default 0) |
limit | int | ❌ | Máximo de registros (default 100, máx 1000) |
Respuesta exitosa: 200 OK
Headers de respuesta:
| Header | Tipo | Descripción |
|---|---|---|
X-Total-Count | integer | Total de clientes activos del tenant (excluye soft-deleted). No considera skip/limit. |
Response:
[ { "id": 1, "dni": 123456789, "nombre": "Cliente Ejemplo", ... }]cURL:
curl "http://localhost/api/clientes/?skip=0&limit=20" -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.get("http://localhost/api/clientes/", params={"skip": 0, "limit": 20}, headers={"Authorization": f"Bearer :token"})clientes = resp.json()total = int(resp.headers.get("X-Total-Count", "0"))total_pages = (total + 20 - 1) // 20 # ceil divisionJavaScript:
const resp = await fetch('http://localhost/api/clientes/?skip=0&limit=20', { headers: {'Authorization': `Bearer $:token`}});const clientes = await resp.json();const totalCount = parseInt(resp.headers.get('X-Total-Count') || '0', 10);const totalPages = Math.ceil(totalCount / 20);GET /api/clientes/:cliente_id
Descripción: Obtiene un cliente por ID
Requiere Auth: Sí
Permiso requerido: clientes, leer
Respuesta exitosa: 200 OK
Errores: 404 (no encontrado)
cURL:
curl http://localhost/api/clientes/1 -H "Authorization: Bearer <token>"POST /api/clientes/
Descripción: Crea un nuevo cliente
Requiere Auth: Sí
Permiso requerido: clientes, crear
Request:
{ "dni": 123456789, "nombre": "Nuevo Cliente", "telefono": 3007654321, "direccion": "Calle 10 #20-30", "razon": "Nuevo Cliente", "tipoId": "CC", "resposabilidadTrib": "R-99-PN"}Respuesta exitosa: 201 Created
cURL:
curl -X POST http://localhost/api/clientes/ \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"dni":123456789,"nombre":"Nuevo Cliente","telefono":3007654321,"direccion":"Calle 10 #20-30","razon":"Nuevo Cliente","tipoId":"CC","resposabilidadTrib":"R-99-PN"}'Python:
import httpxresp = httpx.post("http://localhost/api/clientes/", json={"dni": 123456789, "nombre": "Nuevo Cliente", "telefono": 3007654321, "direccion": "Calle 10 #20-30", "razon": "Nuevo Cliente", "tipoId": "CC", "resposabilidadTrib": "R-99-PN"}, headers={"Authorization": f"Bearer :token"})cliente = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/clientes/', { method: 'POST', headers: {'Content-Type': 'application/json', 'Authorization': `Bearer $:token`}, body: JSON.stringify({dni: 123456789, nombre: 'Nuevo Cliente', telefono: 3007654321, direccion: 'Calle 10 #20-30', razon: 'Nuevo Cliente', tipoId: 'CC', resposabilidadTrib: 'R-99-PN'})});PUT /api/clientes/:cliente_id
Descripción: Actualiza un cliente existente
Requiere Auth: Sí
Permiso requerido: clientes, actualizar
Respuesta exitosa: 200 OK
Errores: 404 (no encontrado)
cURL:
curl -X PUT http://localhost/api/clientes/1 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"nombre":"Cliente Actualizado","telefono":3009998877}'DELETE /api/clientes/:cliente_id
Descripción: Elimina (soft delete) un cliente
Requiere Auth: Sí
Permiso requerido: clientes, eliminar
Respuesta exitosa: 204 No Content
Errores: 404 (no encontrado)
cURL:
curl -X DELETE http://localhost/api/clientes/1 -H "Authorization: Bearer <token>"Búsqueda
GET /api/clientes/search/dni
Descripción: Busca un cliente por número de documento
Requiere Auth: Sí
Permiso requerido: clientes, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
dni | int | ✅ | Número de documento |
Respuesta exitosa: 200 OK
Errores: 404 (no encontrado)
cURL:
curl "http://localhost/api/clientes/search/dni?dni=123456789" -H "Authorization: Bearer <token>"GET /api/clientes/search/nombre
Descripción: Busca clientes por nombre (búsqueda parcial, case-insensitive) con paginación.
Requiere Auth: Sí
Permiso requerido: clientes, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
nombre | string | ✅ | Texto parcial del nombre a buscar |
skip | int | ❌ | Registros a saltar (default 0) |
limit | int | ❌ | Máximo de registros (default 100, máx 1000) |
Respuesta exitosa: 200 OK
Headers de respuesta:
| Header | Tipo | Descripción |
|---|---|---|
X-Total-Count | integer | Total de clientes con el texto en el nombre. No considera skip/limit. |
cURL:
curl "http://localhost/api/clientes/search/nombre?nombre=Cliente&skip=0&limit=20" -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.get("http://localhost/api/clientes/search/nombre", params={"nombre": "Cliente", "skip": 0, "limit": 20}, headers={"Authorization": f"Bearer :token"})clientes = resp.json()total = int(resp.headers.get("X-Total-Count", "0"))