Terminales
Terminales
Prefijo: /api/terminales
Requiere Auth: Sí
Permiso requerido: terminales, *
Módulo de gestión de terminales POS. Cada terminal representa un punto de venta físico (ej: “Caja 1”, “Caja 2”, “Kiosco”) con un código único por tenant. Permite controlar qué terminales están activas y cuáles están deshabilitadas.
Contexto de uso: Este módulo es utilizado por el panel de administración (NeoxAdminPanel) para gestionar los terminales del sistema. Los terminales activos pueden realizar ventas; los inactivos quedan bloqueados.
Regla de unicidad: El campo codigo debe ser único por tenant. Si se intenta crear un terminal con un código ya existente en el mismo tenant, la API responde 409 Conflict.
CRUD Terminales
<MethodBadge method="GET" /> /api/terminales/ [NUEVO]
Descripción: Lista los terminales del tenant con paginación. El Superadmin (rol=3) puede listar los terminales de cualquier tenant (o todos) enviando tenant_id; el Administrador (rol=1) y el Asistente (rol=2) siempre ven solo los terminales de SU tenant (si envían tenant_id ajeno → 403).
Requiere Auth: Sí
Permiso requerido: terminales, leer
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
skip | int | ❌ | Número de registros a saltar (default 0) |
limit | int | ❌ | Máximo de registros a retornar (default 100) |
tenant_id | UUID | ❌ | Solo Superadmin: filtra por tenant. Omitido → terminales del tenant del JWT. Usar vacío no aplica (si se envía vacío se trata como omitido) |
Respuesta exitosa: 200 OK
Response:
[ { "id": 1, "codigo": "CAJA-001", "nombre": "Caja Principal", "descripcion": "Terminal de la caja principal del restaurante", "activo": true, "tenant_id": "uuid-tenant" }, { "id": 2, "codigo": "CAJA-002", "nombre": "Caja Secundaria", "descripcion": "Terminal del bar", "activo": false, "tenant_id": "uuid-tenant" }]cURL:
curl "http://localhost/api/terminales/?skip=0&limit=20" \ -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.get("http://localhost/api/terminales/", params={"skip": 0, "limit": 20}, headers={"Authorization": f"Bearer :token"})terminales = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/terminales/?skip=0&limit=20', { headers: {'Authorization': `Bearer $:token`}});const terminales = await resp.json();<MethodBadge method="GET" /> /api/terminales/:terminal_id [NUEVO]
Descripción: Obtiene un terminal por ID
Requiere Auth: Sí
Permiso requerido: terminales, leer
Respuesta exitosa: 200 OK
Errores: 404 (no encontrado)
Response:
{ "id": 1, "codigo": "CAJA-001", "nombre": "Caja Principal", "descripcion": "Terminal de la caja principal del restaurante", "activo": true, "tenant_id": "uuid-tenant"}cURL:
curl http://localhost/api/terminales/1 -H "Authorization: Bearer <token>"<MethodBadge method="POST" /> /api/terminales/ [NUEVO]
Descripción: Crea un nuevo terminal
Requiere Auth: Sí
Permiso requerido: terminales, crear
Request:
{ "codigo": "CAJA-003", "nombre": "Kiosco Terraza", "descripcion": "Terminal portátil para la terraza", "activo": true}Respuesta exitosa: 201 Created
Errores: 409 (código duplicado en el mismo tenant), 403 (límite de terminales alcanzado — limite_terminals del tenant)
Response:
{ "id": 3, "codigo": "CAJA-003", "nombre": "Kiosco Terraza", "descripcion": "Terminal portátil para la terraza", "activo": true, "tenant_id": "uuid-tenant"}cURL:
curl -X POST http://localhost/api/terminales/ \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"codigo":"CAJA-003","nombre":"Kiosco Terraza","descripcion":"Terminal portátil para la terraza","activo":true}'Python:
import httpxresp = httpx.post("http://localhost/api/terminales/", json={"codigo": "CAJA-003", "nombre": "Kiosco Terraza", "descripcion": "Terminal portátil para la terraza", "activo": True}, headers={"Authorization": f"Bearer :token"})terminal = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/terminales/', { method: 'POST', headers: {'Content-Type': 'application/json', 'Authorization': `Bearer $:token`}, body: JSON.stringify({codigo: 'CAJA-003', nombre: 'Kiosco Terraza', descripcion: 'Terminal portátil para la terraza', activo: true})});const terminal = await resp.json();<MethodBadge method="PUT" /> /api/terminales/:terminal_id [NUEVO]
Descripción: Actualiza un terminal existente. Todos los campos son opcionales.
Requiere Auth: Sí
Permiso requerido: terminales, actualizar
Request: (solo enviar campos a modificar)
{ "nombre": "Caja Principal Renovada", "descripcion": "Terminal renovada"}Respuesta exitosa: 200 OK
Errores: 404 (no encontrado), 409 (código duplicado)
cURL:
curl -X PUT http://localhost/api/terminales/1 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"nombre":"Caja Principal Renovada","descripcion":"Terminal renovada"}'<MethodBadge method="DELETE" /> /api/terminales/:terminal_id [NUEVO]
Descripción: Elimina un terminal definitivamente
Requiere Auth: Sí
Permiso requerido: terminales, eliminar
Respuesta exitosa: 204 No Content
Errores: 404 (no encontrado)
cURL:
curl -X DELETE http://localhost/api/terminales/1 -H "Authorization: Bearer <token>"<MethodBadge method="PATCH" /> /api/terminales/:terminal_id/toggle [NUEVO]
Descripción: Activa o desactiva un terminal sin necesidad de enviar el objeto completo. Invierte el valor actual del campo activo.
Requiere Auth: Sí
Permiso requerido: terminales, actualizar
Contexto de uso: Este endpoint está diseñado para switches de interfaz (toggle on/off). El frontend no necesita conocer el estado actual; el servidor lo invierte automáticamente.
Respuesta exitosa: 200 OK
Errores: 404 (no encontrado)
Response:
{ "id": 1, "codigo": "CAJA-001", "nombre": "Caja Principal", "descripcion": "Terminal de la caja principal del restaurante", "activo": false, "tenant_id": "uuid-tenant"}cURL:
curl -X PATCH http://localhost/api/terminales/1/toggle -H "Authorization: Bearer <token>"Python:
import httpxresp = httpx.patch("http://localhost/api/terminales/1/toggle", headers={"Authorization": f"Bearer :token"})terminal = resp.json()JavaScript:
const resp = await fetch('http://localhost/api/terminales/1/toggle', { method: 'PATCH', headers: {'Authorization': `Bearer $:token`}});const terminal = await resp.json();Heartbeat del terminal
<MethodBadge method="POST" /> /api/terminales/heartbeat [NUEVO]
Descripción: Señal de vida del terminal POS. Actualiza Terminal.ultimo_sync del terminal autenticado (tomado del JWT terminal_id). El cliente POS debe llamarlo periódicamente (ej. cada 30–60 s) para que el panel de salud (GET /api/admin/tenants/health) refleje el estado real En línea / Desconectado del tenant.
Contexto de uso: El login y los endpoints /api/sync/push y /api/sync/pull ya actualizan la señal automáticamente (con throttle para no escribir en cada request). Este endpoint sirve para periodos de inactividad operativa donde el POS no envía eventos pero sigue conectado.
Requiere Auth: Sí
Permiso requerido: cualquier usuario autenticado con terminal_id en su JWT
Parámetros: sin body — se usa terminal_id y tenant_id del JWT.
Respuesta exitosa: 200 OK
{ "status": "ok", "registrado": true, "terminal_id": "CAJA-1", "tenant_id": "uuid-tenant", "ultimo_sync": "2026-08-16T10:41:40"}Errores: 401 (sin token), 422 (el JWT no trae terminal_id)
cURL:
curl -X POST http://localhost/api/terminales/heartbeat -H "Authorization: Bearer <token>"