Skip to content

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:

CampoTipoDescripción
cpu_percentfloatUso de CPU en porcentaje (0-100)
memory.total_bytesintRAM total en bytes
memory.used_bytesintRAM usada en bytes
memory.available_bytesintRAM disponible en bytes
memory.percentfloatPorcentaje de RAM usada
disk.total_bytesintEspacio total del disco en bytes
disk.used_bytesintEspacio usado en bytes
disk.free_bytesintEspacio libre en bytes
disk.percentfloatPorcentaje de disco usado
uptime_secondsintSegundos desde que arrancó el servidor
python_versionstringVersión de Python
platformstringSistema operativo
cloud_enabledboolSi PostgreSQL cloud está habilitado
sync_runningboolSi el SyncOrchestrator está activo

cURL:

Terminal window
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.0

GET /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:

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

Terminal window
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ámetroTipoObligatorioDescripción
skipintNúmero de registros a saltar (default 0)
limitintMáximo de registros a retornar (default 100)
resueltaboolFiltrar por estado
tenant_idUUIDFiltrar por tenant
nivelstringFiltrar 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ámetroTipoObligatorioDescripción
resueltaboolFiltrar por estado
tenant_idUUIDFiltrar por tenant
nivelstringFiltrar 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:

  1. Soporte proactivo: detectar antes de que el cliente llame qué tenant tiene problemas (fuera de línea, sync pendiente alto, incidentes/alertas abiertas).
  2. 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):

PrioridadCondiciónNivelDescripción (porqué)
1estado != "ACTIVO" (SUSPENDIDO/CANCELADO/etc.)CRITICAL«Tenant desactivado: no puede operar»
2sync_pendiente > 100 cambios sin procesarCRITICAL«Cola de sincronización atascada: los datos no llegan a la nube»
3en_linea == false Y sin ventas en los últimos 3 díasWARNING«Sin conexión y sin actividad reciente: probable terminal caído o tenant sin configurar»
4incidentes_abiertos > 0 o alertas_abiertas > 0WARNING«Incidentes de negocio o alertas de stock sin resolver»
50 < dias_para_expirar <= 15WARNING«Plan por vencer en ≤ 15 días»
6Ninguna condición anteriorHEALTHY«Tenant operando normalmente»

Variables y umbrales que alimentan el cálculo (constantes en admin.py:229-232):

VariableDefiniciónFuente
estadoACTIVO / PROBANDO / SUSPENDIDO / etc.tabla tenants
en_lineaal menos un terminal activo con ultimo_sync en los últimos 5 minutos (ONLINE_AFTER)tabla terminal
sync_pendientecambios en _sync_outbox con processed_at IS NULLtabla _sync_outbox
tienes_actividadal menos una venta no eliminada en los últimos 3 díastabla ventas
incidentes_abiertoslogs tipo_movimiento = 'INCIDENTE' con resuelto = falsetabla movimientos_log
alertas_abiertasalertas de stock con resuelta = falsetabla alertas_stock
dias_para_expirarfecha_expiracion - hoy (Colombia, UTC-5); null si no tiene expiracióntabla 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ámetroTipoObligatorioDescripción
estadostringFiltrar 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:

CampoTipoDescripción
saludstringHEALTHY | WARNING | CRITICAL
en_lineaboolHeartbeat reciente (última sync ≤ 5 min)
dias_para_expirarint/nullDías hasta la expiración del tenant (None si no vence)
sync_pendienteintCambios pendientes de sincronizar a la nube
motivos_saludlist<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ámetroTipoObligatorioDescripción
tenant_idUUID (path)Tenant a consultar
diasint (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:
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ámetroTipoObligatorioDescripción
tenant_idUUID (path)Tenant a analizar
diasint (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:

CampoTipoDescripción
usuariostringNombre del usuario (SIN_USUARIO si no tiene usuario asignado)
ventasintNº de ventas no eliminadas del período
total_ventasintSuma de totales de ventas del período
cierres_cajaintNº de jornadas cerradas del usuario
total_cierresintSuma de montos de cierre (Caja.cierre) — el dinero total que el cajero manejó en sus cierres
aperturas_cajaintNº de aperturas de caja del usuario
base_inicial_totalintSuma de bases iniciales de todas las jornadas del usuario
egresosintNº de movimientos tipo EGRESO registrados por el usuario
total_egresosintMonto total de los egresos
ingresosintNº de movimientos tipo INGRESO registrados por el usuario
total_ingresosintMonto total de los ingresos
abonosintNº de abonos a créditos realizados por el usuario
total_abonosintMonto de los abonos a créditos
ultimo_cierredatetime | nullFecha/hora del cierre más reciente del usuario
ultimo_cierre_montoint | nullMonto del último cierre
ultima_actividaddatetime | nullFecha/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:
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ámetroTipoObligatorioDescripción
tenant_idUUID (path)Tenant a analizar
diasint (query)Días hacia atrás (default 14, rango 1..90)
usuariostring (query)Filtra por nombre de usuario/cajero

Fórmulas de conciliación:

  • efectivo_esperado = base_inicial + ventas_efectivo_jornada + ingresos − gastos − vueltos
  • diferencia_efectivo = Caja.efectivo − efectivo_esperado
  • estado_conciliacion: CUADRADO si |diferencia| <= umbral; SOBRANTE si diferencia > umbral; FALTANTE si diferencia < −umbral
  • alerta=true cuando estado != CUADRADO (umbral Configuracion.caja_alerta_diferencia)
  • estimado=true cuando la jornada no tiene fila en caja (cierre de versión antigua): montos calculados desde jornada_caja y pagos_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:

CampoTipoDescripción
usuariostringNombre del usuario (SIN_USUARIO si no tiene usuario asignado)
jornadas[]arrayJornadas cerradas del usuario en el rango
totalesobjectAgregados: ventas, cierres, gastos, ingresos, diferencia_efectivo, jornadas
id_jornadaintID de la jornada de caja
apertura / cierredatetime | nullFechas de apertura/cierre (Colombia, UTC-5)
base_inicialintBase inicial con la que abrió caja
total_ventasintTotal vendido en la jornada
recibido_totalintbase_inicial + total_ventas
vueltosintVueltos dados al cliente
monto_cierreintMonto declarado al cerrar
efectivo / transferencia / creditointDesglose de pagos (desde pagos_venta si no hay fila caja)
gastos / ingresosintEgresos e ingresos de la jornada
efectivo_esperadointEfectivo que debería haber según la fórmula
diferencia_efectivointefectivo − efectivo_esperado (negativo = faltante)
estado_conciliacionstringCUADRADO / SOBRANTE / FALTANTE
alertabooltrue si la diferencia supera el umbral
estimadobooltrue 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)