Skip to content

feat(api-externa): expone indicadores del Widget Engine vía API M2M

Infraestructura requested to merge feature/external-metrics-api into qa

El Portal AUP necesita alimentar sus KPIs con indicadores del portal, pero por política de seguridad no puede conectarse a la base del data lake. El JWT actual no sirve para eso: está atado a una fila de users y expira a las 8h, así que un consumidor tendría que guardar la contraseña de un empleado y re-loguearse cada 8 horas.

Se agrega una fachada estable sobre el Widget Engine, con credencial de larga vida, revocable y de alcance acotado.

Endpoints M2M (X-API-Key): GET /api/external/v1/metrics catálogo de lo expuesto GET /api/external/v1/metrics/{key} un escalar (1 fila, 1 número) GET /api/external/v1/widgets/{key} filas completas (series, tablas)

Administración (JWT admin): emitir/revocar keys, ver consumo, definir modo de exposición por widget y verificar un widget antes de exponerlo.

Tres compuertas independientes, todas deben pasar:

  1. key válida, activa, no revocada, no expirada, con scope metrics:read
  2. categoría del widget dentro del alcance del key
  3. widget_definitions.external_mode habilita el endpoint pedido

external_mode es de tres valores ('none'|'scalar'|'full') y no un booleano porque exponer el agregado y exponer el desglose son decisiones distintas: fin.ebitda_total devuelve un consolidado, fin.top_empresas_ebitda devuelve el EBITDA empresa por empresa. Con un booleano, voltearlo pasaría en silencio de lo primero a lo segundo. Un widget en 'scalar' da 404 en /widgets.

Notas de seguridad:

  • El key crudo se devuelve una sola vez; la BD guarda solo su SHA-256.
  • 401 idéntico para key desconocida, revocada, inactiva y expirada, para que no se pueda distinguir una de otra. El motivo real va al log de la app.
  • 404 idéntico para inexistente, 'none' y "expuesto en otro modo", para no permitir enumerar el catálogo interno.
  • Los fallos de auth no escriben en la bitácora de BD: sería un amplificador de escrituras ante un barrido de keys.
  • Params en modo estricto: un param no declarado es 400, no se ignora.
  • Tope de filas configurable con bandera truncated explícita.

Default cerrado: la migración no expone ningún widget.

Verificación: 44 pruebas nuevas (144 en total en la suite unitaria), grafo de modelos OK, migración aplicada dos veces contra Postgres 15 para comprobar idempotencia y que los widgets preexistentes quedan en modo 'none'.

Pendientes conocidos: rate limiting y pantallas de admin en React.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

Merge request reports

Loading