Skip to content

Terminales

Terminales

Prefijo: /api/terminales
Requiere Auth:
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:
Permiso requerido: terminales, leer

Parámetros query:

ParámetroTipoObligatorioDescripción
skipintNúmero de registros a saltar (default 0)
limitintMáximo de registros a retornar (default 100)
tenant_idUUIDSolo 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:

Terminal window
curl "http://localhost/api/terminales/?skip=0&limit=20" \
-H "Authorization: Bearer <token>"

Python:

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

Terminal window
curl http://localhost/api/terminales/1 -H "Authorization: Bearer <token>"

<MethodBadge method="POST" /> /api/terminales/ [NUEVO]

Descripción: Crea un nuevo terminal
Requiere Auth:
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:

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

Terminal window
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:
Permiso requerido: terminales, eliminar

Respuesta exitosa: 204 No Content
Errores: 404 (no encontrado)

cURL:

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

Terminal window
curl -X PATCH http://localhost/api/terminales/1/toggle -H "Authorization: Bearer <token>"

Python:

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

Terminal window
curl -X POST http://localhost/api/terminales/heartbeat -H "Authorization: Bearer <token>"