Skip to content

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 tablas terminales, ventas, jornada_caja y caja llevan sede_id.
  • Límite de 50 sedes activas por tenant: POST responde 403 Forbidden al 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 Conflict si se repite.
  • Borrado físico: DELETE elimina la sede; las FK usan ON DELETE SET NULL → los registros históricos quedan “sin sede” (los datos NO se borran).
  • Stamping server-side: el sede_id NUNCA lo envía el cliente en la venta: se toma del JWT de la sesión (sede_id amarrado en login/seleccionar-sede). Anti-spoofing.
  • Sesión amarrada a sede: al login, si hay >1 sede activa, la respuesta trae sede_seleccion_requerida=true y sedes[]; el cliente muestra el modal y llama a POST /api/auth/seleccionar-sede. Ver Auth.
  • Filtros de lectura: /api/reportes/ventas y /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-sedes devuelve 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ámetroTipoObligatorioDescripción
solo_activasbool❌true = solo sedes con activo=true (default false)
skipint❌Registros a saltar (default 0)
limitint❌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:

Terminal window
curl "http://localhost/api/sedes/?solo_activas=true" -H "Authorization: Bearer <token>"

Python:

import httpx
resp = 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ámetroTipoObligatorioDescripción
terminostring✅Texto a buscar en nombre o código (mín. 1 char)
skipint❌Registros a saltar (default 0)
limitint❌Máximo de registros (default 100)

Respuesta exitosa: 200 OK

cURL:

Terminal window
curl "http://localhost/api/sedes/search?termino=norte" -H "Authorization: Bearer <token>"

Python:

import httpx
resp = 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:

Terminal window
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
}
CampoTipoObligatorioDescripción
codigostring✅Código único por tenant (1–40 chars)
nombrestring✅Nombre visible (1–120 chars)
direccionstring❌Dirección (máx 160)
ciudadstring❌Ciudad (máx 80)
telefonostring❌Teléfono (máx 40)
es_principalbool❌Default false. true reemplaza la principal actual

Respuesta exitosa: 201 Created

Errores:

CódigoCausaSolución
403Límite de 50 sedes alcanzado / sin permisoElimine una sede / solicite permiso
409Ya existe ese codigo en el tenantUse otro código

cURL:

Terminal window
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 httpx
resp = 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:

Terminal window
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:

Terminal window
curl -X DELETE http://localhost/api/sedes/0f2a7c9e-1111-4444-8888-abcdef012345 -H "Authorization: Bearer <token>"

Tabla de errores general

CódigoCausaSolución
401Token expirado o inválidoRe-login
403Sin permiso sedes, <acción> / límite de 50 sedesSolicitar permiso a admin / eliminar sede
404Sede no encontrada (o de otro tenant)Verificar ID
409codigo de sede duplicado en el tenantUsar otro código