Saltar a contenido

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:

Cockpit — vista general

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):

Cockpit — birds-eye: sucursales → gateway → ARCA/AFIP

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:

Cockpit — runner de escenarios

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. kindok | 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 email

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 — ping FEDummy a WSFE (no requiere TA). Cacheado 60 s (DUMMY_TTL_MS) para no martillar a AFIP. Reporta appserver/dbserver/authserver y checkedAt.
  • ta — si el EnteFacturador habilitado tiene token, cuándo expira (expira), si está vigente y minutosRestantes.
  • facturadorcuit y razonSocial del primer EnteFacturador habilitado.

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 (property cockpit.admin-secret).
  • El front lo manda en el header X-Cockpit-Secret en 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?}.
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).
POST /api/cockpit/test/caea-notificar Registra un comprobante CAEA de prueba (Factura B, CF, $121). Body {suc, pos}.

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