Cockpit (panel de operación)¶
El Cockpit es la cara operativa del backend: un conjunto de endpoints REST de monitoreo y de operación, más una interfaz web que los consume para responder, de un vistazo, ¿está vivo el enlace con AFIP?, ¿cuánto se está emitiendo y con qué tasa de éxito? y ¿qué quedó pendiente de resolver?. Para el por qué del ciclo CAE/CAEA que estas métricas reflejan, mirá El ciclo de facturación; acá están los datos exactos.
La UI es una SPA estática (src/main/resources/static/cockpit.html, ~1180 líneas, con el micro-framework reactivo Arrow.js — sin build, sin React/Thymeleaf), servida directamente por Spring. Hace polling a /api/cockpit/* cada ~5 s. WebConfig la mapea a tres rutas que cambian el modo desde la misma página:
| Ruta | Modo | Runner de escenarios |
|---|---|---|
/cockpit y /cockpit-test |
Test (ambiente de pruebas) | Visible |
/cockpit-prod |
Prod (solo monitoreo) | Oculto |
La interfaz¶
Vista general (modo monitoreo), con datos reales del ambiente de prueba:

Arriba de todo, la fila birds-eye resume la salud del sistema de un vistazo: cada sucursal con sus POS y su KPI, el gateway en el medio y, a la derecha, el estado del enlace con ARCA/AFIP (online/offline y vigencia del token de acceso):

En modo test, el runner de escenarios permite ejecutar operaciones contra AFIP homologación (obtener token, dummy, consultar último comprobante, emitir un CAE de prueba, etc.) y ver el XML SOAP del tráfico:

Estas capturas son reales
Se tomaron del cockpit corriendo en http://localhost:8080/cockpit contra AFIP homologación. Para regenerarlas, levantá el backend (ver Setup del entorno) y corré el script de captura Playwright de e2e/.
Todo cuelga del paquete com.tipre.tifacturaonlinemanager.cockpit. Hay tres bloques bien diferenciados:
| Bloque | Controller | Base path | Naturaleza |
|---|---|---|---|
| Monitoreo | CockpitController |
/api/cockpit |
Solo lectura: métricas, estado AFIP, backlog, KPIs, historia. |
| ABM fiscal | CockpitAdminController |
/api/cockpit/sucpospv |
Escritura de SucPosPV, protegido por secret. |
| Test runner | CockpitTestRunnerController |
/api/cockpit/test |
Acciones contra AFIP homologación (algunas emiten de verdad). |
El Cockpit hoy NO tiene auth real
La autenticación/autorización seria es SPEC-014 (todavía en scaffold). El único candado activo es el secret del ABM (ver abajo). Los endpoints de monitoreo y el test runner quedan abiertos a quien llegue al puerto. Mientras SPEC-014 no esté, no expongas el /api/cockpit/* a una red no confiable.
1. Monitoreo — CockpitController¶
@RestController en /api/cockpit. Pensado para polling cada pocos segundos. Combina dos fuentes de verdad:
- Métricas en memoria (
CockpitMetrics): contadores OK/error por operación AFIP, vivos, thread-safe, que se pierden al reiniciar. Es monitoreo operativo, no auditoría. - Datos de la DB (
CockpitPendientes,CockpitPorPos,CockpitAfipStatus, etc.): backlog, stats por POS y estado del TA.
Endpoints¶
| Método | Ruta | Qué devuelve |
|---|---|---|
| GET | /api/cockpit/summary |
afip (modo/endpoints) + ops (contadores por operación con tasa de éxito) + recentErrors. El widget principal. |
| GET | /api/cockpit/errors |
Solo los últimos errores en memoria (buffer acotado a 50). |
| GET | /api/cockpit/pendientes |
Backlog DB: pendientesDeCae, conError, autorizados. |
| GET | /api/cockpit/por-pos?desde=&hasta= |
Comprobantes por PV × modalidad (CAE/CAEA) × período. desde/hasta en yyyyMMdd, opcionales. |
| GET | /api/cockpit/afip-status |
Estado del enlace: online (FEDummy), ta (token/expiración) y facturador (CUIT/razón social). |
| GET | /api/cockpit/ultimas-trx?n=5 |
Feed de las últimas N transacciones (operación, tipo, resultado, PV, fecha-hora). |
| GET | /api/cockpit/jobs |
Grilla de jobs: 1 fila por Tarea con su última y próxima ejecución. Ver Jobs Quartz. |
| GET | /api/cockpit/errores?tipo=&desde=&hasta=&max=200 |
Errores de transacción (POS→servicio y servicio→ARCA) con filtros. desde/hasta en yyyy-MM-dd. |
| GET | /api/cockpit/pos-stats |
Stats por POS físico (nroSuc, nroPos): caeOk, caeError, reprocesos. Solo Trx con version='V2' y tipofacturacion='CAE'. |
| GET | /api/cockpit/server-stats |
Totales del server agregando todos los POS. |
| GET | /api/cockpit/kpi |
KPI global: kpiPct = caeOk·100 / (caeOk+caeError+reprocesos) + arcaOnline. |
| GET | /api/cockpit/ranking |
Top-10 peores POS por KPI (ascendente; a igualdad, más errores+reprocesos primero). |
| GET | /api/cockpit/pos-trx?suc=&pos=&kind= |
Drill-down (máx. 25, id desc) de un POS. kind ∈ ok | error | reproceso; cualquier otro valor → lista vacía. |
| POST | /api/cockpit/stats-snapshot |
Trigger manual de snapshot (inserta uno ahora sin esperar el job de los 5 min). Útil para testear la historia. |
| GET | /api/cockpit/stats-history?hours=24 |
Historia de KPI en la ventana: arcaUptimePct, serverTrend[], posCount. Límite ~288 puntos (24 h a 5 min). |
De dónde sale cada número
pos-stats, server-stats, kpi, ranking y pos-trx se calculan agrupando en Java las filas Trx (no con GROUP BY JPQL) a propósito: el código lo documenta como decisión de máxima compatibilidad de dialecto entre H2 (tests) y SQL Server (runtime). Ver Persistencia.
Métricas en memoria (CockpitMetrics)¶
Las operaciones que se cuentan están en el enum CockpitMetrics.Op:
| Op | Operación AFIP |
|---|---|
TOKEN |
WSAA LoginCms |
CAE |
WSFE FECAESolicitar |
CAEA |
CAEA (solicitud/consulta) |
ULTIMO_CBTE |
FECompUltimoAutorizado |
CONSULTA |
FECompConsultar |
NOTIFICACION |
Cada OpStat expone ok, error, total y successRate (0..100; 100 si no hubo intentos). El buffer de errores recientes está acotado a 50 entradas (recentErrors, más reciente primero). Los clientes AFIP (WSAA/WSFE) alimentan estos contadores al ejecutar cada operación; además, recordError persiste el error vía TrxErrorRegistro para que la grilla /errores lo vea aunque el contador en memoria se haya reseteado.
Estado AFIP (CockpitAfipStatus)¶
/afip-status arma tres bloques:
online— pingFEDummya WSFE (no requiere TA). Cacheado 60 s (DUMMY_TTL_MS) para no martillar a AFIP. Reportaappserver/dbserver/authserverycheckedAt.ta— si elEnteFacturadorhabilitado tiene token, cuándo expira (expira), si estávigenteyminutosRestantes.facturador—cuityrazonSocialdel primerEnteFacturadorhabilitado.
2. ABM de SucPosPV — CockpitAdminController¶
SucPosPV es el mapeo sucursal + POS físico → punto de venta CAE (y opcionalmente CAEA) de AFIP. Es config fiscal sensible: un PV mal cargado factura contra el punto de venta equivocado. Por eso el ABM va gateado por un secret simple.
El candado (header X-Cockpit-Secret)¶
- El secret se configura por la env var
COCKPIT_ADMIN_SECRET(propertycockpit.admin-secret). - El front lo manda en el header
X-Cockpit-Secreten cada request del ABM. - Comparación en tiempo constante (
MessageDigest.isEqual) para no filtrar el secret por timing. - Fail-closed: si no hay secret configurado, todo el ABM responde
503(deshabilitado). Si el secret no coincide,401.
No es seguridad de verdad — es un candado
El propio código lo aclara: "NO es auth real (eso es SPEC-014) — es un candado para que la config fiscal no quede a un click de cualquiera mientras la seguridad sigue en scaffold." Tratalo como tal.
Endpoints¶
| Método | Ruta | Qué hace | Códigos |
|---|---|---|---|
| GET | /api/cockpit/sucpospv |
Lista todos los SucPosPV. |
200 / 401 / 503 |
| GET | /api/cockpit/sucpospv/config |
Config informativa del ABM (revalidarArcaAlGuardar). |
200 / 401 / 503 |
| POST | /api/cockpit/sucpospv |
Alta. Body {suc, pos, pvcae, pvcaea?}. |
200 / 409 (duplicado) / 502 (AFIP no responde con revalidación ON) |
| PUT | /api/cockpit/sucpospv/{id} |
Edición. | 200 / 404 / 409 / 502 |
| DELETE | /api/cockpit/sucpospv/{id} |
Baja. | 200 / 404 |
| POST | /api/cockpit/sucpospv/{id}/validar-arca |
Valida los PV de la fila contra AFIP a demanda. | 200 / 404 / 409 (PV inexistente/bloqueado) / 502 |
El cuerpo de alta/edición es {suc, pos, pvcae, pvcaea} donde pvcaea es opcional (nullable) — un POS puede facturar solo CAE.
Revalidación contra ARCA al guardar
Con cockpit.revalidar-arca-al-guardar=true (env COCKPIT_REVALIDAR_ARCA), el alta/edición valida los PV contra AFIP antes de persistir: si AFIP rechaza un PV, no se guarda (responde 502). Por default es false (no depende de AFIP para editar). Ver Configuración y perfiles.
Las ResponseStatusException se traducen a JSON {status, message} mediante un @ExceptionHandler propio, porque con la cadena de filtros de Security el resolver por defecto las dejaría salir como 500.
3. Test runner — CockpitTestRunnerService / CockpitTestRunnerController¶
@RestController en /api/cockpit/test. Son acciones de validación técnica contra AFIP homologación. Cada método mide durationMs, captura excepciones y devuelve siempre {ok, durationMs, error, …campos} — nunca tira la excepción al cliente. Además el controller envuelve cada llamada en withTraffic(...), que captura el XML SOAP request/response hacia ARCA y lo agrega en la clave traffic: [{dir, action, xml}] para inspeccionarlo desde el panel.
Algunos endpoints EMITEN comprobantes REALES
POST /test/cae, POST /test/caea-solicitar y POST /test/caea-notificar generan/registran comprobantes reales en homologación. El código loguea un WARN ruidoso ([COCKPIT-CAE-PRUEBA] EMITIENDO CAE REAL…). Solo usar en el ambiente de prueba, nunca apuntando a producción.
Endpoints¶
| Método | Ruta | Qué hace | ¿Modifica? |
|---|---|---|---|
| GET | /api/cockpit/test/pos |
Lista los POS virtuales configurados (SucPosPV): [{suc, pos, pvcae, pvcaea}]. |
No |
| POST | /api/cockpit/test/token |
Obtiene/refresca el TA y devuelve vigencia (vigente, expira, minutosRestantes, tokenLen). |
No (cache-hit si vigente) |
| GET | /api/cockpit/test/ultimo?ptoVta=&tipo= |
Último comprobante autorizado (FECompUltimoAutorizado). |
No |
| GET | /api/cockpit/test/consultar?ptoVta=&tipo=&comprobante= |
Consulta un comprobante (cae, resultado, caeFchVto). |
No |
| POST | /api/cockpit/test/cae |
Emite un CAE de prueba (Factura B, Consumidor Final, $121). Body {suc, pos, nroTicketPos?}. |
Sí |
| GET | /api/cockpit/test/dummy |
FEDummy: aliveness de los 3 subsistemas AFIP. |
No |
| GET | /api/cockpit/test/validar-pv?suc=&pos= |
Valida que el pvcae del POS esté activo en AFIP (FEParamGetPtosVenta): encontrado, bloqueado. |
No |
| GET | /api/cockpit/test/caea-consultar?periodo=&orden= |
Consulta el CAEA de la quincena (lectura). | No |
| POST | /api/cockpit/test/caea-solicitar?periodo=&orden= |
Solicita un CAEA a AFIP (idempotente por quincena). | Sí |
| POST | /api/cockpit/test/caea-notificar |
Registra un comprobante CAEA de prueba (Factura B, CF, $121). Body {suc, pos}. |
Sí |
El CAE de prueba es idempotente por ticket
POST /test/cae acepta un nroTicketPos explícito. Re-enviar el mismo ticket ejercita el anti-doble-facturación del flujo real (fecaeSolicitarNextGen): devuelve el CAE existente en vez de emitir de nuevo. Si no se pasa, usa TEST-<timestamp> (siempre nuevo).
Para emitir un CAE de prueba paso a paso, mirá la guía de tareas.
Por dónde seguir¶
- Jobs Quartz — la grilla
/jobsdel cockpit muestra estos jobs. - API REST — el resto de la superficie REST del backend.
- El ciclo de facturación — qué significan CAE, CAEA y los estados que el cockpit reporta.
- Setup del entorno — para levantar el backend y pegarle al cockpit en local.