Saltar al contenido
MultiFactu · Blume POC
English
Esc
navigateopen⌘Jpreview
En esta página

Hoja de ruta — Integración BOE (Datos Abiertos) en MultiFactu

Hoja de ruta — Integración BOE (Datos Abiertos) en MultiFactu

Estado (2026-07-15): módulo IMPLEMENTADO y en producción (capas api→application→domain←infrastructure), scope real read:boe (no boe:read), con e2e BOE 22/22 y unit BOE 246. El texto de planificación de abajo es histórico; no todo refleja la implementación final. Novedades de esta fecha (4.ª pasada del AUDIT.md de eve):

  • Rate limiting saliente IMPLEMENTADO (antes: configurado pero sin consumir). application/services/boe-rate-limiter.service.ts (espaciador virtual scheduling, en proceso), cableado por intento en boe-http.client.adapter.ts (la espera cuenta contra el presupuesto), registrado en boe.module.ts, con tests unit. Consume rateLimit.perSecond (boeRatePerSecond, default 5).
  • Warm-up del snapshot al arranque (OnApplicationBootstrap en boe-snapshot-refresh.scheduler.ts, best-effort) para evitar el cold-start de la primera consulta a normas clave.
  • Pendiente (optimización, no defecto): caché HTTP condicional (ETag/304) y circuit breaker/rate-limit compartidos en Redis para multi-réplica (hoy en proceso, providers intercambiables). El caché por TTL + el rate-limit ya garantizan buena ciudadanía con boe.es.

Rama: BOE · Objetivo: integración modular, escalable y profesional con calificación 10/10. Fuente oficial: https://www.boe.es/datosabiertos/api/api.php Especificaciones: docs-official/BOE/*.md · Catálogo de endpoints: src/modules/boe/boe-api-endpoints.yml


0. Estado de implementación (rama BOE)

Fase Alcance Estado
P0 Cimientos: módulo en capas, BoeConfigService (Zod), value objects (IdentificadorNorma, FechaBoe, IdBloque), constantes, puerto de transporte, wiring en AppModule/config/env. ✅ Hecho
P1 Transporte HTTP resiliente (BoeHttpClientAdapter: timeout/budget, retry+jitter, circuit breaker), parser XML con defensa XXE, primitivas de envelope (XML↔JSON). ✅ Hecho
P2 Gateway (decode + normalize + mapeo de errores RFC 7807), normalizadores por recurso con invariante XML≡JSON, builder de búsqueda, caché por capas y BoeReadService. ✅ Hecho
P3 Superficie REST (controladores finos + pipes + DTO + mapper), OpenAPI (docs/openapi/boe.json, baseline sin breaking), auth por scope boe:read. ✅ Hecho
P5 Endurecimiento: mutation testing (L03, stryker.boe.conf.json), property-based ampliado, defensa de entrada. 🚧 En curso
P6 Persistencia/búsqueda local (Prisma boe). ⬜ Opcional/pendiente
P7 Métricas Prometheus, runbook, rendimiento (microbench/k6). ⬜ Pendiente

Cobertura de tests entregada: unit (value objects, parsers/normalizadores, gateway, builder, caché, read service, pipes, mapper, config), property-based (L08), contrato OpenAPI (L05), y e2e REST (test/boe.e2e-spec.ts). Gate del módulo: pnpm boe:quality:gate.

Superficie expuesta a Eve (REST + OpenAPI): /boe/sumario/:fecha, /borme/sumario/:fecha, /boe/legislacion-consolidada (búsqueda), /boe/legislacion-consolidada/id/:id (+ /metadatos, /analisis, /indice), /boe/datos-auxiliares/:tabla.


1. Resumen ejecutivo

El BOE publica una API REST de datos abiertos, de solo lectura (GET sobre https, negociación por cabecera Accept, envelope response{status,data}) con cuatro familias funcionales:

  1. Sumario del BOE — sumario diario por fecha.
  2. Sumario del BORME — sumario diario del Registro Mercantil por fecha.
  3. Legislación Consolidada — lista/búsqueda de normas y obtención de norma completa o partes (metadatos, análisis, ELI, texto, índice y bloques).
  4. Datos auxiliares — vocabularios controlados (materias, ámbitos, estados de consolidación, departamentos, rangos, relaciones anteriores/posteriores).

17 endpoints upstream en total (ver §3 y el YAML canónico).

Esta integración es read-only y sin credenciales, lo que la diferencia del módulo aeat-modelos (que es transaccional, con mTLS, idempotencia y máquina de estados). Por tanto, el reto de calidad no está en la criticidad transaccional, sino en:

  • Robustez de parsing dual XML/JSON sobre estructuras heterogéneas y profundas (con divergencias reales entre XML y JSON ya verificadas — ver §6.1).
  • Resiliencia y buena ciudadanía frente a boe.es (timeouts, retry con backoff+jitter, circuit breaker, presupuesto de petición, rate limiting saliente y caché agresiva — los datos cambian poco y los sumarios son inmutables una vez publicados).
  • Un contrato estable para Eve expuesto como REST + OpenAPI (ver §1.2 y §11). GraphQL y MCP quedan fuera de alcance (diferidos).
  • Cobertura de tests en todas las capas que el repositorio ya practica (unit, integración con testcontainers, e2e, contrato OpenAPI, mutación con Stryker, property-based con fast-check, y rendimiento con tinybench/k6).

Esta hoja de ruta replica deliberadamente la arquitectura en capas y los patrones de resiliencia de src/modules/aeat-modelos, adaptados a un caso de uso de lectura/ingesta.

1.1 Propósito de negocio

MultiFactu es un backend fiscal. El BOE aporta la capa normativa de referencia:

  • Resolver y versionar la legislación consolidada que respalda los cálculos fiscales (encaja con normativeSourceVersion/policyVersion ya presentes en aeat_modelos_submission).
  • Servir de base de datos normativa para el modo agéntico de Eve (ver §1.2): legislación y sumarios verificables y citables (identificador + url_eli/url_html_consolidada).
  • Vigilar publicaciones (sumarios BOE/BORME) relevantes para clientes (cambios normativos, anuncios mercantiles).

1.2 Consumidor: Eve (decisión de superficie)

El consumidor objetivo es Eve (github.com/ADP-DIGITEK/eve.multifactu.com), descrito como “Agent layer for app.multifactu.com”. Hechos verificados en su repositorio:

  • Construido con el framework Eve de Vercel (agentes durables); modelo anthropic/claude-sonnet-4.6 vía Vercel AI Gateway.
  • Sus capacidades son tools en TypeScript (agent/tools/*.ts) definidas con defineTool (de eve/tools), con inputSchema de zod y un async execute().
  • Dependencias: eve, ai (Vercel AI SDK), zod, @vercel/connect. No hay cliente MCP ni GraphQL.
  • Las tools sobre datos reales “se conectarán progresivamente” — es decir, su execute() hará fetch a un endpoint HTTP.

Conclusión (Opción A): Eve consume HTTP, no MCP ni GraphQL. Por tanto este módulo expone REST + OpenAPI:

  • Es el substrato universal: cualquier execute() de Eve puede llamarlo con fetch.
  • El inputSchema zod de cada tool de Eve mapea 1:1 con el DTO/esquema OpenAPI de nuestro endpoint (incluso se puede generar cliente desde el spec).
  • MCP/GraphQL serían peso muerto para este consumidor. Si en el futuro Eve adoptara un runtime MCP-nativo, las tools MCP de este repo son fachadas finas (src/mcp/tools/*gqlFetch): añadirlas costaría horas, sin rediseño. Se documentan como superficies diferidas (§11.3), no se construyen ahora.

Principio que protege la reversibilidad: la lógica vive en application/domain (puertos); las superficies son adaptadores finos encima. Mantener esa disciplina deja la puerta abierta a MCP/GraphQL a coste casi nulo, sin construirlos en especulativo.


2. Rúbrica 10/10 (cómo se mide el éxito)

# Dimensión Peso Criterio de “10”
1 Arquitectura en capas 10% api → application → domain ← infrastructure limpio; pasa pnpm arch:check (dependency-cruiser) sin violaciones.
2 Cobertura funcional 10% Los 17 endpoints upstream cubiertos en el motor (application/infra) y expuestos a Eve mediante 2-3 contratos REST con forma de intención.
3 Parsing y normalización 10% XML y JSON producen el mismo modelo de dominio normalizado, resolviendo las divergencias reales (data[] envuelto, mapas aux, errores XML); fixtures golden de respuestas reales.
4 Resiliencia saliente 10% Timeout + retry(backoff+jitter) + circuit breaker + request budget + rate limit + caché con ETag/If-None-Match.
5 Caché y escalabilidad 10% Caché por capas (memoria/Redis), claves deterministas, invalidación e inmutabilidad de sumarios; sin llamadas redundantes.
6 Contrato para Eve (REST + OpenAPI) 10% Endpoints REST documentados en OpenAPI, con auth/scopes, negociación de formato y baseline de contrato sin drift. GraphQL/MCP diferidos (no puntúan).
7 Persistencia 5% Schema boe en Prisma con multitenancy donde aplique, índices correctos, migración reproducible.
8 Observabilidad 5% Métricas Prometheus por endpoint, logs estructurados, tracing OTel, correlationId.
9 Seguridad y robustez 5% Validación de entrada, defensa XXE en parser XML, límites de tamaño/profundidad, manejo de errores RFC 7807.
10 Testing multicapa 15% Unit + integración (testcontainers) + e2e + contrato OpenAPI + mutación (Stryker ≥ break) + property-based + rendimiento. Cobertura global ≥ 85/80 y ≥ 95/90 en parsers/validadores/mappers.

El “10/10” se alcanza cuando todas las dimensiones están en verde y el quality gate del módulo (boe:quality:gate, §15) pasa en CI.


3. Alcance — endpoints upstream

Catálogo canónico y machine-readable: src/modules/boe/boe-api-endpoints.yml.

Familia Endpoint Formatos Notas
Sumario BOE GET /boe/sumario/{fecha} JSON, XML fecha=AAAAMMDD
Sumario BORME GET /borme/sumario/{fecha} JSON, XML nodo <apartado> solo en sección C
Consolidada GET /legislacion-consolidada JSON, XML búsqueda query+range+sort, from/to/offset/limit
Consolidada GET /legislacion-consolidada/id/{id} XML norma completa
Consolidada GET /legislacion-consolidada/id/{id}/metadatos JSON, XML
Consolidada GET /legislacion-consolidada/id/{id}/analisis JSON, XML materias/notas/referencias
Consolidada GET /legislacion-consolidada/id/{id}/metadata-eli XML
Consolidada GET /legislacion-consolidada/id/{id}/texto XML puede ser muy grande
Consolidada GET /legislacion-consolidada/id/{id}/texto/indice JSON, XML
Consolidada GET /legislacion-consolidada/id/{id}/texto/bloque/{id_bloque} XML
Aux. GET /datos-auxiliares/materias JSON, XML cache larga
Aux. GET /datos-auxiliares/ambitos JSON, XML cache larga
Aux. GET /datos-auxiliares/estados-consolidacion JSON, XML cache larga
Aux. GET /datos-auxiliares/departamentos JSON, XML cache larga
Aux. GET /datos-auxiliares/rangos JSON, XML cache larga
Aux. GET /datos-auxiliares/relaciones-anteriores JSON, XML cache larga
Aux. GET /datos-auxiliares/relaciones-posteriores JSON, XML cache larga

4. Principios de arquitectura

Se siguen las reglas de CLAUDE.md raíz y de src/modules/CLAUDE.md:

  • Módulo en capas (dominio de contrato externo): api → application → domain ← infrastructure.
  • Controllers/resolvers/tools finos: la lógica vive en application/domain, no en src/graphql, src/mcp ni controladores.
  • Puertos e inversión de dependencias: la application depende de interfaces (domain/ports), implementadas en infrastructure e inyectadas por token en el .module.ts (igual que AEAT_*_TRANSPORT_PORT).
  • Config validada: un BoeConfigService lee de ConfigService y valida con Zod (espejo de aeat-modelos.config.ts y src/core/config/env.validation.ts).
  • Error envelope global: se reutiliza AllExceptionsFilter (RFC 7807). Los errores de dominio extienden DomainException.
  • Sin acoplar al runtime de docs: docs-official/BOE y el YAML no se compilan ni se sirven.

5. Estructura de carpetas propuesta

src/modules/boe/
├── boe-api-endpoints.yml                 # catálogo canónico (ya creado)
├── boe.module.ts                         # wiring: providers, controllers, puertos
├── boe.config.ts                         # BoeConfigService (Zod) + .spec.ts

├── api/
│   ├── controllers/
│   │   ├── boe-sumario.controller.ts     # GET /boe/sumario/:fecha, /borme/sumario/:fecha
│   │   ├── boe-legislacion.controller.ts # lista + norma + subrecursos
│   │   └── boe-datos-auxiliares.controller.ts
│   ├── dto/
│   │   ├── sumario.dto.ts                 # FechaParamDto (AAAAMMDD)
│   │   ├── legislacion-query.dto.ts       # from/to/offset/limit/query (+ validadores)
│   │   └── norma-id.dto.ts                # IdNormaParamDto, IdBloqueParamDto
│   └── mappers/
│       └── domain-to-dto.mapper.ts        # dominio -> respuesta pública estable

├── application/
│   ├── use-cases/
│   │   ├── get-sumario-boe.use-case.ts
│   │   ├── get-sumario-borme.use-case.ts
│   │   ├── list-normas.use-case.ts
│   │   ├── get-norma.use-case.ts          # completa/metadatos/analisis/eli
│   │   ├── get-texto.use-case.ts          # texto/indice/bloque
│   │   └── get-datos-auxiliares.use-case.ts
│   └── services/
│       ├── boe-cache.service.ts           # claves deterministas + TTL por recurso
│       ├── boe-search-query.builder.ts    # serializa query_string/range/sort -> JSON
│       ├── boe-circuit-breaker.service.ts # (reutilizar patrón aeat) por familia
│       ├── boe-rate-limit.service.ts      # admisión saliente (memory|redis)
│       └── boe-runtime-metrics.service.ts # Prometheus por endpoint

├── domain/
│   ├── constants/boe.constants.ts         # secciones, tipos de bloque, códigos retorno
│   ├── ports/
│   │   ├── boe-transport.port.ts          # BOE_TRANSPORT_PORT (fetch crudo)
│   │   ├── boe-cache.port.ts              # BOE_CACHE_PORT
│   │   └── boe-repository.port.ts         # BOE_REPOSITORY_PORT (mirror/índice opcional)
│   ├── value-objects/
│   │   ├── identificador-norma.vo.ts      # parse/validate BOE-A-AAAA-N
│   │   ├── fecha-boe.vo.ts                 # AAAAMMDD <-> Date
│   │   └── id-bloque.vo.ts
│   └── types/boe.types.ts                 # Sumario, Norma, Metadatos, Analisis, Texto, Bloque...

└── infrastructure/
    ├── adapters/
    │   └── boe-http.client.adapter.ts     # implementa BOE_TRANSPORT_PORT (@nestjs/axios)
    ├── parsers/
    │   ├── boe-xml.parser.ts              # fast-xml-parser + defensa XXE
    │   ├── boe-json.parser.ts
    │   └── normalizers/                   # XML|JSON -> modelo de dominio único
    ├── cache/
    │   └── boe-cache.adapter.ts           # cache-manager + @keyv/redis (ya en deps)
    ├── db/
    │   └── boe.repository.ts              # Prisma (mirror/índice, opcional fase 6)
    └── schedulers/
        └── boe-aux-refresh.scheduler.ts   # refresco de datos auxiliares (@nestjs/schedule)

6. Modelo de dominio

Tipos en domain/types/boe.types.ts y value objects con validación propia:

  • IdentificadorNorma — valida ^[A-Z]{3}-[A-Z]-\d{4}-\d{1,5}$; normaliza mayúsculas; expone publicacion (BOE/BORME) y anio.
  • FechaBoeAAAAMMDDDate UTC; rechaza fechas imposibles; helper para fecha_actualizacion AAAAMMDDTHHmmSSZ.
  • IdBloque — identificador de bloque (pr, a1, dd, df, fi, an…).
  • Modelos normalizados: SumarioBoe, SumarioBorme, NormaConsolidada, MetadatosNorma, AnalisisNorma (materias[], notas[], referencias{anteriores[],posteriores[]}), TextoConsolidado (bloque[].version[]), IndiceTexto, DatosAuxiliares.

Invariante clave (testeable): parsear el mismo recurso en XML y en JSON debe producir un modelo de dominio idéntico (normalización canónica de item único vs array, atributos @codigo, texto, etc.).

6.1 Divergencias XML vs JSON (verificadas en vivo el 2026-06-23)

La documentación oficial describe la estructura XML; la serialización JSON difiere. Comprobado con curl contra https://www.boe.es y reflejado en boe-api-endpoints.yml (bloque json_serialization y verification). El normalizador debe absorber estas diferencias para garantizar el invariante anterior:

Recurso XML JSON (real) Normalización
metadatos, analisis, texto/indice data.<nodo> data es array de 1 elemento (data[0]) desenvolver data[0]
Lista de consolidada data.item[] data es array de items (sin item) usar data[]
Datos auxiliares data con nodos data es mapa { "<codigo>": "<descripcion>" } mapear a [{codigo, texto}]
Sumarios data.sumario... igual; item/diario únicos llegan como objeto coaccionar a array
Recursos XML-only (norma completa, metadata-eli, texto, texto/bloque) XML no hay JSON: Accept: application/json400 “No soportado ningún mime type” pedir Accept: application/xml
Respuestas de error XML el envelope de error puede venir como XML aunque se pida JSON (observado en 404) el adapter debe detectar y parsear envelope XML en errores

Estas reglas son la base de los tests de parsing (L1) y del invariante con property-based (L8).


7. Capa infrastructure

7.1 Adapter HTTP (boe-http.client.adapter.ts)

Replica el patrón de resiliencia de consulta-ext.client.adapter.ts (aeat-modelos), pero para GET:

  • Cliente: @nestjs/axios (HttpService), validateStatus: () => true, responseType: 'text'.
  • Cabecera Accept según formato solicitado; por defecto JSON donde esté disponible, XML cuando sea el único formato (norma completa, texto, ELI, bloque).
  • Timeout por intento + request budget total.
  • Retry exponencial con jitter solo en 429/5xx/errores de red.
  • Circuit breaker por familia de endpoint (sumario/legislación/auxiliares).
  • Rate limiting saliente (buena ciudadanía con boe.es): límite configurable de req/seg.
  • Caché HTTP condicional: almacenar ETag/Last-Modified; reenviar If-None-Match/If-Modified-Since; tratar 304 como hit.
  • Métricas por intento (endpoint, statusCode, latencyMs, cache=hit|miss|revalidated).

7.2 Parsing dual y defensa

  • fast-xml-parser (ya en deps) con ignoreAttributes:false, attributeNamePrefix consistente; defensa XXE (sin DTD/entidades externas) y límites de profundidad/tamaño.
  • Para validación estructural: ajv (en deps) con JSON Schema propio por recurso (el BOE no publica XSD para estas APIs). El XSD solo se usaría si BOE lo publicara; mientras tanto, JSON Schema canónico.
  • Normalizadores: unifican peculiaridades (un solo item llega como objeto, varios como array; coerción de szBytes/pagina_* a number; fechas a FechaBoe).

7.3 Caché (boe-cache.adapter.ts)

  • cache-manager + @keyv/redis (ya en deps).
  • Política por recurso:
    • Sumarios (BOE/BORME) de fechas pasadas → inmutables, TTL muy largo / permanente.
    • Datos auxiliares → TTL ≥ 24 h + refresco programado (boe-aux-refresh.scheduler.ts).
    • Lista/búsqueda de consolidada → TTL corto (minutos).
    • Norma/metadatos/texto → TTL medio + revalidación por ETag.
  • Claves deterministas: boe:{familia}:{recurso}:{args-normalizados}:{formato} (testeable; ver property-based §14).

8. Capa application

  • Use-cases finos que orquestan: validar entrada → consultar caché → (miss) adapter HTTP → parsear/normalizar → cachear → mapear.
  • boe-search-query.builder.ts: construye el JSON de query (campos permitidos, range solo en fechas, sort) con validación estricta de campos para evitar 400 Search error.
  • get-texto.use-case.ts: soporta texto completo, indice y bloque; estrategia recomendada para front/IA = índice + bloques bajo demanda (evita descargar textos enormes).
  • Servicios transversales (circuit breaker, rate limit, métricas) compartidos por los use-cases.

9. Capa api (REST)

  • Controllers finos con DTOs class-validator:
    • FechaParamDto (@Matches(/^\d{8}$/) + validación semántica).
    • IdNormaParamDto, IdBloqueParamDto.
    • LegislacionQueryDto (from,to,offset,limit,query) con validadores y saneo.
  • Auth/scopes: rutas bajo RestAuthGuard global; scopes boe:read (lectura) declarados con @RequireScopes. Decidir en P0 si parte es público (@PublicRoute) o todo autenticado (recomendado: autenticado + rate limit de edge).
  • Negociación de formato: query ?format=json|xml o passthrough de Accept; por defecto JSON normalizado del producto.
  • Errores: AllExceptionsFilter → RFC 7807; mapear 404 upstream a 404 de dominio y 400 upstream a 400 de validación.
  • Swagger: decoradores @nestjs/swagger completos para alimentar OpenAPI.

Base de rutas propuesta: /boe/sumario, /borme/sumario, /boe/legislacion-consolidada/..., /boe/datos-auxiliares/....


10. Persistencia (Prisma) — opcional/fase avanzada

Schema dedicado boe en packages/prisma/schema.prisma (patrón multi-schema ya usado).

  • boe_http_cache (si se prefiere cache durable a Redis): cache_key @id, etag, last_modified, payload Json, format, fetched_at, expires_at.
  • boe_norma_index (mirror/índice de búsqueda local, fase 6): identificador @id, titulo, rango_codigo, departamento_codigo, fecha_publicacion, fecha_actualizacion, estado_consolidacion_codigo, vigencia_agotada, organization_id? (si hay listas de seguimiento por organización), índices compuestos (@@index([fecha_actualizacion])), (@@index([rango_codigo, fecha_publicacion])).
  • Timestamps @db.Timestamptz(6); naming snake_case; migración reproducible y pnpm prisma:validate.

La integración funciona end-to-end sin Prisma (caché Redis + upstream). La persistencia se añade para búsqueda local, watchlists y resiliencia offline.


11. Superficies públicas

Decisión (§1.2): la única superficie activa es REST + OpenAPI, porque Eve consume HTTP. GraphQL y MCP se documentan como diferidos, no se construyen.

11.1 REST — contratos orientados a Eve (superficie ACTIVA)

El motor (application/infrastructure) cubre los 17 endpoints upstream, pero la superficie pública expuesta a Eve se diseña con forma de intención, pequeña y estable, para que cada tool de Eve la mapee 1:1. Contratos propuestos (a refinar con casos de uso reales de Eve):

Contrato REST Tool de Eve equivalente Devuelve
GET /boe/legislacion-consolidada (búsqueda) boe_buscar_legislacion lista con metadatos resumidos + identificador + enlaces citables
GET /boe/legislacion-consolidada/{id} (metadatos + análisis + índice) boe_obtener_norma norma “agregada” lista para citar, sin el texto enorme
GET /boe/legislacion-consolidada/{id}/bloque/{idBloque} boe_obtener_bloque un bloque concreto del texto, bajo demanda
GET /boe/sumario/{fecha} y GET /borme/sumario/{fecha} boe_sumario / borme_sumario sumario del día
GET /boe/datos-auxiliares/{tabla} (interno / cache) vocabularios (materias, rangos, etc.)
  • Controllers finos + DTOs class-validator; respuesta JSON normalizada del producto (no el JSON crudo del BOE: ya desenvuelto, con item/diario coaccionados a array y aux como [{codigo,texto}]).
  • Auth/scopes: RestAuthGuard global + @RequireScopes('boe:read').
  • OpenAPI: decoradores @nestjs/swagger completos para que el spec sirva de contrato a Eve (y permita generar cliente/typings para sus tools).
  • Entregable para Eve: además del spec, una nota de integración (docs-official/BOE/README.md) con los esquemas zod sugeridos para defineTool, derivados del OpenAPI.

11.2 OpenAPI (contrato y no-drift)

  • pnpm openapi:export:docs genera docs/openapi/boe.json (split por tag boe).
  • Añadir boe a scripts/split-openapi.ts y refrescar baseline (pnpm openapi:baseline:refresh).
  • Gate de drift: pnpm openapi:diff sin breaking changes no intencionados.

11.3 Superficies diferidas (NO en alcance) — GraphQL y MCP

  • No se construyen porque Eve no las consume (sin cliente MCP/GraphQL; ver §1.2).
  • Quedan disponibles a coste bajo si cambian los requisitos:
    • GraphQL: BoeGraphqlModule + BoeQueryResolver (solo queries; complexity por campo; @RequireScopes('boe:read')).
    • MCP: src/mcp/tools/boe.ts con tools finas que llaman al GraphQL/REST propio (patrón verifactu.ts), schemas en src/mcp/schemas.ts.
  • Como la lógica vive en application/domain, añadirlas serían adaptadores finos sin tocar el núcleo.

12. Configuración (env vars)

BoeConfigService (Zod), variables con prefijo BOE_:

Variable Default Uso
boeEnabled false habilita el módulo
boeBaseUrl https://www.boe.es base upstream
boeTimeoutMs 15000 timeout por intento
boeRetries 2 reintentos
boeRetryBaseDelayMs 400 backoff base
boeRequestBudgetMs auto presupuesto total
boeCircuitBreakerThreshold 5 fallos para abrir
boeCircuitBreakerCooldownMs 30000 cooldown
boeRatePerSecond 5 rate limit saliente
boeCacheBackend auto memory/redis/auto
boeCacheTtlAuxMs 86400000 TTL datos auxiliares
boeCacheTtlListMs 300000 TTL listas/búsqueda
boeCacheTtlNormaMs 3600000 TTL norma/metadatos/texto

Validación: error de arranque si boeEnabled=true y falta boeBaseUrl. Añadir el bloque al env.validation.ts y a configuration.ts.


13. Observabilidad

  • Métricas Prometheus (prom-client, ya en deps): contador/histograma por endpoint, format, outcome, cache. Exponer en el endpoint de métricas existente.
  • Logs estructurados (nestjs-pino) con correlationId (CLS) ya disponible.
  • Tracing OTel (auto-instrumentación ya configurada) — spans por familia y por intento HTTP.
  • SLO sugerido: p95 < 500 ms con caché caliente; tasa de error upstream < 1%.

14. Estrategia de testing por capa (núcleo del 10/10)

Se replican exactamente las capas y convenciones del repo (numeración L##, fixtures, testcontainers, Stryker, fast-check, tinybench/k6). Cobertura global ya exigida: branches 80 / functions 80 / lines 85 / statements 85; 95/90 en archivos críticos vía coverageThreshold por archivo en package.json.

L1 — Unit (*.spec.ts, junto al código)

  • Value objects: IdentificadorNorma, FechaBoe, IdBloque — validación, normalización, casos límite.
  • Parsers/normalizadores: XML→dominio y JSON→dominio, incluyendo el caso item único vs array, atributos, coerción numérica.
  • boe-search-query.builder: serialización de query_string/range/sort; rechazo de campos no permitidos.
  • boe-cache.service: derivación de claves y selección de TTL por recurso.
  • Use-cases: con BOE_TRANSPORT_PORT y BOE_CACHE_PORT mockeados (jest.fn), camino hit/miss/revalidate, propagación de 404/400.
  • Mappers dominio→DTO.
  • Objetivo de cobertura: 95/90 en parsers, value objects, builder y mappers.

L2 — Integración (*.integration.spec.ts, testcontainers)

  • Redis (test/support/testcontainers/redis.ts): boe-cache.adapter real contra Redis efímero (hit/miss/TTL/revalidación). Gating AGENTS_REQUIRE_REDIS/CI.
  • Postgres (test/support/testcontainers/postgres.ts, DB_INTEGRATION=1): boe.repository (si se implementa P6) con cadena de migraciones Prisma (startMigratedTestDb()), round-trip de índice/cache.
  • Adapter HTTP contra un servidor BOE simulado local (fixtures golden) para validar retry/circuit-breaker/budget/ETag de forma determinista.

e2e (test/boe.e2e-spec.ts, supertest) — solo REST

  • App Nest con BOE_TRANSPORT_PORT mockeado (sin red real), igual que test/verifactu.e2e-spec.ts mockea puertos.
  • Cubre los contratos REST expuestos a Eve: 200 con JSON normalizado, 404 (norma inexistente), 400 (fecha/id/búsqueda inválidos), 403 (método no permitido), paginación de lista.
  • Caso clave (consumo Eve): simular un fetch como el del execute() de una tool, validando que el cuerpo se ajusta al inputSchema/salida esperada por Eve.

L5 — Contrato OpenAPI (test/contract/ + jest-openapi)

  • Generar OpenAPI, añadir boe al split, refrescar test/contract/openapi.baseline.json.
  • expect(response).toSatisfyApiSpec() en e2e (helper test/openapi-conformance.helper.ts).
  • pnpm openapi:diff en CI sin breaking changes no intencionados.
  • Test de presencia de rutas (estilo agents.openapi-contract.spec.ts): verificar que los contratos REST expuestos a Eve existen en el spec generado.

L3 — Mutación (Stryker, stryker.conf.json)

Añadir a mutate los archivos deterministas y críticos:

  • domain/value-objects/*.ts (identificador, fecha, bloque).
  • infrastructure/parsers/** y normalizers/**.
  • application/services/boe-search-query.builder.ts y boe-cache.service.ts.
  • Umbral: respetar break actual (≥ 80) sin regresión; objetivo high 85.

L8 — Property-based (fast-check, *.property.spec.ts)

  • FechaBoe: round-trip AAAAMMDD ⇆ Date; rechazo de fechas imposibles.
  • IdentificadorNorma: round-trip parse/format; idempotencia de normalización.
  • Equivalencia XML/JSON: para un mismo recurso generado, ambos parsers producen el mismo dominio (invariante central de §6).
  • boe-cache key: estabilidad y unicidad ante permutación de parámetros.
  • boe-search-query.builder: la serialización nunca produce JSON inválido.

L10/L11 — Rendimiento (scripts/performance/)

  • Microbench (tinybench): hotpath de parsing de texto consolidado grande (con imágenes base64) y de búsqueda; baseline con tolerancia (±10%) en performance/bench/baselines/.
  • Carga (k6): escenarios smoke/nightly sobre endpoints de lectura con caché caliente; thresholds p95/p99 y error-rate.

Fixtures golden

  • test/fixtures/boe/ con respuestas reales (recortadas) en XML y JSON por cada familia: sumario BOE, sumario BORME, lista, metadatos, análisis, texto (con bloque e imagen base64), índice, y los 7 datos auxiliares. Son la base de L1/L2/e2e/L5/L8.

15. Quality gate del módulo

Añadir a package.json (espejo de ticketbai:quality:gate):

"boe:test:unit":   "jest --testPathPatterns=src/modules/boe",
"boe:test:e2e":    "jest --config ./test/jest-e2e.json --testPathPatterns=boe",
"boe:openapi:contract": "jest --runInBand --testPathPatterns=src/modules/boe/.*openapi-contract",
"boe:quality:gate": "pnpm typecheck && ultracite check src/modules/boe && pnpm boe:test:unit && pnpm boe:test:e2e && pnpm openapi:diff"

Sin boe:test:mcp ni gates de GraphQL: esas superficies están diferidas (§11.3).

arch:check (dependency-cruiser) debe pasar para el subárbol src/modules/boe.


16. Plan de fases

Cada fase es un conjunto de commits sobre esta rama BOE, con su propio gate verde antes de continuar. Las fases P0–P4 entregan una integración productiva consumible por Eve; P5–P7 elevan a 10/10.

P0 — Cimientos del módulo (scaffolding)

  • boe.module.ts, boe.config.ts (+Zod, env vars), registro en app.module.ts.
  • domain/ (tipos, value objects, ports, constants).
  • Bloque de config en configuration.ts + env.validation.ts.
  • Tests: L1 de value objects; boe.config.spec.ts; boe.module.contract.spec.ts.
  • DoD: pnpm typecheck && pnpm arch:check && boe:test:unit verde.

P1 — Adapter HTTP + parsing dual

  • boe-http.client.adapter.ts (resiliencia completa), boe-xml.parser.ts, boe-json.parser.ts, normalizadores.
  • Fixtures golden iniciales.
  • Tests: L1 parsers/normalizadores; L8 equivalencia XML/JSON; L2 adapter contra servidor simulado.
  • DoD: parsing de las 4 familias verde; invariante XML≡JSON probado.

P2 — Use-cases + caché

  • Use-cases de sumarios, lista/búsqueda, norma y subrecursos, datos auxiliares.
  • boe-cache.service + boe-cache.adapter (Redis) + boe-search-query.builder.
  • boe-aux-refresh.scheduler.
  • Tests: L1 use-cases/builder/cache; L2 caché contra Redis (testcontainers).
  • DoD: hit/miss/revalidación probados; búsqueda construida y validada.

P3 — Superficie REST + OpenAPI

  • Controllers, DTOs, mappers, auth/scopes, Swagger; respuesta JSON normalizada del producto.
  • Tests: e2e (200/400/404/paginación); L5 contrato (baseline + drift + presencia de rutas).
  • DoD: boe:test:e2e y pnpm openapi:diff verdes; baseline actualizado.

P4 — Contratos orientados a Eve + handoff

  • Diseñar/afinar los 2-3 contratos REST con forma de intención (§11.1) según los casos de uso reales de Eve.
  • Generar typings/cliente desde el OpenAPI y documentar en docs-official/BOE/README.md los inputSchema zod sugeridos para defineTool.
  • (Opcional) Spike de una tool en eve.multifactu.com (agent/tools/boe_buscar_legislacion.ts) que haga fetch al contrato, como validación end-to-end del consumo.
  • Tests: e2e del contrato Eve-facing (cuerpo conforme al esquema esperado por la tool); contrato OpenAPI estable.
  • DoD: Eve puede consumir el contrato con un fetch simple; sin GraphQL ni MCP.

P5 — Endurecimiento (mutación + property-based + seguridad)

  • Añadir archivos críticos a stryker.conf.json; subir property-based.
  • Defensa XXE, límites de tamaño/profundidad de XML, fuzz de entradas.
  • Tests: L3 (Stryker ≥ break sin regresión); L8 ampliado.
  • DoD: mutation score del subárbol ≥ umbral; sin findings de seguridad.

P6 — Persistencia y búsqueda local (opcional, escalabilidad)

  • Schema Prisma boe, migración, boe.repository, índice de búsqueda local, watchlists por organización.
  • Tests: L2 Postgres (testcontainers) con cobertura 95/90 del repositorio (patrón aeat-modelos.repository).
  • DoD: round-trip + búsqueda local probados; migración reproducible.

P7 — Rendimiento, observabilidad y operación

  • Métricas Prometheus, dashboards/SLO, microbench + baselines, k6.
  • Runbook (docs/boe-runbooks.md) y README de contrato (docs-official/BOE/README.md ya creado, ampliar con contrato backend).
  • Tests: L10/L11 con baselines; alertas.
  • DoD: SLO definido y medido; boe:quality:gate completo verde en CI.

17. Riesgos y mitigaciones

Riesgo Impacto Mitigación
Textos consolidados enormes (imágenes base64) Memoria/latencia Preferir indice + bloque bajo demanda; streaming; límites de tamaño.
Heterogeneidad XML (item único vs array) Bugs de parsing Normalizadores + invariante XML≡JSON con property-based.
Caída/lentitud de boe.es Disponibilidad Circuit breaker, budget, caché durable, 304 revalidation.
Sobrecarga al upstream Bloqueo/ética Rate limit saliente + caché agresiva + refresco programado de auxiliares.
XXE / entidades maliciosas Seguridad Parser sin DTD/entidades externas; límites de profundidad.
Drift de contrato OpenAPI Rotura de clientes openapi:diff en gate; baseline versionado.
Cambios normativos en la API BOE Mantenimiento Catálogo YAML como fuente única; tests de contrato sobre fixtures.

18. Definition of Done (checklist 10/10)

  • Módulo en capas; pnpm arch:check verde para src/modules/boe.
  • 17 endpoints upstream cubiertos en el motor; expuestos a Eve mediante 2-3 contratos REST con forma de intención.
  • Parsing XML y JSON normalizado a un único modelo, resolviendo las divergencias de §6.1; invariante XML≡JSON probado.
  • Resiliencia completa (timeout/retry/jitter/circuit-breaker/budget/rate-limit/ETag).
  • Caché por capas con claves deterministas y políticas por recurso.
  • OpenAPI generado, boe.json split, baseline y openapi:diff sin breaking.
  • Contrato consumible por Eve con fetch; inputSchema zod documentados. GraphQL/MCP diferidos (§11.3), no requeridos.
  • (Opcional) Persistencia Prisma boe con migración reproducible.
  • Observabilidad: métricas Prometheus, logs con correlationId, tracing OTel.
  • Tests en todas las capas: L1 unit, L2 integración (Redis+Postgres testcontainers), e2e REST, L5 contrato OpenAPI, L3 mutación (Stryker), L8 property-based, L10/L11 rendimiento.
  • Cobertura global ≥ 85/80 y ≥ 95/90 en parsers/value-objects/builder/mappers/repository.
  • boe:quality:gate verde en CI.
  • Documentación: este roadmap, boe-api-endpoints.yml, docs-official/BOE/, runbook.

19. Referencias del repositorio (patrones a replicar)

  • Módulo en capas y resiliencia HTTP: src/modules/aeat-modelos/** (adapters, circuit breaker, config Zod, repository, schedulers).
  • Contrato frontend/backend de referencia: docs-official/AEAT/README.md.
  • Consumidor (Eve): github.com/ADP-DIGITEK/eve.multifactu.com (agent/tools/*.ts con defineTool+zod, agent/agent.ts, package.json).
  • OpenAPI: scripts/generate-openapi-json.ts, scripts/split-openapi.ts, scripts/openapi/check-breaking.ts, test/contract/openapi.baseline.json.
  • Diferidos (solo si se reactivan §11.3) — GraphQL: src/graphql/integrations/verifactu/**, src/graphql/shared/complexity.plugin.ts. MCP: src/mcp/tools/verifactu.ts, src/mcp/schemas.ts, src/mcp/shared.ts.
  • Prisma multi-schema: packages/prisma/schema.prisma (aeat_modelos_submission).
  • Auth/errores: src/core/auth/rest-auth.guard.ts, src/core/auth/require-scopes.decorator.ts, src/core/error/all-exception-filter.ts.
  • Testing: testcontainers test/support/testcontainers/*, e2e test/verifactu.e2e-spec.ts, contrato test/openapi-conformance.helper.ts, mutación stryker.conf.json, property-based src/modules/**/**.property.spec.ts, rendimiento scripts/performance/*.

¿Te ha resultado útil esta página?