Sedes
Sedes [NUEVO]
Prefijo: /api/sedes
Requiere Auth: Sí
Permiso requerido: sedes, *
Una Sede es una local/tienda física de un tenant y agrupa las Terminales (cajas). El módulo habilita operación multi-sede: cada venta, jornada y cierre queda marcado con la sede de quien la registró.
Notas de negocio
- Jerarquía:
Sede(1) → (N)Terminal. Las tablasterminales,ventas,jornada_cajaycajallevansede_id. - Límite de 50 sedes activas por tenant:
POSTresponde403 Forbiddenal exceder. Eliminar una sede libera cupo. - Una única sede principal: si crea una con
es_principal=true, se quita la bandera a las demás. codigoúnico por tenant:409 Conflictsi se repite.- Borrado físico:
DELETEelimina la sede; las FK usanON DELETE SET NULL→ los registros históricos quedan “sin sede” (los datos NO se borran). - Stamping server-side: el
sede_idNUNCA lo envía el cliente en la venta: se toma del JWT de la sesión (sede_idamarrado en login/seleccionar-sede). Anti-spoofing. - Sesión amarrada a sede: al login, si hay >1 sede activa, la respuesta trae
sede_seleccion_requerida=trueysedes[]; el cliente muestra el modal y llama aPOST /api/auth/seleccionar-sede. Ver Auth. - Filtros de lectura:
/api/reportes/ventasy/api/dashboard/*aceptan?sede_id=. Un Asistente (rol 2) siempre queda cegado a su propia sede; Admin/Superadmin eligen o ven todas. - Monitoreo de cierre:
GET /api/caja/estado-sedesdevuelve ventas y jornada por sede (pestañas del CierreCaja). Ver Caja. - Paginación: los GET listados devuelven header
X-Total-Count.
GET /api/sedes/ [NUEVO]
Descripción: Lista las sedes del tenant autenticado (principal y nombre). Pobla el selector de sedes del panel y el modal de selección.
Requiere Auth: Sí
Permiso requerido: sedes, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
solo_activas | bool | ❌ | true = solo sedes con activo=true (default false) |
skip | int | ❌ | Registros a saltar (default 0) |
limit | int | ❌ | Máximo de registros (default 100) |
Respuesta exitosa: 200 OK (+ header X-Total-Count)
Response:
[ { "id": "9b1e8f5a-3f2c-4d6e-9a10-7c2f1e0b4d11", "tenant_id": "90af7e06-4211-4f99-82b7-adcf9b0a2382", "codigo": "default", "nombre": "Sede Principal", "direccion": null, "ciudad": null, "telefono": null, "activo": true, "es_principal": true, "created_at": "2026-10-22T09:15:00-05:00" }]cURL:
curl "http://localhost/api/sedes/?solo_activas=true" -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.get("http://localhost/api/sedes/", params={"solo_activas": "true"}, headers={"Authorization": f"Bearer {token}"})sedes = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/sedes/?solo_activas=true', { headers: {'Authorization': `Bearer ${token}`}});const sedes = await resp.json();GET /api/sedes/search [NUEVO]
Descripción: Busca sedes por nombre o código (case-insensitive). Ideal para un combobox con autocompletado.
Requiere Auth: Sí
Permiso requerido: sedes, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
termino | string | ✅ | Texto a buscar en nombre o código (mín. 1 char) |
skip | int | ❌ | Registros a saltar (default 0) |
limit | int | ❌ | Máximo de registros (default 100) |
Respuesta exitosa: 200 OK
cURL:
curl "http://localhost/api/sedes/search?termino=norte" -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.get("http://localhost/api/sedes/search", params={"termino": "norte"}, headers={"Authorization": f"Bearer {token}"})resultados = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/sedes/search?termino=norte', { headers: {'Authorization': `Bearer ${token}`}});const resultados = await resp.json();GET /api/sedes/:sede_id [NUEVO]
Descripción: Obtiene el detalle de una sede por ID (UUID) — pantalla de edición.
Requiere Auth: Sí
Permiso requerido: sedes, leer
Respuesta exitosa: 200 OK
Errores: 404 (no encontrada o de otro tenant)
Response:
{ "id": "0f2a7c9e-1111-4444-8888-abcdef012345", "tenant_id": "90af7e06-4211-4f99-82b7-adcf9b0a2382", "codigo": "NOR", "nombre": "Sede Norte", "direccion": "Cra 15 # 80-20", "ciudad": "Bogota", "telefono": "6015551234", "activo": true, "es_principal": false, "created_at": "2026-10-20T11:00:00-05:00"}cURL:
curl http://localhost/api/sedes/0f2a7c9e-1111-4444-8888-abcdef012345 -H "Authorization: Bearer <token>"POST /api/sedes/ [NUEVO]
Descripción: Crea una sede (valida límite de 50 por tenant y codigo único).
Requiere Auth: Sí
Permiso requerido: sedes, crear
Request:
{ "codigo": "NOR", "nombre": "Sede Norte", "direccion": "Cra 15 # 80-20", "ciudad": "Bogota", "es_principal": false}| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
codigo | string | ✅ | Código único por tenant (1–40 chars) |
nombre | string | ✅ | Nombre visible (1–120 chars) |
direccion | string | ❌ | Dirección (máx 160) |
ciudad | string | ❌ | Ciudad (máx 80) |
telefono | string | ❌ | Teléfono (máx 40) |
es_principal | bool | ❌ | Default false. true reemplaza la principal actual |
Respuesta exitosa: 201 Created
Errores:
| Código | Causa | Solución |
|---|---|---|
403 | Límite de 50 sedes alcanzado / sin permiso | Elimine una sede / solicite permiso |
409 | Ya existe ese codigo en el tenant | Use otro código |
cURL:
curl -X POST http://localhost/api/sedes/ \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"codigo":"NOR","nombre":"Sede Norte","ciudad":"Bogota"}'Python:
import httpxresp = httpx.post("http://localhost/api/sedes/", json={"codigo": "NOR", "nombre": "Sede Norte", "ciudad": "Bogota"}, headers={"Authorization": f"Bearer {token}"})sede = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/sedes/', { method: 'POST', headers: {'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`}, body: JSON.stringify({codigo: 'NOR', nombre: 'Sede Norte', ciudad: 'Bogota'})});const sede = await resp.json();PUT /api/sedes/:sede_id [NUEVO]
Descripción: Actualiza una sede (parcial update). Puede cambiar codigo, nombre, dirección o desactivarla.
Requiere Auth: Sí
Permiso requerido: sedes, actualizar
Request: todos los campos opcionales
{ "nombre": "Sede Norte - Renombrada", "direccion": "Cra 15 # 80-25", "activo": true}Respuesta exitosa: 200 OK
Errores: 404 (no encontrada), 409 (codigo duplicado en otro registro)
cURL:
curl -X PUT http://localhost/api/sedes/0f2a7c9e-1111-4444-8888-abcdef012345 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"nombre":"Sede Norte - Renombrada"}'DELETE /api/sedes/:sede_id [NUEVO]
Descripción: Elimina una sede (borrado físico — libera cupo). Las FK quedan NULL (histórico conserva los datos).
Requiere Auth: Sí
Permiso requerido: sedes, eliminar
Respuesta exitosa: 204 No Content
Errores: 404 (no encontrada)
cURL:
curl -X DELETE http://localhost/api/sedes/0f2a7c9e-1111-4444-8888-abcdef012345 -H "Authorization: Bearer <token>"Tabla de errores general
| Código | Causa | Solución |
|---|---|---|
401 | Token expirado o inválido | Re-login |
403 | Sin permiso sedes, <acción> / límite de 50 sedes | Solicitar permiso a admin / eliminar sede |
404 | Sede no encontrada (o de otro tenant) | Verificar ID |
409 | codigo de sede duplicado en el tenant | Usar otro código |