Admin Endpoints
Admin Endpoints
Prefijo: /api
Requiere Auth: Sí (solo Superadmin rol=3)
Permiso requerido: dashboard, leer o configuracion, leer
Endpoints de administración del sistema. Estos endpoints son exclusivos para usuarios con rol Superadmin (rol=3) y proporcionan métricas del servidor, base de datos y visión general del sistema multi-tenant.
GET /api/system/metrics
Descripción: Métricas del servidor en tiempo real: CPU, RAM, disco, uptime, versiones
Requiere Auth: Sí (rol=3)
Permiso requerido: N/A (solo Superadmin)
Respuesta exitosa: 200 OK
Errores: 403 (no es Superadmin)
Response:
{ "cpu_percent": 23.5, "memory": { "total_bytes": 8589934592, "used_bytes": 4294967296, "available_bytes": 4294967296, "percent": 50.0 }, "disk": { "total_bytes": 107374182400, "used_bytes": 53687091200, "free_bytes": 53687091200, "percent": 50.0 }, "uptime_seconds": 604800, "python_version": "3.12.0", "platform": "linux", "cloud_enabled": true, "sync_running": false}Campos:
| Campo | Tipo | Descripción |
|---|---|---|
cpu_percent | float | Uso de CPU en porcentaje (0-100) |
memory.total_bytes | int | RAM total en bytes |
memory.used_bytes | int | RAM usada en bytes |
memory.available_bytes | int | RAM disponible en bytes |
memory.percent | float | Porcentaje de RAM usada |
disk.total_bytes | int | Espacio total del disco en bytes |
disk.used_bytes | int | Espacio usado en bytes |
disk.free_bytes | int | Espacio libre en bytes |
disk.percent | float | Porcentaje de disco usado |
uptime_seconds | int | Segundos desde que arrancó el servidor |
python_version | string | Versión de Python |
platform | string | Sistema operativo |
cloud_enabled | bool | Si PostgreSQL cloud está habilitado |
sync_running | bool | Si el SyncOrchestrator está activo |
cURL:
curl http://localhost/api/system/metrics -H "Authorization: Bearer <token>"JavaScript:
const resp = await fetch('http://localhost/api/system/metrics', { headers: {'Authorization': `Bearer $:token`}});const metrics = await resp.json();// metrics.memory.percent → 50.0GET /api/system/db/metrics
Descripción: Métricas de la base de datos: conteo de registros por tabla
Requiere Auth: Sí (rol=3)
Permiso requerido: N/A (solo Superadmin)
Respuesta exitosa: 200 OK
Response:
{ "database": "postgresql", "tables": { "ventas": 15000, "productos": 500, "clientes": 1200, "usuarios": 45, "tenants": 15, "creditos": 320 }}cURL:
curl http://localhost/api/system/db/metrics -H "Authorization: Bearer <token>"GET /api/admin/dashboard/overview
Descripción: Visión general cross-tenant para Superadmin: tenants activos, usuarios, ventas del día/mes, sync pendiente
Requiere Auth: Sí (rol=3)
Permiso requerido: dashboard, leer
Respuesta exitosa: 200 OK
Response:
{ "tenants_activos": 15, "tenants_inactivos": 3, "tenants_proximos_a_expirar": [ { "id": "uuid", "nombre": "Tenant Ejemplo", "fecha_expiracion": "2026-08-15", "dias_restantes": 22 } ], "total_usuarios": 45, "ventas_hoy": 128, "ventas_totales_hoy": 4500000, "ventas_mes": 3850, "ventas_totales_mes": 135000000, "sync_pendiente_global": 42, "estado_sistema": { "cloud_connected": true, "sync_queue_ok": true, "uptime_horas": 168 }}cURL:
curl http://localhost/api/admin/dashboard/overview -H "Authorization: Bearer <token>"GET /api/admin/alertas-seguridad [NUEVO]
Descripción: Bandeja de soporte: todas las alertas de seguridad (fuerza bruta) de TODOS los tenants
Requiere Auth: Sí (rol=3)
Permiso requerido: N/A (solo Superadmin, bypass RBAC)
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) |
resuelta | bool | ❌ | Filtrar por estado |
tenant_id | UUID | ❌ | Filtrar por tenant |
nivel | string | ❌ | Filtrar por nivel: MEDIA, CRITICA |
Respuesta exitosa: 200 OK — lista de alertas (formato en Alertas de Seguridad)
Errores: 403 (no es Superadmin)
GET /api/admin/alertas-seguridad/count [NUEVO]
Descripción: Conteo global de alertas de seguridad de todos los tenants
Requiere Auth: Sí (rol=3)
Permiso requerido: N/A (solo Superadmin)
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
resuelta | bool | ❌ | Filtrar por estado |
tenant_id | UUID | ❌ | Filtrar por tenant |
nivel | string | ❌ | Filtrar por nivel |
Respuesta exitosa: 200 OK
{ "total": 42, "pendientes": 7, "resueltas": 35}PATCH /api/admin/alertas-seguridad/:alerta_id/resolver [NUEVO]
Descripción: Resuelve una alerta de seguridad de cualquier tenant (soporte)
Requiere Auth: Sí (rol=3)
Permiso requerido: N/A (solo Superadmin)
Parámetros: alerta_id (path, int)
Respuesta exitosa: 200 OK — alerta con resuelta: true y resuelta_por
Errores: 403 (no es Superadmin), 404 (alerta no encontrada)
Contexto y caso de uso — Salud de Tenants
Panel de salud cross-tenant exclusivo para Superadmin / Soporte (rol=3, bypass RBAC). Proporciona una vista operativa de TODOS los tenants desde un solo lugar: qué tenant está caído, cuál tiene sync atascado, cuál está por vencer, cuál acumula incidentes o alertas de stock abiertas.
Es información operativa (salud y métricas de uso), no de negocio (no expone ventas, clientes ni detalles financieros por tenant). Resuelve dos necesidades:
- Soporte proactivo: detectar antes de que el cliente llame qué tenant tiene problemas (fuera de línea, sync pendiente alto, incidentes/alertas abiertas).
- Renovaciones: ver qué tenants están próximos a expirar su plan para gestionar la renovación.
Los 3 endpoints se complementan: la vista cross-tenant es la tabla del panel; el detalle es la fila de un tenant al hacer clic; las métricas de uso alimentan la gráfica diaria de ventas.
Niveles de salud — qué datos definen cada estado y su descripción
El estado salud se calcula en el backend con la función _nivel_salud() de app/routers/admin.py, evaluando estas variables por tenant en orden de prioridad (gana la primera que se cumpla):
| Prioridad | Condición | Nivel | Descripción (porqué) |
|---|---|---|---|
| 1 | estado != "ACTIVO" (SUSPENDIDO/CANCELADO/etc.) | CRITICAL | «Tenant desactivado: no puede operar» |
| 2 | sync_pendiente > 100 cambios sin procesar | CRITICAL | «Cola de sincronización atascada: los datos no llegan a la nube» |
| 3 | en_linea == false Y sin ventas en los últimos 3 días | WARNING | «Sin conexión y sin actividad reciente: probable terminal caído o tenant sin configurar» |
| 4 | incidentes_abiertos > 0 o alertas_abiertas > 0 | WARNING | «Incidentes de negocio o alertas de stock sin resolver» |
| 5 | 0 < dias_para_expirar <= 15 | WARNING | «Plan por vencer en ≤ 15 días» |
| 6 | Ninguna condición anterior | HEALTHY | «Tenant operando normalmente» |
Variables y umbrales que alimentan el cálculo (constantes en admin.py:229-232):
| Variable | Definición | Fuente |
|---|---|---|
estado | ACTIVO / PROBANDO / SUSPENDIDO / etc. | tabla tenants |
en_linea | al menos un terminal activo con ultimo_sync en los últimos 5 minutos (ONLINE_AFTER) | tabla terminal |
sync_pendiente | cambios en _sync_outbox con processed_at IS NULL | tabla _sync_outbox |
tienes_actividad | al menos una venta no eliminada en los últimos 3 días | tabla ventas |
incidentes_abiertos | logs tipo_movimiento = 'INCIDENTE' con resuelto = false | tabla movimientos_log |
alertas_abiertas | alertas de stock con resuelta = false | tabla alertas_stock |
dias_para_expirar | fecha_expiracion - hoy (Colombia, UTC-5); null si no tiene expiración | tabla tenants |
Importante: los endpoints NO exponen datos de negocio; solo métricas operativas agregadas. La fecha/hora de ultima_venta, ultimo_inicio y por_dia[].fecha están en hora Colombia (UTC-5), nunca UTC.
GET /api/admin/tenants/health [NUEVO]
Descripción: Visión de salud cross-tenant (Sprint 5): estado, último inicio, en línea, sync pendiente, incidentes/alertas abiertas y nivel de salud calculado para todos los tenants. Es el endpoint principal del panel: devuelve el listado completo más los totales agregados (saludables / warning / críticos).
Caso de uso: soporte revisa de un vistazo cuántos tenants están en CRITICAL o WARNING, filtra por estado y hace clic en una fila para el detalle.
Requiere Auth: Sí (rol=3)
Permiso requerido: N/A (solo Superadmin)
Parámetros query:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
estado | string | ❌ | Filtrar por estado: ACTIVO, PROBANDO, SUSPENDIDO |
Respuesta exitosa: 200 OK
{ "total": 15, "saludables": 11, "con_warning": 3, "criticos": 1, "entries": [ { "tenant_id": "uuid", "codigo": "PROD01", "nombre": "Tenant Ejemplo", "estado": "ACTIVO", "plan": "PRO", "dias_para_expirar": 22, "usuarios": 5, "terminales": 2, "en_linea": true, "sesiones_abiertas": 1, "sync_pendiente": 0, "incidentes_abiertos": 0, "alertas_abiertas": 0, "ultima_venta": "2026-08-13T10:00:00", "ultimo_inicio": "2026-08-13T08:30:00", "salud": "HEALTHY", "motivos_salud": ["Tenant operando normalmente"] } ]}Campos:
| Campo | Tipo | Descripción |
|---|---|---|
salud | string | HEALTHY | WARNING | CRITICAL |
en_linea | bool | Heartbeat reciente (última sync ≤ 5 min) |
dias_para_expirar | int/null | Días hasta la expiración del tenant (None si no vence) |
sync_pendiente | int | Cambios pendientes de sincronizar a la nube |
motivos_salud | list<string> | Porqué del estado — descripciones de las condiciones que detonan el nivel (para mostrar directamente en el panel de soporte) |
Niveles de salud y porqué (orden de prioridad): ver tabla «Niveles de salud» arriba. Uso operativo: un sync_pendiente > 100 es CRITICAL (datos no llegan a la nube); incidentes_abiertos > 0 o alertas_abiertas > 0 es WARNING; estado != ACTIVO es CRITICAL.
Errores: 403 (no es Superadmin)
GET /api/admin/tenants/:tenant_id/health [NUEVO]
Descripción: Detalle de salud de un solo tenant (Sprint 5): mismos campos que la vista cross-tenant pero recalculados en tiempo real solo para el tenant indicado.
Caso de uso: al hacer clic en una fila del panel de salud, se recarga el detalle de ese tenant para ver con precisión por qué está en WARNING/CRITICAL (por ejemplo, si fue por sync pendiente o por incidentes abiertos).
Requiere Auth: Sí (rol=3)
Permiso requerido: N/A (solo Superadmin)
Parámetros: tenant_id (path, UUID)
Respuesta exitosa: 200 OK — objeto TenantHealthEntry (mismo formato y campos que el entry de la vista cross-tenant, incluyendo motivos_salud con el porqué del estado)
Errores: 403 (no es Superadmin), 404 (tenant no encontrado)
GET /api/admin/tenants/:tenant_id/usage [NUEVO]
Descripción: Métricas de uso del sistema para un tenant (Sprint 5): serie diaria de ventas (count y total), ticket promedio, y totales acumulados de usuarios/productos/clientes/créditos.
Caso de uso: la gráfica de actividad del tenant muestra si está operando a diario (curva de ventas) y cuánto volumen mueve; los totales ayudan a dimensionar el tenant.
Requiere Auth: Sí (rol=3)
Permiso requerido: N/A (solo Superadmin)
Parámetros:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
tenant_id | UUID (path) | ✅ | Tenant a consultar |
dias | int (query) | ❌ | Días de la serie (default 14, máx 90) |
Respuesta exitosa: 200 OK
{ "tenant_id": "uuid", "codigo": "PROD01", "nombre": "Tenant Ejemplo", "usuarios_totales": 5, "productos": 248, "clientes": 320, "creditos": 12, "por_dia": [ { "fecha": "2026-07-31", "ventas": 18, "total_ventas": 450000, "ticket_promedio": 25000.0, "transacciones": 18 } ]}Nota: por_dia incluye un elemento por cada día del rango (días sin ventas vienen con ventas: 0); las fechas usan hora Colombia (UTC-5), formato ISO YYYY-MM-DD; transacciones es igual a ventas (reservado para futuras métricas).
Errores: 403 (no es Superadmin), 404 (tenant no encontrado)
GET /api/admin/tenants/:tenant_id/activity [NUEVO]
Descripción: Análisis de transacciones del tenant por usuario (Sprint 5 + Fase 2 auditoría): agrega por usuario las ventas (cantidad y total_ventas), los cierres de caja (cierres_caja y total_cierres — monto fiscal de cierres), las aperturas de caja (aperturas_caja y base_inicial_total), los egresos (egresos y total_egresos), los ingresos (ingresos y total_ingresos), los abonos a créditos (abonos y total_abonos), el último cierre (ultimo_cierre y ultimo_cierre_monto) y la fecha de ultima_actividad.
Caso de uso: el superadmin (o el admin del tenant para su propio tenant) quiere saber qué usuarios están operando, cuánto dinero maneja cada uno y si hubo movimientos de ingreso/egreso inusuales. Los montos se leen de las tablas de negocio (ventas, jornada_caja, caja, egresos, creditos_movimientos), no de los logs de auditoría.
Requiere Auth: Sí
Permiso requerido: Superadmin (rol=3) → cualquier tenant; Administrador (rol=1) → solo SU tenant (tenant_id del JWT == tenant_id del path). Asistente (rol=2) u otro tenant → 403. No consulta permisos_rol.
Parámetros:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
tenant_id | UUID (path) | ✅ | Tenant a analizar |
dias | int (query) | ❌ | Días hacia atrás a analizar (default 14, máx 90) |
Respuesta exitosa: 200 OK
{ "tenant_id": "uuid", "codigo": "PROD01", "nombre": "Tenant Ejemplo", "desde": "2026-08-03", "hasta": "2026-08-16", "por_usuario": [ { "usuario": "ana", "ventas": 18, "total_ventas": 450000, "cierres_caja": 3, "total_cierres": 1350000, "aperturas_caja": 3, "base_inicial_total": 600000, "egresos": 2, "total_egresos": 60000, "ingresos": 1, "total_ingresos": 40000, "abonos": 1, "total_abonos": 50000, "ultimo_cierre": "2026-08-15T19:45:00", "ultimo_cierre_monto": 480000, "ultima_actividad": "2026-08-15T19:45:00" } ]}Campos por usuario:
| Campo | Tipo | Descripción |
|---|---|---|
usuario | string | Nombre del usuario (SIN_USUARIO si no tiene usuario asignado) |
ventas | int | Nº de ventas no eliminadas del período |
total_ventas | int | Suma de totales de ventas del período |
cierres_caja | int | Nº de jornadas cerradas del usuario |
total_cierres | int | Suma de montos de cierre (Caja.cierre) — el dinero total que el cajero manejó en sus cierres |
aperturas_caja | int | Nº de aperturas de caja del usuario |
base_inicial_total | int | Suma de bases iniciales de todas las jornadas del usuario |
egresos | int | Nº de movimientos tipo EGRESO registrados por el usuario |
total_egresos | int | Monto total de los egresos |
ingresos | int | Nº de movimientos tipo INGRESO registrados por el usuario |
total_ingresos | int | Monto total de los ingresos |
abonos | int | Nº de abonos a créditos realizados por el usuario |
total_abonos | int | Monto de los abonos a créditos |
ultimo_cierre | datetime | null | Fecha/hora del cierre más reciente del usuario |
ultimo_cierre_monto | int | null | Monto del último cierre |
ultima_actividad | datetime | null | Fecha/hora (Colombia, UTC-5) de la acción más reciente |
Nota: la ventana se calcula como hoy − (dias − 1) con fecha hora Colombia (UTC-5); si un usuario no realizó ninguna transacción del período no aparece en el listado; los montos se suman sin signo (los egresos son gastos, los ingresos son entradas de dinero, los abonos son pagos de clientes).
Errores: 403 (rol no autorizado o tenant ajeno), 404 (tenant no encontrado)
GET /api/admin/tenants/:tenant_id/balance-caja [NUEVO]
Descripción: Balance de caja detallado por cajero/usuario de un tenant: jornadas cerradas con montos reales (base inicial, total vendido, recibido, vueltos, efectivo, transferencia, crédito, gastos, ingresos) y conciliación de efectivo esperado vs declarado. Alimenta el panel de Usuarios del AdminPanel (apartado de balance por usuario) y sirve al superadmin para auditar cualquier tenant.
Caso de uso: el superadmin (o el admin de su propio tenant) identifica riesgos de caja: un cajero cuyo efectivo declarado no coincide con el esperado (sobrantes/faltantes).
Requiere Auth: Sí
Permiso requerido: Superadmin (rol=3) → cualquier tenant; Administrador (rol=1) → solo SU tenant (tenant_id del JWT == tenant_id del path). Asistente (rol=2) u otro tenant → 403. No consulta permisos_rol.
Parámetros:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
tenant_id | UUID (path) | ✅ | Tenant a analizar |
dias | int (query) | ❌ | Días hacia atrás (default 14, rango 1..90) |
usuario | string (query) | ❌ | Filtra por nombre de usuario/cajero |
Fórmulas de conciliación:
efectivo_esperado = base_inicial + ventas_efectivo_jornada + ingresos − gastos − vueltosdiferencia_efectivo = Caja.efectivo − efectivo_esperadoestado_conciliacion:CUADRADOsi|diferencia| <= umbral;SOBRANTEsidiferencia > umbral;FALTANTEsidiferencia < −umbralalerta=truecuandoestado != CUADRADO(umbralConfiguracion.caja_alerta_diferencia)estimado=truecuando la jornada no tiene fila encaja(cierre de versión antigua): montos calculados desdejornada_cajaypagos_venta
Respuesta exitosa: 200 OK
{ "tenant_id": "uuid", "codigo": "PROD01", "nombre": "Tenant Ejemplo", "desde": "2026-08-03", "hasta": "2026-08-16", "umbral_alerta": 10000, "usuario_filtro": null, "por_usuario": [ { "usuario": "ana", "jornadas": [ { "id_jornada": 42, "usuario": "ana", "apertura": "2026-08-15T07:00:00", "base_inicial": 200000, "total_ventas": 450000, "recibido_total": 480000, "vueltos": 30000, "cierre": "2026-08-15T19:45:00", "monto_cierre": 610000, "efectivo": 350000, "transferencia": 120000, "credito": 60000, "gastos": 50000, "ingresos": 20000, "efectivo_esperado": 540000, "diferencia_efectivo": -190000, "estado_conciliacion": "FALTANTE", "alerta": true, "estimado": false } ], "totales": { "ventas": 18, "cierres": 1, "gastos": 2, "ingresos": 1, "diferencia_efectivo": -190000, "jornadas": 1 } } ]}Campos:
| Campo | Tipo | Descripción |
|---|---|---|
usuario | string | Nombre del usuario (SIN_USUARIO si no tiene usuario asignado) |
jornadas[] | array | Jornadas cerradas del usuario en el rango |
totales | object | Agregados: ventas, cierres, gastos, ingresos, diferencia_efectivo, jornadas |
id_jornada | int | ID de la jornada de caja |
apertura / cierre | datetime | null | Fechas de apertura/cierre (Colombia, UTC-5) |
base_inicial | int | Base inicial con la que abrió caja |
total_ventas | int | Total vendido en la jornada |
recibido_total | int | base_inicial + total_ventas |
vueltos | int | Vueltos dados al cliente |
monto_cierre | int | Monto declarado al cerrar |
efectivo / transferencia / credito | int | Desglose de pagos (desde pagos_venta si no hay fila caja) |
gastos / ingresos | int | Egresos e ingresos de la jornada |
efectivo_esperado | int | Efectivo que debería haber según la fórmula |
diferencia_efectivo | int | efectivo − efectivo_esperado (negativo = faltante) |
estado_conciliacion | string | CUADRADO / SOBRANTE / FALTANTE |
alerta | bool | true si la diferencia supera el umbral |
estimado | bool | true si los montos son estimados (sin fila caja) |
Nota: fechas/horas en hora Colombia (UTC-5). diferencia_efectivo negativa = faltante (riesgo), positiva = sobrante. recibido_total = base_inicial + total_ventas.
Errores: 403 (rol no autorizado o tenant ajeno), 404 (tenant no encontrado)