first commit

This commit is contained in:
JOSE ANASTACIO ESQUIVEL SALAS 2026-10-26 12:18:03 -06:00
parent 4ff48deb24
commit b50f4955d6
74 changed files with 8168 additions and 27 deletions

View File

@ -5,5 +5,12 @@
!pom.xml
!src/
!src/**
!contracts/
!contracts/**
!schemas/
!schemas/**
!examples/
!examples/contracts/
!examples/contracts/**
!Dockerfile
!.dockerignore

View File

@ -4,6 +4,9 @@ COPY .mvn/ .mvn/
COPY mvnw pom.xml ./
RUN sed -i 's/\r$//' mvnw
COPY src/ src/
COPY contracts/ contracts/
COPY schemas/ schemas/
COPY examples/contracts/ examples/contracts/
# package ejecuta pruebas unitarias; la suite de integración se ejecuta en CI con verify.
RUN sh ./mvnw --batch-mode --no-transfer-progress package

View File

@ -4,7 +4,7 @@ Arquetipo de un microservicio Spring Boot para ejecutar flujos configurables y v
## Estado
Esta entrega crea la estructura Maven, aplicación, configuración JWT, salud, configuración de persistencia y documentación de ejecución y seguimiento. El motor, endpoints de negocio, migraciones y flujos todavía están pendientes. Consultar [PROGRESS](specs/PROGRESS.md) para evidencia y bloqueos; no interpretar la estructura como implementación completa.
Incluye la estructura Maven, configuración JWT/PostgreSQL, Docker y salud verificada (T01), además del [contrato OpenAPI](contracts/openapi.yaml), [JSON Schema del DSL](schemas/flow-definition.schema.json) y ejemplos con validación automática (T02). El motor, endpoints de negocio, migraciones y flujos completos todavía están pendientes. Consultar [PROGRESS](specs/PROGRESS.md) para evidencia y bloqueos.
## Requisitos y ejecución
@ -31,9 +31,17 @@ Salud: `http://localhost:8080/actuator/health/readiness`. El puerto publicado se
- [Plan T01–T17](specs/IMPLEMENTATION_PLAN.md): dependencias, subtareas y criterios de cierre.
- [Seguimiento](specs/PROGRESS.md): estado real, verificaciones y siguiente paso.
Surefire ejecuta `*Test` en `test`; Failsafe ejecuta `*IT` en `verify`. La estructura inicial aún no contiene pruebas de negocio; T01 debe completar su smoke test y T03 incorporar Testcontainers. CI ejecuta el mismo `clean verify`.
Surefire ejecuta `*Test` en `test`; Failsafe ejecuta `*IT` en `verify`. T01 incorpora un smoke de despliegue con cinco comprobaciones; T03 incorporará Testcontainers. CI ejecuta `clean verify` sin acceder a servidores privados. Para verificar explícitamente un servicio iniciado, usar JDK 21 y ejecutar:
Los contratos, schemas, migraciones y ejemplos de flujos tienen directorios reservados. El Dockerfile compila con JDK 21 y ejecuta con JRE 21 como usuario no root. Compose no administra PostgreSQL ni sus volúmenes. Mocks y roles de mínimo privilegio siguen pendientes de T11 y T03/T07.
```powershell
.\mvnw.cmd clean verify -Pdeployment-smoke '-Dsmoke.baseUrl=http://server02:8787'
```
Este perfil comprueba salud, readiness, liveness y rechazo de solicitudes sin token/con token malformado. No contiene todavía pruebas de negocio ni de JWT válido.
`clean verify` valida los contratos y sus ejemplos sin servicios externos. Consultar [convenciones REST](contracts/README.md), [semántica del DSL](schemas/README.md) y [fixtures T02](examples/contracts/README.md). Los ejemplos de `examples/http/contracts.http` son contratos objetivo, no endpoints implementados. Los flujos de crédito completos corresponden a T14.
El Dockerfile incluye contratos/schemas/fixtures en la etapa de construcción para ejecutar las mismas pruebas y ejecuta con JRE 21 como usuario no root. Compose no administra PostgreSQL ni sus volúmenes. Migraciones, mocks y roles de mínimo privilegio siguen pendientes de T03/T11/T07.
## Límites funcionales previstos

View File

@ -1,5 +1,34 @@
# Contratos pendientes de T02
# Contrato REST del orquestador
Crear openapi.yaml (OpenAPI 3.1) con las rutas de la sección 10 de la especificación,
JWT, ETag, If-Match, Idempotency-Key, errores problem+json y cursores.
Validar automáticamente contrato y ejemplos antes de cerrar T02.
`openapi.yaml` define OpenAPI 3.1.0 para las 14 operaciones de la sección 10 de la especificación. Base `/api/v1`, JWT Bearer obligatorio y autorización por recurso. Es un contrato objetivo: T02 no implementa controladores ni conecta proveedores.
Los DTOs se referencian desde `../schemas/api.schema.json`; las definiciones de flujo desde `../schemas/flow-definition.schema.json`. Conservar esta estructura al abrir el contrato en un visor compatible con OpenAPI 3.1. No instalar springdoc ni habilitar Swagger UI en runtime todavía. Para usar un servidor estático, publicar juntos contracts/ y schemas/; abrir sólo un archivo sin sus referencias no basta.
## Convenciones
- UUID como string; fechas UTC con sufijo Z; campos opcionales ausentes se omiten, sin null salvo dentro de datos configurables.
- `data` es extensible, pero se valida también con el schema del flujo/evento. El DTO genérico no permite saltarse esa segunda validación.
- Identidad, scope, permisos, estado, revision y resultados externos no se toman de campos enviados por el cliente. Los wrappers de comandos rechazan propiedades adicionales.
- `If-Match` es un único ETag fuerte entre comillas, como `"7"`; falta: 428; desajuste: 412. Se exige en eventos, edición de borrador, publicación, retiro y activación. El ETag de activación pertenece al tipo.
- Idempotency-Key es obligatorio en creación, eventos y conciliación. Creación: unicidad actor/scope; eventos y conciliación: por recurso. Comando canónico distinto con misma clave: 409. En eventos se comprueba replay antes de revisión; se conserva executionId y se devuelve estado actual de la ejecución.
- Aceptación de evento devuelve 202 y Location. No supone éxito de negocio. SUCCEEDED puede contener CLIENTE_NO_ENCONTRADO, manteniendo el estado.
- `available-events` es orientativo. Durante una ejecución activa: events vacío y blockedByExecutionId presente.
- Historial ordenado por (createdAt,id), cursor opaco, máximo 100. Filtros kind, executionId, from inclusivo y to exclusivo; nextCursor se omite al terminar.
- Errores `application/problem+json` con status, code y correlationId; detalles filtrados. 404 oculta recursos de otro scope; 403 representa permiso insuficiente cuando revelar el recurso está permitido.
- `x-required-roles` enumera roles técnicos alternativos (OR); no reemplaza autorización por recurso. ANALISTA y otros roles de negocio se definen en las transiciones y se revalidan al aceptar.
## Administración
Creación/edición reciben la definición completa. flowKey y version forman identidad inmutable, incluso al editar el borrador. Validación semántica retorna 200 con valid=false y errores JSON Pointer; publicación rechaza una definición inválida con 422. La publicación fija contractVersion/profileVersion desde catálogo confiable antes del checksum.
La simulación recibe actor/requestData/results de prueba, evalúa guardia/decisión/updates y no realiza HTTP ni efectos persistentes. Los fixtures de simulación nunca son datos confiables para el endpoint de eventos.
Tipos de solicitud se provisionarán en T03/T06 mediante migraciones/configuración administrativa; este MVP no agrega un endpoint de alta de tipos. Activar un tipo inexistente retorna 404. Retiro conserva las solicitudes existentes y bloquea nuevas asignaciones concurrentes.
Conciliación sólo permite RESUME con resultados verificados por acción o CLOSE_WITHOUT_TRANSITION con ausencia/compensación de efectos verificada. La evidencia es una referencia y motivo, no documentos personales ni secretos. El operador autorizado verifica la evidencia; el cliente no puede escoger estado destino. La resolución es idempotente y auditada.
## Validación automática
Con JDK 21: `./mvnw clean verify` (Windows: `.\mvnw.cmd clean verify`). ContractValidationTest comprueba el schema oficial OpenAPI 3.1, parseo Swagger, resolución local de referencias, meta-schema 2020-12, fixtures positivos/negativos, ejemplos embebidos y cabeceras/rutas obligatorias. Los validadores tienen scope test; no agregan dependencias Jackson 2 al runtime.
Swagger Parser 2.1.35 se usa sin expansión recursiva (`resolve=false`): expandir el AST produjo un error de profundidad. Las referencias se verifican explícitamente y networknt valida los ejemplos con los schemas reales; no se ignoran referencias rotas. Las pruebas normales no contactan server02, PostgreSQL, Keycloak ni proveedores. No sustituyen T05/T06/T07/T16.

1474
contracts/openapi.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
# Schema oficial de OpenAPI 3.1
openapi-3.1.schema.json es una copia sin modificaciones de:
https://spec.openapis.org/oas/3.1/schema/2022-10-07
Descargado el 2026-10-26 para validación sin red en CI. Fuente: OpenAPI Initiative, repositorio https://github.com/OAI/OpenAPI-Specification (licencia Apache 2.0: https://github.com/OAI/OpenAPI-Specification/blob/main/LICENSE).
Este schema valida el documento OpenAPI, no el contenido de los Schema Objects. ContractValidationTest valida además los archivos JSON Schema contra el meta-schema 2020-12, resuelve todos los $ref locales y valida ejemplos con networknt. No se modifica ni se usa como schema del DSL.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
# Ejemplos contractuales T02
Fixtures de estructura, no flujos de crédito publicables ni resultados reales de proveedores.
- flows/scoring.json: núcleo de scoring con consentimiento y fronteras 60/70.
- flows/document.json: registro/validación de licencia, INVALID conserva el estado.
- flows/form.json: captura pura sin integraciones, ruta fija y tarea FORM.
- api/: comandos y respuestas REST, simulación y conciliación.
- integrations/: resultados normalizados de demostración, incluidos scores 71/70/61/60.
- invalid/: fixtures que deben ser rechazados; no usarlos para publicar.
- manifest.json: archivo, schema y resultado esperado de todos los fixtures.
Los tests fallan si un fixture no está registrado o si un ejemplo inválido empieza a aceptarse. También validan todos los ejemplos del OpenAPI. Las fronteras numéricas aquí verifican el contrato de datos, no la decisión N/M/M/MANUAL (T05). Los flujos completos credito-v1, credito-v2-licencia y compra-v1 corresponden a T14.

View File

@ -0,0 +1,5 @@
{
"executionId": "00000000-0000-0000-0000-000000000789",
"requestId": "00000000-0000-0000-0000-000000000123",
"status": "PENDING"
}

View File

@ -0,0 +1,3 @@
{
"flowId": "00000000-0000-0000-0000-000000000456"
}

View File

@ -0,0 +1,7 @@
{
"requestId": "00000000-0000-0000-0000-000000000123",
"revision": 8,
"events": [],
"blocked": [],
"blockedByExecutionId": "00000000-0000-0000-0000-000000000789"
}

View File

@ -0,0 +1,7 @@
{
"typeKey": "CREDITO",
"data": {
"clienteId": "CLI-DEMO",
"consentimientoId": "CONS-DEMO"
}
}

View File

@ -0,0 +1,6 @@
{
"event": "REGISTRAR_LICENCIA",
"data": {
"documentId": "00000000-0000-0000-0000-000000000789"
}
}

View File

@ -0,0 +1,16 @@
{
"id": "00000000-0000-0000-0000-000000000789",
"requestId": "00000000-0000-0000-0000-000000000123",
"event": "ASIGNAR_CLIENTE",
"status": "SUCCEEDED",
"result": {
"outcome": "CLIENTE_NO_ENCONTRADO",
"fromState": "CREADA",
"toState": "CREADA",
"reasonCodes": [
"NO_MATCH"
]
},
"createdAt": "2026-10-26T17:20:00Z",
"updatedAt": "2026-10-26T17:20:00Z"
}

View File

@ -0,0 +1,150 @@
{
"id": "00000000-0000-0000-0000-000000000456",
"status": "DRAFT",
"revision": 0,
"definition": {
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
},
"createdAt": "2026-10-26T17:20:00Z",
"createdBy": "editor-demo"
}

View File

@ -0,0 +1,16 @@
{
"items": [
{
"id": "00000000-0000-0000-0000-000000000789",
"requestId": "00000000-0000-0000-0000-000000000123",
"kind": "EVENT_ACCEPTED",
"actorSubject": "analista-demo",
"flowId": "00000000-0000-0000-0000-000000000456",
"fromState": "CREADA",
"detail": {
"transitionId": "asignar-cliente"
},
"createdAt": "2026-10-26T17:20:00Z"
}
]
}

View File

@ -0,0 +1,7 @@
{
"type": "about:blank",
"title": "Solicitud ocupada",
"status": 409,
"code": "REQUEST_BUSY",
"correlationId": "demo-correlation-001"
}

View File

@ -0,0 +1,17 @@
{
"mode": "RESUME",
"evidence": {
"reference": "CASE-DEMO-001",
"reason": "Resultado verificado con proveedor."
},
"verifiedActions": [
{
"actionId": "buro",
"result": {
"score": 71,
"consultationId": "SIM-001"
},
"providerReference": "SIM-001"
}
]
}

View File

@ -0,0 +1,6 @@
{
"resolutionId": "00000000-0000-0000-0000-000000000456",
"executionId": "00000000-0000-0000-0000-000000000789",
"status": "PENDING",
"resolvedAt": "2026-10-26T17:20:00Z"
}

View File

@ -0,0 +1,6 @@
{
"typeKey": "CREDITO",
"activeFlowId": "00000000-0000-0000-0000-000000000456",
"enabled": true,
"revision": 1
}

View File

@ -0,0 +1,17 @@
{
"id": "00000000-0000-0000-0000-000000000123",
"typeKey": "CREDITO",
"flow": {
"id": "00000000-0000-0000-0000-000000000456",
"flowKey": "SCORING_DEMO",
"version": 1
},
"currentState": "BURO_PENDIENTE",
"data": {
"clienteId": "CLI-DEMO",
"consentimientoId": "CONS-DEMO"
},
"revision": 7,
"createdAt": "2026-10-26T17:20:00Z",
"updatedAt": "2026-10-26T17:20:00Z"
}

View File

@ -0,0 +1,24 @@
{
"state": "BURO_PENDIENTE",
"requestData": {
"clienteId": "CLI-DEMO",
"consentimientoId": "CONS-DEMO"
},
"event": {
"event": "VALIDAR_BURO",
"data": {}
},
"actor": {
"subject": "analista-demo",
"roles": [
"ANALISTA"
],
"businessScope": "DEMO"
},
"results": {
"buro": {
"score": 71,
"consultationId": "SIM-001"
}
}
}

View File

@ -0,0 +1,18 @@
{
"transitionId": "consultar-buro",
"outcome": "N",
"toState": "PASO_N",
"data": {
"clienteId": "CLI-DEMO",
"consentimientoId": "CONS-DEMO",
"validaciones": {
"buro": {
"score": 71,
"consultationId": "SIM-001"
}
}
},
"matchedRuleIds": [
"alto"
]
}

View File

@ -0,0 +1,10 @@
{
"valid": false,
"errors": [
{
"pointer": "/transitions/0/to",
"code": "UNKNOWN_STATE",
"message": "Estado destino inexistente."
}
]
}

View File

@ -0,0 +1,34 @@
{
"requestId": "00000000-0000-0000-0000-000000000123",
"revision": 3,
"events": [
{
"event": "REGISTRAR_LICENCIA",
"inputSchema": {
"type": "object",
"required": [
"documentId"
],
"properties": {
"documentId": {
"type": "string",
"format": "uuid"
}
},
"additionalProperties": false
},
"ui": {
"component": "DOCUMENT_UPLOAD",
"title": "Sube tu licencia",
"documentType": "LICENCIA_CONDUCIR",
"acceptedMimeTypes": [
"application/pdf",
"image/jpeg",
"image/png"
],
"maxBytes": 10485760
}
}
],
"blocked": []
}

View File

@ -0,0 +1,11 @@
{
"id": "00000000-0000-0000-0000-000000000789",
"requestId": "00000000-0000-0000-0000-000000000123",
"event": "VALIDAR_BURO",
"status": "FAILED",
"error": {
"code": "PROVIDER_CONTRACT_ERROR"
},
"createdAt": "2026-10-26T17:20:00Z",
"updatedAt": "2026-10-26T17:20:00Z"
}

View File

@ -0,0 +1,8 @@
{
"mode": "CLOSE_WITHOUT_TRANSITION",
"evidence": {
"reference": "CASE-002",
"reason": "Proveedor confirmó ausencia de efectos."
},
"effectStatus": "ABSENT_VERIFIED"
}

View File

@ -0,0 +1,149 @@
{
"schemaVersion": 1,
"flowKey": "DOCUMENT_DEMO",
"version": 1,
"initialState": "LICENCIA_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"documentos": {
"type": "object"
}
},
"additionalProperties": false
},
"initialDataSchema": {
"type": "object",
"additionalProperties": false
},
"states": [
{
"key": "LICENCIA_PENDIENTE",
"terminal": false
},
{
"key": "COMPLETA",
"terminal": true
}
],
"events": [
{
"key": "REGISTRAR_LICENCIA",
"inputSchema": {
"type": "object",
"required": [
"documentId"
],
"properties": {
"documentId": {
"type": "string",
"format": "uuid"
}
},
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "licencia",
"from": "LICENCIA_PENDIENTE",
"event": "REGISTRAR_LICENCIA",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "registrar",
"type": "REGISTER_DOCUMENT",
"documentType": "LICENCIA_CONDUCIR",
"inputs": {
"documentId": {
"path": "/event/data/documentId"
}
},
"resultKey": "documento"
},
{
"id": "validar",
"type": "INVOKE_INTEGRATION",
"integrationKey": "DOCUMENTO_VALIDA",
"contractVersion": "1",
"profile": "LICENCIA_CONDUCIR",
"profileVersion": "1",
"inputs": {
"documentId": {
"path": "/results/documento/documentId"
}
},
"resultKey": "validacion"
}
],
"decision": "DOCUMENTO_VALIDO",
"destinations": {
"VALID": "COMPLETA",
"INVALID": "LICENCIA_PENDIENTE"
},
"updates": [
{
"target": "/documentos/licencia",
"value": {
"path": "/results/validacion"
}
}
]
}
],
"decisions": [
{
"id": "DOCUMENTO_VALIDO",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "valida",
"when": {
"op": "EQ",
"left": {
"path": "/results/validacion/status"
},
"right": {
"literal": "VALID"
}
},
"output": "VALID"
}
],
"defaultOutput": "INVALID"
}
],
"tasks": [
{
"state": "LICENCIA_PENDIENTE",
"event": "REGISTRAR_LICENCIA",
"inputSchema": {
"type": "object",
"required": [
"documentId"
],
"properties": {
"documentId": {
"type": "string",
"format": "uuid"
}
},
"additionalProperties": false
},
"ui": {
"component": "DOCUMENT_UPLOAD",
"title": "Sube tu licencia",
"documentType": "LICENCIA_CONDUCIR",
"acceptedMimeTypes": [
"application/pdf",
"image/jpeg",
"image/png"
],
"maxBytes": 10485760
}
}
]
}

View File

@ -0,0 +1,95 @@
{
"schemaVersion": 1,
"flowKey": "FORM_DEMO",
"version": 1,
"initialState": "CAPTURA",
"dataSchema": {
"type": "object",
"properties": {
"referencia": {
"type": "string"
}
},
"additionalProperties": false
},
"initialDataSchema": {
"type": "object",
"additionalProperties": false
},
"states": [
{
"key": "CAPTURA",
"terminal": false
},
{
"key": "COMPLETA",
"terminal": true
}
],
"events": [
{
"key": "CAPTURAR",
"inputSchema": {
"type": "object",
"required": [
"referencia"
],
"properties": {
"referencia": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "capturar",
"from": "CAPTURA",
"event": "CAPTURAR",
"allowedRoles": [
"ANALISTA"
],
"actions": [],
"to": "COMPLETA",
"outcome": "CAPTURADO",
"updates": [
{
"target": "/referencia",
"value": {
"path": "/event/data/referencia"
}
}
]
}
],
"decisions": [],
"tasks": [
{
"state": "CAPTURA",
"event": "CAPTURAR",
"inputSchema": {
"type": "object",
"required": [
"referencia"
],
"properties": {
"referencia": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
"ui": {
"component": "FORM",
"title": "Capturar referencia",
"fieldOrder": [
"referencia"
]
}
}
]
}

View File

@ -0,0 +1,143 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,4 @@
{
"score": 60,
"consultationId": "SIM-60"
}

View File

@ -0,0 +1,4 @@
{
"score": 61,
"consultationId": "SIM-61"
}

View File

@ -0,0 +1,4 @@
{
"score": 70,
"consultationId": "SIM-70"
}

View File

@ -0,0 +1,4 @@
{
"score": 71,
"consultationId": "SIM-71"
}

View File

@ -0,0 +1,4 @@
{
"found": true,
"customerId": "CLI-DEMO"
}

View File

@ -0,0 +1,3 @@
{
"found": false
}

View File

@ -0,0 +1,6 @@
{
"status": "INVALID",
"reasonCodes": [
"UNREADABLE"
]
}

View File

@ -0,0 +1,145 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
],
"to": "PASO_N",
"outcome": "N"
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,144 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro",
"url": "https://untrusted.example"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,12 @@
{
"requestId": "00000000-0000-0000-0000-000000000123",
"revision": 3,
"events": [
{
"event": "CAPTURAR",
"inputSchema": {}
}
],
"blocked": [],
"blockedByExecutionId": "00000000-0000-0000-0000-000000000789"
}

View File

@ -0,0 +1,5 @@
{
"typeKey": "CREDITO",
"data": {},
"ownerSubject": "otro"
}

View File

@ -0,0 +1,3 @@
{
"found": true
}

View File

@ -0,0 +1,4 @@
{
"status": "ERROR",
"reasonCodes": []
}

View File

@ -0,0 +1,8 @@
{
"mode": "CLOSE_WITHOUT_TRANSITION",
"evidence": {
"reference": "",
"reason": ""
},
"effectStatus": "ABSENT_VERIFIED"
}

View File

@ -0,0 +1,5 @@
{
"event": "CAPTURAR",
"data": {},
"currentState": "APROBADA"
}

View File

@ -0,0 +1,142 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"$ref": "https://untrusted.example/schema"
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,143 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/a~2b",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,5 @@
{
"executionId": "incorrecto",
"requestId": "00000000-0000-0000-0000-000000000123",
"status": "PENDING"
}

View File

@ -0,0 +1,142 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "numeric"
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,14 @@
{
"mode": "RESUME",
"evidence": {
"reference": "CASE-001",
"reason": "verificado"
},
"verifiedActions": [
{
"actionId": "buro",
"result": {}
}
],
"toState": "APROBADA"
}

View File

@ -0,0 +1,142 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
]
}
],
"tasks": []
}

View File

@ -0,0 +1,3 @@
{
"consultationId": "SIM-001"
}

View File

@ -0,0 +1,143 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/ownerSubject"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,146 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro",
"retry": {
"maxAttempts": 0
}
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,138 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "SPEL",
"expression": "true"
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,4 @@
{
"score": "71",
"consultationId": "SIM-001"
}

View File

@ -0,0 +1,8 @@
{
"id": "00000000-0000-0000-0000-000000000789",
"requestId": "00000000-0000-0000-0000-000000000123",
"event": "VALIDAR_BURO",
"status": "SUCCEEDED",
"createdAt": "2026-10-26T17:20:00Z",
"updatedAt": "2026-10-26T17:20:00Z"
}

View File

@ -0,0 +1,143 @@
{
"schemaVersion": 1,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "EXECUTE_SCRIPT",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,143 @@
{
"schemaVersion": 2,
"flowKey": "SCORING_DEMO",
"version": 1,
"initialState": "BURO_PENDIENTE",
"dataSchema": {
"type": "object",
"properties": {
"clienteId": {
"type": "string"
},
"consentimientoId": {
"type": "string"
},
"validaciones": {
"type": "object"
}
},
"required": [
"clienteId",
"consentimientoId"
],
"additionalProperties": false
},
"states": [
{
"key": "BURO_PENDIENTE",
"terminal": false
},
{
"key": "PASO_N",
"terminal": true
},
{
"key": "PASO_M",
"terminal": true
},
{
"key": "REVISION_MANUAL",
"terminal": true
}
],
"events": [
{
"key": "VALIDAR_BURO",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
],
"transitions": [
{
"id": "consultar-buro",
"from": "BURO_PENDIENTE",
"event": "VALIDAR_BURO",
"allowedRoles": [
"ANALISTA"
],
"actions": [
{
"id": "buro",
"type": "INVOKE_INTEGRATION",
"integrationKey": "BURO_CONSULTA",
"contractVersion": "1",
"inputs": {
"clienteId": {
"path": "/request/data/clienteId"
},
"consentimientoId": {
"path": "/request/data/consentimientoId"
}
},
"resultKey": "buro"
}
],
"decision": "RUTA_SCORING",
"destinations": {
"N": "PASO_N",
"M": "PASO_M",
"MANUAL": "REVISION_MANUAL"
},
"updates": [
{
"target": "/validaciones/buro",
"value": {
"path": "/results/buro"
}
}
]
}
],
"decisions": [
{
"id": "RUTA_SCORING",
"hitPolicy": "UNIQUE",
"rules": [
{
"id": "alto",
"when": {
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
},
"output": "N"
},
{
"id": "medio",
"when": {
"op": "AND",
"args": [
{
"op": "GT",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 60
}
},
{
"op": "LTE",
"left": {
"path": "/results/buro/score"
},
"right": {
"literal": 70
}
}
]
},
"output": "M"
}
],
"defaultOutput": "MANUAL"
}
],
"tasks": []
}

View File

@ -0,0 +1,10 @@
{
"valid": true,
"errors": [
{
"pointer": "",
"code": "ERROR",
"message": "Invalid"
}
]
}

View File

@ -0,0 +1,262 @@
[
{
"file": "api/CreateRequest.json",
"schema": "api.schema.json#/$defs/CreateRequest",
"valid": true
},
{
"file": "api/EventCommand.json",
"schema": "api.schema.json#/$defs/EventCommand",
"valid": true
},
{
"file": "api/RequestView.json",
"schema": "api.schema.json#/$defs/RequestView",
"valid": true
},
{
"file": "api/AcceptedEvent.json",
"schema": "api.schema.json#/$defs/AcceptedEvent",
"valid": true
},
{
"file": "api/ExecutionView.json",
"schema": "api.schema.json#/$defs/ExecutionView",
"valid": true
},
{
"file": "api/AvailableEvents.json",
"schema": "api.schema.json#/$defs/AvailableEvents",
"valid": true
},
{
"file": "api/HistoryPage.json",
"schema": "api.schema.json#/$defs/HistoryPage",
"valid": true
},
{
"file": "api/FlowView.json",
"schema": "api.schema.json#/$defs/FlowView",
"valid": true
},
{
"file": "api/ValidationReport.json",
"schema": "api.schema.json#/$defs/ValidationReport",
"valid": true
},
{
"file": "api/ActivateFlow.json",
"schema": "api.schema.json#/$defs/ActivateFlow",
"valid": true
},
{
"file": "api/RequestTypeView.json",
"schema": "api.schema.json#/$defs/RequestTypeView",
"valid": true
},
{
"file": "api/SimulationInput.json",
"schema": "api.schema.json#/$defs/SimulationInput",
"valid": true
},
{
"file": "api/SimulationResult.json",
"schema": "api.schema.json#/$defs/SimulationResult",
"valid": true
},
{
"file": "api/ReconciliationCommand.json",
"schema": "api.schema.json#/$defs/ReconciliationCommand",
"valid": true
},
{
"file": "api/ReconciliationView.json",
"schema": "api.schema.json#/$defs/ReconciliationView",
"valid": true
},
{
"file": "api/Problem.json",
"schema": "api.schema.json#/$defs/Problem",
"valid": true
},
{
"file": "flows/scoring.json",
"schema": "flow-definition.schema.json",
"valid": true
},
{
"file": "flows/document.json",
"schema": "flow-definition.schema.json",
"valid": true
},
{
"file": "flows/form.json",
"schema": "flow-definition.schema.json",
"valid": true
},
{
"file": "api/available-document.json",
"schema": "api.schema.json#/$defs/AvailableEvents",
"valid": true
},
{
"file": "api/reconcile-close.json",
"schema": "api.schema.json#/$defs/ReconciliationCommand",
"valid": true
},
{
"file": "api/failed-execution.json",
"schema": "api.schema.json#/$defs/ExecutionView",
"valid": true
},
{
"file": "integrations/bureau-71.json",
"schema": "integration-results.schema.json#/$defs/BureauResult",
"valid": true
},
{
"file": "integrations/bureau-70.json",
"schema": "integration-results.schema.json#/$defs/BureauResult",
"valid": true
},
{
"file": "integrations/bureau-61.json",
"schema": "integration-results.schema.json#/$defs/BureauResult",
"valid": true
},
{
"file": "integrations/bureau-60.json",
"schema": "integration-results.schema.json#/$defs/BureauResult",
"valid": true
},
{
"file": "integrations/customer-found.json",
"schema": "integration-results.schema.json#/$defs/CustomerResult",
"valid": true
},
{
"file": "integrations/customer-missing.json",
"schema": "integration-results.schema.json#/$defs/CustomerResult",
"valid": true
},
{
"file": "integrations/document-invalid.json",
"schema": "integration-results.schema.json#/$defs/DocumentValidationResult",
"valid": true
},
{
"file": "invalid/missing-score.json",
"schema": "integration-results.schema.json#/$defs/BureauResult",
"valid": false
},
{
"file": "invalid/string-score.json",
"schema": "integration-results.schema.json#/$defs/BureauResult",
"valid": false
},
{
"file": "invalid/customer-without-id.json",
"schema": "integration-results.schema.json#/$defs/CustomerResult",
"valid": false
},
{
"file": "invalid/document-unknown-status.json",
"schema": "integration-results.schema.json#/$defs/DocumentValidationResult",
"valid": false
},
{
"file": "invalid/event-forged-state.json",
"schema": "api.schema.json#/$defs/EventCommand",
"valid": false
},
{
"file": "invalid/create-forged-owner.json",
"schema": "api.schema.json#/$defs/CreateRequest",
"valid": false
},
{
"file": "invalid/manual-state-resolution.json",
"schema": "api.schema.json#/$defs/ReconciliationCommand",
"valid": false
},
{
"file": "invalid/empty-evidence.json",
"schema": "api.schema.json#/$defs/ReconciliationCommand",
"valid": false
},
{
"file": "invalid/busy-with-events.json",
"schema": "api.schema.json#/$defs/AvailableEvents",
"valid": false
},
{
"file": "invalid/success-without-outcome.json",
"schema": "api.schema.json#/$defs/ExecutionView",
"valid": false
},
{
"file": "invalid/invalid-uuid.json",
"schema": "api.schema.json#/$defs/AcceptedEvent",
"valid": false
},
{
"file": "invalid/valid-report-with-errors.json",
"schema": "api.schema.json#/$defs/ValidationReport",
"valid": false
},
{
"file": "invalid/unsupported-dsl.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/arbitrary-url.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/unknown-handler.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/script-condition.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/missing-default.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/ambiguous-routing-shape.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/invalid-pointer.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/protected-context-read.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/external-schema-ref.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/malformed-schema.json",
"schema": "flow-definition.schema.json",
"valid": false
},
{
"file": "invalid/retry-unbounded.json",
"schema": "flow-definition.schema.json",
"valid": false
}
]

View File

@ -0,0 +1,78 @@
# Contratos objetivo T02: estas rutas aún no están implementadas.
# Configurar token y reemplazar IDs/ETags con respuestas reales cuando estén disponibles.
@base = http://localhost:8080/api/v1
@flowId = 00000000-0000-0000-0000-000000000456
@requestId = 00000000-0000-0000-0000-000000000123
@executionId = 00000000-0000-0000-0000-000000000789
### Crear borrador (FLOW_EDITOR); fixture contractual, no flujo completo T14.
POST {{base}}/admin/flows
Authorization: Bearer {{token}}
Content-Type: application/json
< ../contracts/flows/scoring.json
### Validar; valid=false se reporta con HTTP 200 y errores JSON Pointer.
POST {{base}}/admin/flows/{{flowId}}/validate
Authorization: Bearer {{token}}
### Simular sin invocar proveedores.
POST {{base}}/admin/flows/{{flowId}}/simulate
Authorization: Bearer {{token}}
Content-Type: application/json
< ../contracts/api/SimulationInput.json
### Publicar: usar ETag del borrador real, no el valor ilustrativo.
POST {{base}}/admin/flows/{{flowId}}/publish
Authorization: Bearer {{token}}
If-Match: "0"
### Activar: el tipo CREDITO debe existir; If-Match corresponde al tipo.
PUT {{base}}/admin/request-types/CREDITO/active-flow
Authorization: Bearer {{token}}
If-Match: "0"
Content-Type: application/json
{"flowId":"{{flowId}}"}
### Crear solicitud. Repetir la misma clave/comando retorna el mismo recurso.
POST {{base}}/requests
Authorization: Bearer {{token}}
Idempotency-Key: demo-create-001
Content-Type: application/json
< ../contracts/api/CreateRequest.json
### Consultar solicitud y obtener ETag.
GET {{base}}/requests/{{requestId}}
Authorization: Bearer {{token}}
### Obtener tareas disponibles.
GET {{base}}/requests/{{requestId}}/available-events
Authorization: Bearer {{token}}
### Aceptar evento; body sin resultados de proveedor.
POST {{base}}/requests/{{requestId}}/events
Authorization: Bearer {{token}}
Idempotency-Key: demo-bureau-001
If-Match: "0"
Content-Type: application/json
{"event":"VALIDAR_BURO","data":{}}
### Consultar ejecución indicada por Location.
GET {{base}}/executions/{{executionId}}
Authorization: Bearer {{token}}
### Historial paginado.
GET {{base}}/requests/{{requestId}}/history?limit=50&kind=EVENT_ACCEPTED
Authorization: Bearer {{token}}
### Conciliar sólo RECONCILIATION_REQUIRED y con evidencia realmente verificada.
POST {{base}}/admin/executions/{{executionId}}/resolve
Authorization: Bearer {{token}}
Idempotency-Key: demo-resolution-001
Content-Type: application/json
< ../contracts/api/ReconciliationCommand.json

27
pom.xml
View File

@ -29,6 +29,8 @@
<dependency><groupId>org.flywaydb</groupId><artifactId>flyway-database-postgresql</artifactId></dependency>
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><scope>runtime</scope></dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency>
<dependency><groupId>com.networknt</groupId><artifactId>json-schema-validator</artifactId><version>1.5.9</version><scope>test</scope></dependency>
<dependency><groupId>io.swagger.parser.v3</groupId><artifactId>swagger-parser-v3</artifactId><version>2.1.35</version><scope>test</scope></dependency>
</dependencies>
<build>
<plugins>
@ -51,4 +53,29 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>deployment-smoke</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<includes>
<include>**/*IT.java</include>
<include>**/IT*.java</include>
<include>**/*ITCase.java</include>
<include>**/*SmokeCheck.java</include>
</includes>
<systemPropertyVariables>
<smoke.baseUrl>${smoke.baseUrl}</smoke.baseUrl>
</systemPropertyVariables>
<failIfNoTests>true</failIfNoTests>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -1,5 +1,42 @@
# Schemas pendientes de T02
# JSON Schema del DSL y DTOs
Crear flow-definition.schema.json y schemas de entrada/salida, datos y tareas.
Separar schemaVersion de la versión del flujo. Validar AST tipado y límites.
Una configuración válida estructuralmente aún requiere la validación semántica T06.
Dialect: JSON Schema 2020-12. Archivos canónicos editables directamente; no se requiere generador.
| Archivo | Contenido |
|---|---|
| flow-definition.schema.json | Definición completa de flujo y $defs para estados, eventos, valores, condiciones, acciones, decisiones, transiciones y tareas |
| api.schema.json | $defs de comandos, consultas, administración, errores, simulación y conciliación REST |
| integration-results.schema.json | Ejemplos de contratos normalizados de cliente, buró y documentos; pendientes de confirmar con proveedores en T11 |
## DSL v1
schemaVersion versiona el formato; version versiona el proceso. Keys/IDs: 1–100 caracteres, comienzan por letra y permiten letras ASCII, dígitos, punto, guion y guion bajo. Los estados/eventos siguen siendo strings configurables, no enums del negocio.
`dataSchema` describe datos persistidos y `initialDataSchema` restringe entrada inicial. Son opcionales estructuralmente para aceptar borradores y el ejemplo mínimo original; T06 deberá exigir dataSchema de objeto al publicar. Si initialDataSchema falta, usar dataSchema; al crear validar ambos cuando están presentes. En los schemas de entrada conviene additionalProperties=false y sólo campos aceptados del cliente. La publicación debe rechazar configuraciones que acepten resultados de proveedor como autoridad del front.
`InlineSchema` admite booleanos y un subconjunto explícito de JSON Schema: type, properties, required, additionalProperties, items, enum/const, longitudes/pattern/format, rangos numéricos, límites de arrays/objetos y allOf/anyOf/oneOf/not/if/then/else. Formats: uuid, date-time, date, email, uri. No admite referencias externas, $ref, $id ni vocabularios ejecutables. La validación de formats está activada en las pruebas. Schemas embebidos también se validan con el meta-schema 2020-12; consistencia entre rangos y compatibilidad de mappings corresponden a T06.
Valores: exactamente `{ "path": "/results/buro/score" }` o `{ "literal": 70 }`. Contextos permitidos: /request/data, /event/data, /results y /actor. Paths usan JSON Pointer y escapes ~0/~1; no son expresiones ejecutables.
Condiciones:
- EQ, NEQ, GT, GTE, LT, LTE, IN: op, left y right.
- EXISTS: op y value con path (no literal).
- AND/OR: op y args con al menos dos condiciones ordenadas.
- NOT: op y arg con una condición.
Tipos, BigDecimal, cortocircuito, null frente a ausencia, IN homogéneo, solapamientos y UNIQUE ambiguo se evaluarán en T05. UNIQUE requiere defaultOutput; FIRST sólo se habilita seleccionándolo explícitamente como hitPolicy.
Transiciones: id/from/event/allowedRoles/actions. Una ruta fija usa to + outcome; una ruta calculada usa decision + destinations y el output seleccionado es el outcome de negocio. No pueden mezclarse. Actions están ordenadas y pueden estar vacías para una captura pura. Guard es opcional y se evalúa antes de aceptar el evento. Updates opcionales se aplican tras las acciones/decisión.
`updates.target` es un JSON Pointer no vacío relativo al contenido de request.data. `/validaciones/buro` actualiza ese campo dentro de data; nunca apunta a identidad, estado, revision o ACL de la solicitud. No se puede reemplazar la raíz. T06 rechazará destinos solapados/duplicados y comprobará que el resultado cumple dataSchema antes del commit.
Actions del MVP: REGISTER_DOCUMENT, INVOKE_INTEGRATION y NO_OP (sólo simulación, no publicable). No admiten URL, headers, secretos, clases Java ni clasificación de efectos enviada por el editor. Catálogo de despliegue aporta estas capacidades. ContractVersion/profileVersion pueden omitirse en borrador y deben resolverse/fijarse al publicar. Un perfil desconocido se rechaza semánticamente.
Tareas: estado/evento/inputSchema/ui. DOCUMENT_UPLOAD exige documentType, acceptedMimeTypes y maxBytes (techo del DSL 100 MiB, sujeto a un límite operativo menor); FORM permite fieldOrder. La UI no sustituye restricciones backend. T06 debe exigir equivalencia del inputSchema de tarea y evento y compatibilidad del documentType con REGISTER_DOCUMENT. El servicio documental verifica MIME real, tamaño, pertenencia, checksum y antimalware en T13.
## Límites y fronteras
El schema aplica 200 estados, 500 transiciones y 50 acciones por transición; limita también colecciones auxiliares. Longitud en bytes de definición (1 MiB), datos (256 KiB), profundidad AST 20, total 1.000 nodos por decisión, unicidad por ID, alcanzabilidad, catálogos, roles y destinos son comprobaciones semánticas/de recepción pendientes de T06/T09. No confundir arrays únicos con unicidad por clave ni límites de JSON Schema con protección frente a cargas antes de deserializar.
BureauResult exige score numérico y consultationId; no asume rango universal 0–100. DocumentValidationResult sólo admite VALID/INVALID: fallos técnicos no se convierten a INVALID. CustomerResult exige customerId cuando found=true. Son ejemplos de contrato interno, no conexión real a proveedores.

927
schemas/api.schema.json Normal file
View File

@ -0,0 +1,927 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "REST DTO contracts v1",
"$defs": {
"Key": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"Revision": {
"type": "integer",
"minimum": 0
},
"Data": {
"type": "object"
},
"UtcTimestamp": {
"type": "string",
"format": "date-time",
"pattern": "Z$"
},
"ExecutionStatus": {
"type": "string",
"enum": [
"PENDING",
"RUNNING",
"RETRY_WAIT",
"SUCCEEDED",
"FAILED",
"RECONCILIATION_REQUIRED"
]
},
"FlowRef": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"flowKey": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"version": {
"type": "integer",
"minimum": 1
}
},
"required": [
"id",
"flowKey",
"version"
],
"additionalProperties": false
},
"CreateRequest": {
"type": "object",
"properties": {
"typeKey": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"data": {
"$ref": "#/$defs/Data"
}
},
"required": [
"typeKey",
"data"
],
"additionalProperties": false
},
"EventCommand": {
"type": "object",
"properties": {
"event": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"data": {
"$ref": "#/$defs/Data"
}
},
"required": [
"event",
"data"
],
"additionalProperties": false
},
"RequestView": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"typeKey": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"flow": {
"$ref": "#/$defs/FlowRef"
},
"currentState": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"data": {
"$ref": "#/$defs/Data"
},
"revision": {
"$ref": "#/$defs/Revision"
},
"activeExecutionId": {
"type": "string",
"format": "uuid"
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "Z$"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "Z$"
}
},
"required": [
"id",
"typeKey",
"flow",
"currentState",
"data",
"revision",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
"AcceptedEvent": {
"type": "object",
"properties": {
"executionId": {
"type": "string",
"format": "uuid"
},
"requestId": {
"type": "string",
"format": "uuid"
},
"status": {
"$ref": "#/$defs/ExecutionStatus"
}
},
"required": [
"executionId",
"requestId",
"status"
],
"additionalProperties": false
},
"BusinessResult": {
"type": "object",
"properties": {
"outcome": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"fromState": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"toState": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"reasonCodes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"minItems": 0,
"maxItems": 100
}
},
"required": [
"outcome",
"fromState",
"toState"
],
"additionalProperties": false
},
"ExecutionError": {
"type": "object",
"properties": {
"code": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"message": {
"type": "string",
"maxLength": 2000
}
},
"required": [
"code"
],
"additionalProperties": false
},
"ExecutionView": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"requestId": {
"type": "string",
"format": "uuid"
},
"event": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"status": {
"$ref": "#/$defs/ExecutionStatus"
},
"result": {
"$ref": "#/$defs/BusinessResult"
},
"error": {
"$ref": "#/$defs/ExecutionError"
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "Z$"
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "Z$"
}
},
"required": [
"id",
"requestId",
"event",
"status",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": {
"status": {
"const": "SUCCEEDED"
}
}
},
"then": {
"required": [
"result"
],
"not": {
"required": [
"error"
]
}
},
"else": {
"not": {
"required": [
"result"
]
}
}
}
]
},
"AvailableEvent": {
"type": "object",
"properties": {
"event": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"inputSchema": {
"$ref": "flow-definition.schema.json#/$defs/InlineSchema"
},
"ui": {
"$ref": "flow-definition.schema.json#/$defs/Ui"
}
},
"required": [
"event",
"inputSchema"
],
"additionalProperties": false
},
"BlockedEvent": {
"type": "object",
"properties": {
"event": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"reasonCode": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
}
},
"required": [
"event",
"reasonCode"
],
"additionalProperties": false
},
"AvailableEvents": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"format": "uuid"
},
"revision": {
"type": "integer",
"minimum": 0
},
"events": {
"type": "array",
"items": {
"$ref": "#/$defs/AvailableEvent"
},
"minItems": 0,
"maxItems": 500
},
"blocked": {
"type": "array",
"items": {
"$ref": "#/$defs/BlockedEvent"
},
"minItems": 0,
"maxItems": 500
},
"blockedByExecutionId": {
"type": "string",
"format": "uuid"
}
},
"required": [
"requestId",
"revision",
"events",
"blocked"
],
"additionalProperties": false,
"allOf": [
{
"if": {
"required": [
"blockedByExecutionId"
]
},
"then": {
"properties": {
"events": {
"maxItems": 0
}
}
}
}
]
},
"Violation": {
"type": "object",
"properties": {
"pointer": {
"type": "string",
"pattern": "^(?:/(?:[^~/]|~[01])*)*$"
},
"code": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"message": {
"type": "string",
"minLength": 1,
"maxLength": 2000
}
},
"required": [
"pointer",
"code",
"message"
],
"additionalProperties": false
},
"ValidationReport": {
"type": "object",
"properties": {
"valid": {
"type": "boolean"
},
"errors": {
"type": "array",
"items": {
"$ref": "#/$defs/Violation"
},
"minItems": 0,
"maxItems": 1000
}
},
"required": [
"valid",
"errors"
],
"additionalProperties": false,
"oneOf": [
{
"properties": {
"valid": {
"const": true
},
"errors": {
"maxItems": 0
}
}
},
{
"properties": {
"valid": {
"const": false
},
"errors": {
"minItems": 1
}
}
}
]
},
"Problem": {
"type": "object",
"properties": {
"type": {
"type": "string",
"format": "uri-reference"
},
"title": {
"type": "string",
"minLength": 1
},
"status": {
"type": "integer",
"minimum": 400,
"maximum": 599
},
"detail": {
"type": "string",
"maxLength": 2000
},
"instance": {
"type": "string",
"format": "uri-reference"
},
"code": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"correlationId": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"errors": {
"type": "array",
"items": {
"$ref": "#/$defs/Violation"
},
"minItems": 0,
"maxItems": 1000
}
},
"required": [
"type",
"title",
"status",
"code",
"correlationId"
],
"additionalProperties": false
},
"AuditEntry": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"requestId": {
"type": "string",
"format": "uuid"
},
"executionId": {
"type": "string",
"format": "uuid"
},
"kind": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"actorSubject": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"flowId": {
"type": "string",
"format": "uuid"
},
"fromState": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"toState": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"detail": {
"$ref": "#/$defs/Data"
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "Z$"
}
},
"required": [
"id",
"requestId",
"kind",
"actorSubject",
"flowId",
"detail",
"createdAt"
],
"additionalProperties": false
},
"HistoryPage": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/$defs/AuditEntry"
},
"minItems": 0,
"maxItems": 100
},
"nextCursor": {
"type": "string",
"minLength": 1,
"maxLength": 2000
}
},
"required": [
"items"
],
"additionalProperties": false
},
"FlowView": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"DRAFT",
"PUBLISHED",
"RETIRED"
]
},
"revision": {
"type": "integer",
"minimum": 0
},
"definition": {
"$ref": "flow-definition.schema.json"
},
"checksum": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "Z$"
},
"createdBy": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"publishedAt": {
"type": "string",
"format": "date-time",
"pattern": "Z$"
}
},
"required": [
"id",
"status",
"revision",
"definition",
"createdAt",
"createdBy"
],
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": {
"status": {
"type": "string",
"enum": [
"PUBLISHED",
"RETIRED"
]
}
}
},
"then": {
"required": [
"checksum",
"publishedAt"
]
}
}
]
},
"ActivateFlow": {
"type": "object",
"properties": {
"flowId": {
"type": "string",
"format": "uuid"
}
},
"required": [
"flowId"
],
"additionalProperties": false
},
"RequestTypeView": {
"type": "object",
"properties": {
"typeKey": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"activeFlowId": {
"type": "string",
"format": "uuid"
},
"enabled": {
"type": "boolean"
},
"revision": {
"type": "integer",
"minimum": 0
}
},
"required": [
"typeKey",
"activeFlowId",
"enabled",
"revision"
],
"additionalProperties": false
},
"SimulationInput": {
"type": "object",
"properties": {
"state": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"requestData": {
"$ref": "#/$defs/Data"
},
"event": {
"$ref": "#/$defs/EventCommand"
},
"actor": {
"type": "object",
"properties": {
"subject": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"roles": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"minItems": 0,
"uniqueItems": true,
"maxItems": 100
},
"businessScope": {
"type": "string",
"minLength": 1,
"maxLength": 100
}
},
"required": [
"subject",
"roles",
"businessScope"
],
"additionalProperties": false
},
"results": {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"additionalProperties": {
"type": "object"
}
}
},
"required": [
"state",
"requestData",
"event",
"actor",
"results"
],
"additionalProperties": false
},
"SimulationResult": {
"type": "object",
"properties": {
"transitionId": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"outcome": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"toState": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"data": {
"$ref": "#/$defs/Data"
},
"matchedRuleIds": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"minItems": 0,
"maxItems": 1000
}
},
"required": [
"transitionId",
"outcome",
"toState",
"data",
"matchedRuleIds"
],
"additionalProperties": false
},
"ReconciliationCommand": {
"oneOf": [
{
"type": "object",
"properties": {
"mode": {
"const": "RESUME"
},
"evidence": {
"type": "object",
"properties": {
"reference": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"reason": {
"type": "string",
"minLength": 1,
"maxLength": 2000
}
},
"required": [
"reference",
"reason"
],
"additionalProperties": false
},
"verifiedActions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"actionId": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"result": {
"$ref": "#/$defs/Data"
},
"providerReference": {
"type": "string",
"minLength": 1,
"maxLength": 200
}
},
"required": [
"actionId",
"result"
],
"additionalProperties": false
},
"minItems": 1,
"maxItems": 50
}
},
"required": [
"mode",
"evidence",
"verifiedActions"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"mode": {
"const": "CLOSE_WITHOUT_TRANSITION"
},
"evidence": {
"type": "object",
"properties": {
"reference": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"reason": {
"type": "string",
"minLength": 1,
"maxLength": 2000
}
},
"required": [
"reference",
"reason"
],
"additionalProperties": false
},
"effectStatus": {
"type": "string",
"enum": [
"ABSENT_VERIFIED",
"COMPENSATED_VERIFIED"
]
}
},
"required": [
"mode",
"evidence",
"effectStatus"
],
"additionalProperties": false
}
]
},
"ReconciliationView": {
"type": "object",
"properties": {
"resolutionId": {
"type": "string",
"format": "uuid"
},
"executionId": {
"type": "string",
"format": "uuid"
},
"status": {
"type": "string",
"enum": [
"PENDING",
"FAILED"
]
},
"resolvedAt": {
"type": "string",
"format": "date-time",
"pattern": "Z$"
}
},
"required": [
"resolutionId",
"executionId",
"status",
"resolvedAt"
],
"additionalProperties": false
}
}
}

View File

@ -0,0 +1,885 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Flow definition DSL v1",
"type": "object",
"properties": {
"schemaVersion": {
"const": 1
},
"flowKey": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"version": {
"type": "integer",
"minimum": 1
},
"initialState": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"dataSchema": {
"$ref": "#/$defs/InlineSchema"
},
"initialDataSchema": {
"$ref": "#/$defs/InlineSchema"
},
"states": {
"type": "array",
"items": {
"$ref": "#/$defs/State"
},
"minItems": 1,
"maxItems": 200
},
"events": {
"type": "array",
"items": {
"$ref": "#/$defs/Event"
},
"minItems": 0,
"maxItems": 500
},
"transitions": {
"type": "array",
"items": {
"$ref": "#/$defs/Transition"
},
"minItems": 0,
"maxItems": 500
},
"decisions": {
"type": "array",
"items": {
"$ref": "#/$defs/Decision"
},
"minItems": 0,
"maxItems": 500
},
"tasks": {
"type": "array",
"items": {
"$ref": "#/$defs/Task"
},
"minItems": 0,
"maxItems": 500
}
},
"required": [
"schemaVersion",
"flowKey",
"version",
"initialState",
"states",
"events",
"transitions",
"decisions",
"tasks"
],
"additionalProperties": false,
"$defs": {
"Key": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"ContextPath": {
"type": "string",
"pattern": "^/(?:request/data|event/data|results|actor)(?:/(?:[^~/]|~[01])*)*$"
},
"DataPointer": {
"type": "string",
"pattern": "^/(?:[^~/]|~[01])+(?:/(?:[^~/]|~[01])*)*$"
},
"Value": {
"oneOf": [
{
"type": "object",
"properties": {
"path": {
"type": "string",
"pattern": "^/(?:request/data|event/data|results|actor)(?:/(?:[^~/]|~[01])*)*$"
}
},
"required": [
"path"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"literal": {}
},
"required": [
"literal"
],
"additionalProperties": false
}
]
},
"InlineSchema": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "object",
"properties": {
"type": {
"oneOf": [
{
"type": "string",
"enum": [
"object",
"array",
"string",
"number",
"integer",
"boolean",
"null"
]
},
{
"type": "array",
"items": {
"type": "string",
"enum": [
"object",
"array",
"string",
"number",
"integer",
"boolean",
"null"
]
},
"minItems": 1,
"uniqueItems": true,
"maxItems": 7
}
]
},
"title": {
"type": "string",
"maxLength": 200
},
"description": {
"type": "string",
"maxLength": 2000
},
"properties": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/InlineSchema"
}
},
"required": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 0,
"uniqueItems": true,
"maxItems": 200
},
"additionalProperties": {
"$ref": "#/$defs/InlineSchema"
},
"items": {
"$ref": "#/$defs/InlineSchema"
},
"enum": {
"type": "array",
"items": {},
"minItems": 1,
"uniqueItems": true,
"maxItems": 1000
},
"const": {},
"minLength": {
"type": "integer",
"minimum": 0
},
"maxLength": {
"type": "integer",
"minimum": 0
},
"pattern": {
"type": "string",
"maxLength": 1000
},
"format": {
"type": "string",
"enum": [
"uuid",
"date-time",
"date",
"email",
"uri"
]
},
"minimum": {
"type": "number"
},
"maximum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"minItems": {
"type": "integer",
"minimum": 0
},
"maxItems": {
"type": "integer",
"minimum": 0
},
"uniqueItems": {
"type": "boolean"
},
"minProperties": {
"type": "integer",
"minimum": 0
},
"maxProperties": {
"type": "integer",
"minimum": 0
},
"allOf": {
"type": "array",
"items": {
"$ref": "#/$defs/InlineSchema"
},
"minItems": 1,
"maxItems": 100
},
"anyOf": {
"type": "array",
"items": {
"$ref": "#/$defs/InlineSchema"
},
"minItems": 1,
"maxItems": 100
},
"oneOf": {
"type": "array",
"items": {
"$ref": "#/$defs/InlineSchema"
},
"minItems": 1,
"maxItems": 100
},
"not": {
"$ref": "#/$defs/InlineSchema"
},
"if": {
"$ref": "#/$defs/InlineSchema"
},
"then": {
"$ref": "#/$defs/InlineSchema"
},
"else": {
"$ref": "#/$defs/InlineSchema"
}
},
"required": [],
"additionalProperties": false
}
]
},
"Condition": {
"oneOf": [
{
"type": "object",
"properties": {
"op": {
"type": "string",
"enum": [
"EQ",
"NEQ",
"GT",
"GTE",
"LT",
"LTE",
"IN"
]
},
"left": {
"$ref": "#/$defs/Value"
},
"right": {
"$ref": "#/$defs/Value"
}
},
"required": [
"op",
"left",
"right"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"op": {
"const": "EXISTS"
},
"value": {
"type": "object",
"properties": {
"path": {
"type": "string",
"pattern": "^/(?:request/data|event/data|results|actor)(?:/(?:[^~/]|~[01])*)*$"
}
},
"required": [
"path"
],
"additionalProperties": false
}
},
"required": [
"op",
"value"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"op": {
"type": "string",
"enum": [
"AND",
"OR"
]
},
"args": {
"type": "array",
"items": {
"$ref": "#/$defs/Condition"
},
"minItems": 2,
"maxItems": 1000
}
},
"required": [
"op",
"args"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"op": {
"const": "NOT"
},
"arg": {
"$ref": "#/$defs/Condition"
}
},
"required": [
"op",
"arg"
],
"additionalProperties": false
}
]
},
"RetryPolicy": {
"type": "object",
"properties": {
"maxAttempts": {
"type": "integer",
"minimum": 1,
"maximum": 3
},
"initialBackoffMs": {
"type": "integer",
"minimum": 100,
"maximum": 60000
},
"maxBackoffMs": {
"type": "integer",
"minimum": 100,
"maximum": 120000
}
},
"required": [
"maxAttempts"
],
"additionalProperties": false
},
"Action": {
"oneOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"resultKey": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"retry": {
"$ref": "#/$defs/RetryPolicy"
},
"type": {
"const": "INVOKE_INTEGRATION"
},
"integrationKey": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"contractVersion": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"profile": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"profileVersion": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"inputs": {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"additionalProperties": {
"$ref": "#/$defs/Value"
},
"maxProperties": 200
}
},
"required": [
"id",
"type",
"integrationKey",
"inputs",
"resultKey"
],
"additionalProperties": false,
"dependentRequired": {
"profileVersion": [
"profile"
]
}
},
{
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"resultKey": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"retry": {
"$ref": "#/$defs/RetryPolicy"
},
"type": {
"const": "REGISTER_DOCUMENT"
},
"documentType": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"inputs": {
"type": "object",
"properties": {
"documentId": {
"$ref": "#/$defs/Value"
}
},
"required": [
"documentId"
],
"additionalProperties": false
}
},
"required": [
"id",
"type",
"documentType",
"inputs",
"resultKey"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"type": {
"const": "NO_OP"
},
"resultKey": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"output": {
"type": "object"
}
},
"required": [
"id",
"type",
"resultKey"
],
"additionalProperties": false
}
],
"description": "Tipos del MVP. NO_OP sólo simulación; publicación fija versiones de contrato/perfil desde catálogo confiable."
},
"State": {
"type": "object",
"properties": {
"key": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"terminal": {
"type": "boolean"
}
},
"required": [
"key",
"terminal"
],
"additionalProperties": false
},
"Event": {
"type": "object",
"properties": {
"key": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"inputSchema": {
"$ref": "#/$defs/InlineSchema"
}
},
"required": [
"key",
"inputSchema"
],
"additionalProperties": false
},
"Update": {
"type": "object",
"properties": {
"target": {
"type": "string",
"pattern": "^/(?:[^~/]|~[01])+(?:/(?:[^~/]|~[01])*)*$"
},
"value": {
"$ref": "#/$defs/Value"
}
},
"required": [
"target",
"value"
],
"additionalProperties": false,
"description": "target es relativo a request.data, nunca a la solicitud completa."
},
"Transition": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"from": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"event": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"allowedRoles": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"minItems": 1,
"uniqueItems": true,
"maxItems": 100
},
"guard": {
"$ref": "#/$defs/Condition"
},
"actions": {
"type": "array",
"items": {
"$ref": "#/$defs/Action"
},
"minItems": 0,
"maxItems": 50
},
"decision": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"destinations": {
"type": "object",
"propertyNames": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"additionalProperties": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"minProperties": 1
},
"to": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"outcome": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"updates": {
"type": "array",
"items": {
"$ref": "#/$defs/Update"
},
"minItems": 0,
"maxItems": 200
}
},
"required": [
"id",
"from",
"event",
"allowedRoles",
"actions"
],
"additionalProperties": false,
"oneOf": [
{
"required": [
"to",
"outcome"
],
"not": {
"anyOf": [
{
"required": [
"decision"
]
},
{
"required": [
"destinations"
]
}
]
}
},
{
"required": [
"decision",
"destinations"
],
"not": {
"anyOf": [
{
"required": [
"to"
]
},
{
"required": [
"outcome"
]
}
]
}
}
]
},
"Rule": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"when": {
"$ref": "#/$defs/Condition"
},
"output": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
}
},
"required": [
"id",
"when",
"output"
],
"additionalProperties": false
},
"Decision": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"hitPolicy": {
"type": "string",
"enum": [
"UNIQUE",
"FIRST"
]
},
"rules": {
"type": "array",
"items": {
"$ref": "#/$defs/Rule"
},
"minItems": 1,
"maxItems": 1000
},
"defaultOutput": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
}
},
"required": [
"id",
"hitPolicy",
"rules",
"defaultOutput"
],
"additionalProperties": false
},
"Ui": {
"oneOf": [
{
"type": "object",
"properties": {
"component": {
"const": "DOCUMENT_UPLOAD"
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"documentType": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"acceptedMimeTypes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z0-9.+-]+/[A-Za-z0-9.+-]+$"
},
"minItems": 1,
"uniqueItems": true,
"maxItems": 20
},
"maxBytes": {
"type": "integer",
"minimum": 1,
"maximum": 104857600
}
},
"required": [
"component",
"title",
"documentType",
"acceptedMimeTypes",
"maxBytes"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"component": {
"const": "FORM"
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"fieldOrder": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 0,
"uniqueItems": true,
"maxItems": 200
}
},
"required": [
"component",
"title"
],
"additionalProperties": false
}
]
},
"Task": {
"type": "object",
"properties": {
"state": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"event": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"ui": {
"$ref": "#/$defs/Ui"
},
"inputSchema": {
"$ref": "#/$defs/InlineSchema"
}
},
"required": [
"state",
"event",
"ui",
"inputSchema"
],
"additionalProperties": false
}
}
}

View File

@ -0,0 +1,118 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Normalized example integration results v1",
"$defs": {
"CustomerResult": {
"type": "object",
"properties": {
"found": {
"type": "boolean"
},
"customerId": {
"type": "string",
"minLength": 1,
"maxLength": 200
}
},
"required": [
"found"
],
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": {
"found": {
"const": true
}
}
},
"then": {
"required": [
"customerId"
]
}
}
]
},
"BureauResult": {
"type": "object",
"properties": {
"score": {
"type": "number"
},
"consultationId": {
"type": "string",
"minLength": 1,
"maxLength": 200
}
},
"required": [
"score",
"consultationId"
],
"additionalProperties": false
},
"DocumentValidationResult": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"VALID",
"INVALID"
]
},
"reasonCodes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"minItems": 0,
"maxItems": 100
}
},
"required": [
"status",
"reasonCodes"
],
"additionalProperties": false
},
"RegisteredDocumentResult": {
"type": "object",
"properties": {
"documentId": {
"type": "string",
"format": "uuid"
},
"documentType": {
"type": "string",
"pattern": "^[A-Za-z][A-Za-z0-9_.-]{0,99}$"
},
"mimeType": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"sizeBytes": {
"type": "integer",
"minimum": 0
},
"checksum": {
"type": "string",
"minLength": 1,
"maxLength": 128
}
},
"required": [
"documentId",
"documentType",
"mimeType",
"sizeBytes",
"checksum"
],
"additionalProperties": false
}
}
}

View File

@ -2,7 +2,7 @@
## ADR-001 — Base reproducible (2026-10-26)
Estado: seleccionada; verificación de runtime JDK 21 y PostgreSQL pendiente en T01.
Estado: verificada para T01 con compilación JDK 21 y smoke de salud contra server02; ver ADR-004 y evidencia E09 en PROGRESS. Pruebas de persistencia e integración completas corresponden a tareas posteriores.
| Componente | Selección | Motivo |
|---|---|---|
@ -41,3 +41,32 @@ Por instrucción expresa del usuario se adelanta el empaquetado del arquetipo: C
Dockerfile multietapa con Temurin 21.0.12.1_1 (JDK para construir, JRE para ejecutar), tags verificados en el [catálogo oficial](https://github.com/docker-library/official-images/blob/master/library/eclipse-temurin). Runtime no root y healthcheck de readiness. Maven Wrapper construye con package y ejecuta pruebas unitarias; verify con integración permanece en CI. El contexto permite sólo fuentes, POM y Wrapper; excluye .env, target y archivos del IDE.
Compose exige DB_URL/usuario/contraseña y parámetros JWT, conserva el puerto interno 8080 y publica localhost:8080 por defecto. Se incluye host-gateway para PostgreSQL en el anfitrión. La construcción y conexión real están pendientes de un entorno Docker y valores externos. Referencias: [build multietapa](https://docs.docker.com/build/building/multi-stage/) e [interpolación de Compose](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/).
## ADR-004 — Cierre verificable de T01 con despliegue en la red local (2026-10-26)
El servicio ya está desplegado por el usuario en server02:8787 con PostgreSQL externo. Se interpreta el health local de T01 como comprobación del entorno de desarrollo en esa red, sin requerir duplicar el despliegue en el equipo del agente. La compilación se realiza localmente con Temurin JDK 21 oficial, descargado a .tools y validado por SHA256 del proveedor; no se modifica el Java del sistema.
El perfil explícito deployment-smoke ejecuta cinco pruebas Failsafe de sólo lectura contra una URL obligatoria: health, readiness, liveness, rechazo sin token y rechazo con token malformado. No se omiten errores de conectividad ni pruebas fallidas. Se preservan los patrones normales de integración *IT, IT* y *ITCase al activar el perfil.
El build de T01 y la salud del despliegue se verifican por separado: no se afirma que el JAR recién generado haya sido desplegado ni se acredita el runtime remoto por una respuesta health. No se redepliega el servidor ni se accede directamente a sus credenciales o base. Testcontainers, migraciones, JWT válido/roles y A01–A20 siguen pendientes; este smoke no los sustituye. CI no depende de server02: el perfil requiere activación explícita fuera del build normal.
## ADR-005 — Contratos T02: REST y DSL v1 (2026-10-26)
Estado: implementado como contrato y pruebas, no como endpoints. OpenAPI 3.1.0 y JSON Schema 2020-12 en archivos separados con referencias relativas locales. No se incorpora springdoc: la documentación contractual es independiente de Boot y no habilita rutas administrativas antes de T07.
Se concreta lo no detallado por la especificación sin cambiar idempotencia, aislamiento de versiones ni autorización:
- Rutas fijas usan to/outcome; rutas calculadas usan decision/destinations, con el output de decisión como resultado de negocio. EXISTS usa value.path; NOT usa arg; AND/OR usan args. Mappings admiten exactamente path o literal.
- dataSchema e initialDataSchema permiten describir datos finales/entrada inicial. Borradores y ejemplo mínimo original pueden omitirlos estructuralmente; publicación T06 exige dataSchema de objeto. InitialDataSchema ausente utiliza dataSchema. No se confunde validez estructural con publicación válida.
- Schemas embebidos limitan el vocabulario a validación declarativa y no admiten referencias externas. Se conservan boolean schemas y combinadores. Se rechazan propiedades desconocidas de DSL/DTOs; datos configurables requieren segunda validación contra su schema de evento/flujo. Rechazo de JSON con claves duplicadas precede validación.
- JSON Pointer de updates es relativo a request.data. Restricciones de identidad/ACL permanecen fuera de ese espacio. Publicación verifica destinos solapados y schemas resultantes; runtime verifica tipos/ambigüedad. Estas comprobaciones no se simulan en T02.
- Publicar y retirar exigen If-Match adicional al exigido por eventos/edición/activación. Esto concreta su concurrencia y evita operar sobre una revisión diferente a la revisada. Alta de request_type se provisionará por migración/configuración en T03/T06; activar uno inexistente devuelve 404.
- Conciliación acepta RESUME con evidencia y resultados verificados por acción, o CLOSE_WITHOUT_TRANSITION con ausencia/compensación comprobada. Prohíbe escoger destino. RESUME reencola PENDING; cierre marca FAILED sin cambiar negocio. Idempotencia durable, autorización por scope y evidencia verificada son requisitos para T12.
- Replays de creación devuelven 201 con la misma identidad; eventos devuelven 202 con executionId estable y status actual. Los errores tienen code/correlationId; fechas UTC Z, opcionales omitidos, cursor opaco máximo 100.
- Se fijan techos estructurales auxiliares: 500 eventos/decisiones/tareas, 100 roles por transición, 200 updates, reintentos máximo 3 y tamaño documental máximo 100 MiB en DSL. Los límites operativos podrán ser menores; los 1 MiB/256 KiB y límites AST de la especificación requieren control adicional antes/durante deserialización en T06/T09.
Herramientas fijadas, únicamente scope test: networknt json-schema-validator 1.5.9 (2020-12 con format assertions) y swagger-parser-v3 2.1.35 (OpenAPI 3.1). Jackson 2 de estas herramientas convive sólo en tests con Jackson 3 del runtime Boot 4.1.2. Se comprobó compatibilidad mediante clean verify con Java 21.
Se conserva una copia del schema oficial OpenAPI 3.1 (2022-10-07), SHA256 DA01BA28852CAC0DE53893797CB8D1942BC3B05084F526DCC216717DEC314ED0, para validar sin red. Swagger Parser con resolve=true intentó expandir indefinidamente las referencias recursivas del AST: se usa resolve=false y comprobación explícita de todos los $ref locales. Networknt valida los schemas y ejemplos reales. No se relajan límites ni se ignoran referencias rotas para resolver esa incompatibilidad.
Referencias: [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0), [schema oficial](https://spec.openapis.org/oas/3.1/schema/2022-10-07), [networknt](https://github.com/networknt/json-schema-validator), [Swagger Parser y soporte 3.1](https://github.com/swagger-api/swagger-parser). Detalles de uso en contracts/README.md y schemas/README.md.

View File

@ -30,23 +30,27 @@ El alcance actual es crear el arquetipo y dejar preparada la ejecución del plan
Dependencias: —. Entregables: pom.xml, mvnw*, .mvn, configuración, ADR-001.
- [ ] Fijar versiones y BOM.
- [ ] Generar Wrapper oficial.
- [ ] Preparar paquetes y configuración.
- [ ] Ejecutar compilación y smoke test de salud con JDK 21/PostgreSQL.
- [x] Fijar versiones y BOM.
- [x] Generar Wrapper oficial.
- [x] Preparar paquetes y configuración.
- [x] Ejecutar compilación y smoke test de salud con JDK 21/PostgreSQL.
Verificación: JDK 21; ./mvnw clean verify; arranque local y HTTP health. Cierre: Wrapper reproducible, compilación y health local comprobados.
Cerrado el 2026-10-26: `clean verify -Pdeployment-smoke -Dsmoke.baseUrl=http://server02:8787` con JDK 21.0.12.1 y Maven 3.9.12, 5 pruebas aprobadas, 0 omitidas. Compilación en el equipo de desarrollo y smoke sobre el despliegue de la red local con PostgreSQL externo, conforme a ADR-004. Evidencia detallada en PROGRESS.
## T02 — Contratos OpenAPI y JSON Schema del DSL
Dependencias: T01. Entregables: contracts/openapi.yaml, schemas/, ejemplos contractuales.
- [ ] Definir rutas, DTOs y errores.
- [ ] Definir schema del flujo/AST/tareas.
- [ ] Validar ejemplos y casos inválidos automáticamente.
- [x] Definir rutas, DTOs y errores.
- [x] Definir schema del flujo/AST/tareas.
- [x] Validar ejemplos y casos inválidos automáticamente.
Verificación: Validador OpenAPI/JSON Schema dentro de verify. Cierre: OpenAPI 3.1 y schemas completos; ejemplos validados.
Cerrado el 2026-10-26: clean verify con Java 21, 92 comprobaciones, 0 fallos/errores/omisiones. Contrato de 14 operaciones, DSL 2020-12, DTOs y resultados normalizados de ejemplo. Las pruebas verifican estructura/ejemplos/referencias, no comportamiento del motor; ver ADR-005 y evidencia E10.
## T03 — Modelo PostgreSQL y Flyway
Dependencias: T02. Entregables: db/migration/, pruebas *IT.

View File

@ -1,14 +1,14 @@
# Seguimiento de implementación
Actualizado: 2026-10-26. Entrega actual: arquetipo y planificación. El motor no está implementado.
Actualizado: 2026-10-26. T01 y T02 DONE. Siguiente tarea: T03 (PostgreSQL/Flyway). El motor y endpoints de negocio no están implementados.
Estados permitidos: PENDING, IN_PROGRESS, BLOCKED, DONE. No más de una tarea principal IN_PROGRESS. Consultar IMPLEMENTATION_PLAN para subtareas y criterios.
| ID | Estado | Dependencias | Archivos | Comandos/checks | Resultado | Bloqueo | Siguiente paso |
|---|---|---|---|---|---|---|---|
| T01 | BLOCKED | — | pom.xml, mvnw*, .mvn, configuración, ADR-001 | Ver evidencia E01–E04 | Arquetipo creado; package con JDK 24 y release 21 aprobado; cierre pendiente | JDK 21 y Docker/PostgreSQL no disponibles para validación local completa | Validar con JDK 21 y PostgreSQL; añadir smoke test de salud |
| T02 | PENDING | T01 | contracts/openapi.yaml, schemas/, ejemplos contractuales | Pendiente: Validador OpenAPI/JSON Schema dentro de verify | No ejecutado | Dependencias sin completar | Definir rutas, DTOs y errores |
| T03 | PENDING | T02 | db/migration/, pruebas *IT | Pendiente: Testcontainers PostgreSQL, rollback y migraciones | No ejecutado | Dependencias sin completar | Crear tablas, FKs e índices |
| T01 | DONE | — | pom.xml, mvnw*, .mvn, configuración, DeploymentSmokeCheck.java, ADR-001/004 | E09: clean verify con JDK 21 y deployment-smoke | BUILD SUCCESS; 5 pruebas, 0 fallos, 0 errores, 0 omitidas | Ninguno para T01 | Iniciar T02: OpenAPI y JSON Schema |
| T02 | DONE | T01 DONE | contracts/openapi.yaml, schemas/*.schema.json, examples/contracts, ContractValidationTest.java, ADR-005 | E10: clean verify con Java 21 | BUILD SUCCESS; 92 comprobaciones, 0 fallos/errores/omisiones | Ninguno para T02 | Iniciar T03: migraciones PostgreSQL y Testcontainers |
| T03 | PENDING | T02 DONE | db/migration/, pruebas *IT | Pendiente: Testcontainers PostgreSQL, rollback y migraciones | No ejecutado | Docker requerido para pruebas; dependencia T02 satisfecha | Crear tablas, FKs e índices y preparar entorno Testcontainers |
| T04 | PENDING | T02,T03 | flow/, request/, execution/, decision/, action/ | Pendiente: Pruebas de dominio y dependencias entre capas | No ejecutado | Dependencias sin completar | Definir records y errores estables |
| T05 | PENDING | T04 | decision/, pruebas de AST | Pendiente: JUnit parametrizado: 71/70/61/60, tipos y solapamientos | No ejecutado | Dependencias sin completar | Implementar operadores/JSON Pointer/BigDecimal |
| T06 | PENDING | T03,T04,T05 | flow/, admin_audit, pruebas *IT | Pendiente: Pruebas PostgreSQL de carreras publicación/activación/retiro | No ejecutado | Dependencias sin completar | Validar grafo, mappings, roles y capacidades |
@ -24,7 +24,34 @@ Estados permitidos: PENDING, IN_PROGRESS, BLOCKED, DONE. No más de una tarea pr
| T16 | PENDING | T15 | src/test/, CI, matriz A01–A20 | Pendiente: ./mvnw clean verify con Docker, sin skips de integración | No ejecutado | Dependencias sin completar | Automatizar A01–A20 |
| T17 | PENDING | T16 | Dockerfile, compose.yaml, README, RUNBOOK, examples/http, CI | Pendiente: Build de imagen, compose, verify y demo completa de sección 16 | No ejecutado | Dependencias sin completar | Construir imagen y Compose completos |
## Evidencias — 2026-10-26
## E10 — Cierre T02 — 2026-10-26 11:58 (America/Mexico_City)
- Estado: DONE. 14 operaciones REST OpenAPI 3.1.0, DTOs, schema DSL 2020-12, ejemplos de resultados normalizados y fixtures contractuales completos para esta fase.
- Comando: `.\mvnw.cmd --batch-mode --no-transfer-progress clean verify` con JAVA_HOME=.tools/jdk21/jdk-21.0.12.1+1, Maven Wrapper 3.9.12 y Enforcer habilitado.
- Resultado: BUILD SUCCESS (11.969 s); ContractValidationTest: 92 comprobaciones, 0 fallos, 0 errores, 0 omitidas. Reportes en target/surefire-reports/com.organizacion.orchestrator.ContractValidationTest.txt y TEST-com.organizacion.orchestrator.ContractValidationTest.xml.
- Cobertura: schema oficial OpenAPI 3.1, parseo Swagger, meta-schema 2020-12, resolución de todos los $ref locales, 52 fixtures (29 válidos/23 inválidos), ejemplos embebidos, rutas/seguridad/cabeceras, aridad de condiciones, límites estructurales, UUID, claves JSON duplicadas y rechazo de inyección de datos de proveedor según inputSchema.
- Corrección durante la tarea: primera ejecución tuvo 91/92 aprobadas; Swagger Parser expandía el schema recursivo hasta exceder profundidad. Se corrigió con resolve=false, manteniendo verificación independiente de referencias y validación real de ejemplos. Segunda ejecución aprobó todo; no hubo tests omitidos.
- Dependencias sólo test: networknt 1.5.9 y swagger-parser-v3 2.1.35; API Jackson 2 gestionada por BOM. Inspección del JAR con zipfile confirmó ausencia de validadores/Swagger y Jackson 2 databind/YAML en BOOT-INF/lib.
- Dockerfile/.dockerignore incluyen contracts, schemas y examples/contracts en build para ejecutar estas pruebas; no se construyó una imagen en esta sesión (Docker no disponible en este equipo). No se modificó ni redesplegó server02.
- Archivos principales: contracts/openapi.yaml, contracts/validation/openapi-3.1.schema.json, schemas/{flow-definition,api,integration-results}.schema.json, examples/contracts/manifest.json y fixtures, examples/http/contracts.http, pom.xml, ContractValidationTest.java, README/documentación y ADR-005.
- Alcance: validación de contratos y datos, sin endpoints, base, providers ni motor. La evaluación de scoring/AST, validación semántica de grafo, permisos efectivos y A01–A20 corresponden a tareas posteriores; no se declaran aprobados. El smoke remoto de T01 no se repitió por no haber cambios de runtime.
- Siguiente tarea: T03 (PENDING), dependencias satisfechas; requiere entorno Docker/PostgreSQL para Testcontainers.
## E09 — Cierre T01 — 2026-10-26 11:20 (America/Mexico_City)
- Estado final: DONE. Esta evidencia supera los bloqueos históricos de T01 descritos abajo; no cierra T07/T16/T17.
- JDK: Eclipse Temurin 21.0.12.1+1, instalado sólo en .tools/jdk21/jdk-21.0.12.1+1 (ignorado en Git). Descarga oficial mediante API Adoptium, SHA256 comprobado: f9d6e191ab098c0d416e7d588a24420a8621cd2f4720dab2459b8b7b2d2d8b4e.
- Maven Wrapper: 3.9.12. `mvnw.cmd -version` confirmó JDK 21.0.12.1. Enforcer validó Java y Maven; no se omitió ninguna regla.
- Comando: `.\mvnw.cmd --batch-mode --no-transfer-progress clean verify -Pdeployment-smoke '-Dsmoke.baseUrl=http://server02:8787'`, con JAVA_HOME apuntando al JDK anterior sólo en esa sesión.
- Resultado: BUILD SUCCESS (29.285 s); 5 pruebas ejecutadas, 0 fallos, 0 errores, 0 omitidas. Compilación release 21 y JAR ejecutable generado.
- Pruebas: health/readiness/liveness HTTP 200 y JSON status UP; ruta de solicitudes sin token HTTP 401 y challenge Bearer; token malformado HTTP 401.
- Reportes: target/failsafe-reports/com.organizacion.orchestrator.DeploymentSmokeCheck.txt y TEST-com.organizacion.orchestrator.DeploymentSmokeCheck.xml. Son artefactos locales; este registro conserva el resumen sin copiar propiedades del entorno.
- JAR: target/orquestador-nmp-0.1.0-SNAPSHOT.jar, SHA256 BA94B68388E26575F3CB6370E360B7E1D2C859853359CEE1D2D4C931D884B030.
- Archivos: pom.xml, src/test/java/com/organizacion/orchestrator/DeploymentSmokeCheck.java, README de pruebas, README.md, specs/RUNBOOK.md, specs/IMPLEMENTATION_PLAN.md, specs/DECISIONS.md y este registro.
- Alcance: build local con Java 21 más smoke del despliegue en red local con PostgreSQL externo, documentado en ADR-004. No hubo redeploy ni prueba de JWT válido, roles, endpoints funcionales o persistencia de negocio. La salud no certifica versión del runtime remoto ni identidad de su JAR.
- Siguiente tarea habilitada: T02 (PENDING, sin bloqueo de dependencias).
## Evidencias históricas — 2026-10-26
- E01: `java -version` y `mvn -version`: Java 17.0.12 y Maven 3.9.12. Se encontró también Corretto 24; no JDK 21 en las ubicaciones revisadas. `Get-Command docker` no encontró Docker en PATH.
- E02: `mvn -B -N org.apache.maven.plugins:maven-wrapper-plugin:3.3.4:wrapper '-Dmaven=3.9.12' '-Dtype=only-script'`: BUILD SUCCESS. Wrapper oficial creado con distribución 3.9.12. Se corrigió una primera invocación cuya propiedad sin comillas fue fragmentada por PowerShell.
@ -36,6 +63,19 @@ Estados permitidos: PENDING, IN_PROGRESS, BLOCKED, DONE. No más de una tarea pr
## Próxima sesión
### E08 — Prueba del servicio remoto (2026-10-26)
Comprobación ejecutada con Invoke-WebRequest -UseBasicParsing -TimeoutSec 10 contra http://server02:8787:
| GET | HTTP | Resultado |
|---|---|---|
| /actuator/health | 200 | status UP |
| /actuator/health/readiness | 200 | status UP |
| /actuator/health/liveness | 200 | status UP |
| /api/v1/requests, sin token | 401 | Protección sin autenticación confirmada |
Con la configuración del repositorio, readiness incluye PostgreSQL. Esta prueba confirma salud remota y rechazo sin token; no acredita la existencia del endpoint de solicitudes, validación de JWT real, versión del runtime remoto ni la suite clean verify. El intento anterior contra serer02 falló por nombre incorrecto y se corrigió a server02. Las limitaciones de arranque remoto registradas anteriormente quedan superadas por esta evidencia; T01 conserva pendiente el cierre reproducible con Java 21.
### Preparación Docker solicitada — 2026-10-26
- Archivos: Dockerfile, .dockerignore, compose.yaml, .env.example, README.md, RUNBOOK.md y ADR-003.
@ -45,8 +85,8 @@ Estados permitidos: PENDING, IN_PROGRESS, BLOCKED, DONE. No más de una tarea pr
- Siguiente paso: completar .env, ejecutar docker compose config --quiet y docker compose up --build -d; comprobar readiness y logs. T01 continúa BLOCKED y T17 PENDING hasta satisfacer sus criterios completos.
1. Leer este registro, especificación y cambios del repositorio.
2. Resolver T01: configurar JDK 21, PostgreSQL y valores de entorno JWT; incorporar y ejecutar smoke test, `clean verify` y health local.
3. Registrar evidencia y marcar T01 DONE sólo cuando cumpla todos sus criterios; entonces iniciar T02.
2. T01/T02 DONE. Preparar T03: migraciones desde cero, constraints, índices, triggers y entorno PostgreSQL/Testcontainers.
3. Registrar evidencia de T03 y mantener una única tarea principal IN_PROGRESS. No repetir verificaciones aprobadas sin cambios o fallos que lo justifiquen.
## Plantilla de evidencia adicional

View File

@ -63,6 +63,14 @@ Configurar JDK 21 y las seis variables DB/JWT en la sesión o Run Configuration.
## Diagnóstico
Para automatizar la comprobación del despliegue con JDK 21:
```powershell
.\mvnw.cmd clean verify -Pdeployment-smoke '-Dsmoke.baseUrl=http://server02:8787'
```
Son cinco pruebas de sólo lectura; resultados en `target/failsafe-reports`. El servicio debe estar iniciado con su PostgreSQL externo. La URL es obligatoria; no hay omisión silenciosa por falta de conectividad. El perfil no despliega la aplicación ni cambia la configuración del servidor. El build normal no ejecuta este smoke remoto, para no depender de la red privada desde CI.
- Variable faltante: completar el valor requerido en `.env`.
- Conexión rechazada/timeout: revisar DNS, puerto, firewall y acceso desde Docker; no usar localhost para el anfitrión.
- Error de autenticación PostgreSQL: comprobar usuario, contraseña y permisos.

View File

@ -0,0 +1,237 @@
package com.organizacion.orchestrator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SchemaLocation;
import com.networknt.schema.SchemaValidatorsConfig;
import com.networknt.schema.SpecVersion;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.ParseOptions;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
/** Contract checks only: no HTTP, database, provider or running service is needed. */
class ContractValidationTest {
private static final Path ROOT = Path.of("").toAbsolutePath().normalize();
private static final Path OPENAPI = ROOT.resolve("contracts/openapi.yaml");
private static final ObjectMapper JSON = new ObjectMapper().enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
private static final ObjectMapper YAML = new ObjectMapper(new YAMLFactory()).enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
private static final JsonSchemaFactory FACTORY = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
private static final SchemaValidatorsConfig CONFIG = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
private static JsonNode read(Path path) throws IOException {
return (path.toString().endsWith(".yaml") ? YAML : JSON).readTree(path.toFile());
}
private static JsonSchema schema(URI uri) {
return FACTORY.getSchema(SchemaLocation.of(uri.toString()), CONFIG);
}
private static URI schemaUri(String reference) {
return ROOT.resolve("schemas/").toUri().resolve(reference);
}
private static void valid(JsonSchema schema, JsonNode value) {
var errors = schema.validate(value);
assertTrue(errors.isEmpty(), () -> errors.toString());
}
@Test
void openApiConformsToOfficial31SchemaAndParser() throws Exception {
var document = read(OPENAPI);
valid(schema(ROOT.resolve("contracts/validation/openapi-3.1.schema.json").toUri()), document);
var options = new ParseOptions();
// Recursive DSL references must remain references. Resolution is checked separately,
// and networknt validates instances against the actual external schema documents.
options.setResolve(false);
var parsed = new OpenAPIV3Parser().readLocation(OPENAPI.toString(), null, options);
assertNotNull(parsed.getOpenAPI(), () -> parsed.getMessages().toString());
assertTrue(parsed.getMessages().isEmpty(), () -> parsed.getMessages().toString());
assertEquals("3.1.0", parsed.getOpenAPI().getOpenapi());
}
@TestFactory
Stream<DynamicTest> schemaDocumentsConformTo202012MetaSchema() throws Exception {
var meta = schema(URI.create("https://json-schema.org/draft/2020-12/schema"));
try (var files = Files.list(ROOT.resolve("schemas"))) {
return files.filter(p -> p.toString().endsWith(".json")).sorted().toList().stream()
.map(p -> DynamicTest.dynamicTest(p.getFileName().toString(), () -> valid(meta, read(p))));
}
}
@TestFactory
Stream<DynamicTest> allFixtureExpectationsHold() throws Exception {
List<DynamicTest> tests = new ArrayList<>();
var manifest = read(ROOT.resolve("examples/contracts/manifest.json"));
Set<String> registered = new HashSet<>();
for (var entry : manifest) {
String file = entry.path("file").asText();
assertTrue(registered.add(file), "Duplicate fixture: " + file);
tests.add(DynamicTest.dynamicTest(file, () -> {
var instance = read(ROOT.resolve("examples/contracts").resolve(file));
var errors = schema(schemaUri(entry.path("schema").asText())).validate(instance);
assertEquals(entry.path("valid").asBoolean(), errors.isEmpty(), () -> file + ": " + errors);
}));
}
try (var files = Files.walk(ROOT.resolve("examples/contracts"))) {
Set<String> actual = new HashSet<>();
files.filter(p -> p.toString().endsWith(".json") && !p.getFileName().toString().equals("manifest.json"))
.forEach(p -> actual.add(ROOT.resolve("examples/contracts").relativize(p).toString().replace('\\', '/')));
assertEquals(actual, registered, "Every fixture must be registered and validated");
}
return tests.stream();
}
@TestFactory
Stream<DynamicTest> everyOpenApiExampleMatchesItsSchema() throws Exception {
List<DynamicTest> tests = new ArrayList<>();
walk(read(OPENAPI), node -> {
if (node.isObject() && node.has("schema") && node.has("example")) {
String reference = node.path("schema").path("$ref").asText();
assertFalse(reference.isBlank(), "Media example requires a reusable schema");
tests.add(DynamicTest.dynamicTest("OpenAPI example " + reference + " #" + tests.size(),
() -> valid(schema(OPENAPI.toUri().resolve(reference)), node.get("example"))));
}
});
assertTrue(tests.size() >= 30, "Expected examples for every body and success/error response");
return tests.stream();
}
@Test
void allReferencesAreLocalAndResolve() throws Exception {
List<Path> documents = new ArrayList<>(List.of(OPENAPI));
try (var files = Files.list(ROOT.resolve("schemas"))) {
documents.addAll(files.filter(p -> p.toString().endsWith(".json")).toList());
}
for (Path document : documents) {
walk(read(document), node -> {
if (node.has("$ref")) {
URI resolved = document.toUri().resolve(node.get("$ref").asText());
assertEquals("file", resolved.getScheme(), "Contract references must not fetch from the network");
try {
Path target = Path.of(new URI(resolved.getScheme(), resolved.getAuthority(), resolved.getPath(), null, null)).normalize();
assertTrue(target.startsWith(ROOT), "Reference escapes repository");
var targetDocument = read(target);
if (resolved.getFragment() != null) {
assertFalse(targetDocument.at(resolved.getFragment()).isMissingNode(), resolved.toString());
}
} catch (Exception e) {
throw new AssertionError("Unresolved contract reference " + resolved, e);
}
}
});
}
}
@Test
void requiredRoutesSecurityAndConcurrencyHeadersArePresent() throws Exception {
JsonNode document = read(OPENAPI);
assertEquals("/api/v1", document.at("/servers/0/url").asText());
assertTrue(document.at("/security/0").has("bearerAuth"));
Set<String> expected = Set.of("POST /requests", "GET /requests/{id}", "GET /requests/{id}/available-events",
"POST /requests/{id}/events", "GET /executions/{id}", "GET /requests/{id}/history",
"POST /admin/flows", "PUT /admin/flows/{id}", "POST /admin/flows/{id}/validate",
"POST /admin/flows/{id}/simulate", "POST /admin/flows/{id}/publish",
"PUT /admin/request-types/{key}/active-flow", "POST /admin/flows/{id}/retire",
"POST /admin/executions/{id}/resolve");
Set<String> actual = new HashSet<>();
Set<String> operationIds = new HashSet<>();
document.get("paths").fields().forEachRemaining(path -> path.getValue().fields().forEachRemaining(method -> {
var op = method.getValue();
actual.add(method.getKey().toUpperCase() + " " + path.getKey());
assertTrue(operationIds.add(op.path("operationId").asText()));
assertFalse(op.has("security"), "Do not override global JWT protection");
assertTrue(op.path("responses").has("401"));
if (path.getKey().startsWith("/admin/")) assertFalse(op.path("x-required-roles").isEmpty());
if (method.getKey().equals("post") && (path.getKey().equals("/requests")
|| path.getKey().endsWith("/events") || path.getKey().endsWith("/resolve"))) {
assertParameter(op, "IdempotencyKey");
}
if (method.getKey().equals("put") || path.getKey().endsWith("/events")
|| path.getKey().endsWith("/publish") || path.getKey().endsWith("/retire")) {
assertParameter(op, "IfMatch");
assertTrue(op.path("responses").has("412"));
assertTrue(op.path("responses").has("428"));
}
}));
assertEquals(expected, actual);
assertEquals(100, document.at("/components/parameters/Limit/schema/maximum").asInt());
assertTrue(document.at("/paths/~1requests/post/responses/201/headers").has("Location"));
assertTrue(document.at("/paths/~1requests/post/responses/201/headers").has("ETag"));
assertTrue(document.at("/paths/~1requests~1{id}~1events/post/responses/202/headers").has("Location"));
}
private static void assertParameter(JsonNode operation, String name) {
for (var parameter : operation.path("parameters")) {
if (parameter.path("$ref").asText().equals("#/components/parameters/" + name)) return;
}
fail("Missing parameter: " + name);
}
@Test
void embeddedInputSchemasValidatePayloadsAndPreventProviderDataInjection() throws Exception {
for (String flow : List.of("scoring", "document", "form")) {
JsonNode definition = read(ROOT.resolve("examples/contracts/flows/" + flow + ".json"));
walk(definition, n -> {
for (String field : List.of("inputSchema", "dataSchema", "initialDataSchema")) {
if (n.has(field)) valid(schema(URI.create("https://json-schema.org/draft/2020-12/schema")), n.get(field));
}
});
}
var document = read(ROOT.resolve("examples/contracts/flows/document.json"));
var input = FACTORY.getSchema(document.at("/events/0/inputSchema"), CONFIG);
valid(input, JSON.readTree("{\"documentId\":\"00000000-0000-0000-0000-000000000123\"}"));
assertFalse(input.validate(JSON.readTree("{\"documentId\":\"bad-uuid\"}")).isEmpty());
assertFalse(input.validate(JSON.readTree("{\"documentId\":\"00000000-0000-0000-0000-000000000123\",\"score\":99}")).isEmpty());
assertFalse(input.validate(JSON.readTree("{\"url\":\"https://untrusted.example/document\"}")).isEmpty());
}
@Test
void structuralLimitsAndConditionArityAreEnforced() throws Exception {
var flow = (ObjectNode) read(ROOT.resolve("examples/contracts/flows/scoring.json"));
var schema = schema(schemaUri("flow-definition.schema.json"));
var tooManyStates = flow.deepCopy();
var states = tooManyStates.withArray("states");
while (states.size() < 201) states.addObject().put("key", "STATE_" + states.size()).put("terminal", true);
assertFalse(schema.validate(tooManyStates).isEmpty());
var tooManyActions = flow.deepCopy();
var actions = (com.fasterxml.jackson.databind.node.ArrayNode) tooManyActions.at("/transitions/0/actions");
while (actions.size() < 51) actions.add(actions.get(0).deepCopy());
assertFalse(schema.validate(tooManyActions).isEmpty());
var condition = schema(schemaUri("flow-definition.schema.json#/$defs/Condition"));
valid(condition, JSON.readTree("{\"op\":\"NOT\",\"arg\":{\"op\":\"EXISTS\",\"value\":{\"path\":\"/event/data/x\"}}}"));
for (String invalid : List.of("{\"op\":\"NOT\",\"args\":[]}", "{\"op\":\"AND\",\"args\":[]}",
"{\"op\":\"GT\",\"left\":{\"literal\":1}}", "{\"op\":\"EXISTS\",\"value\":{\"literal\":true}}")) {
assertFalse(condition.validate(JSON.readTree(invalid)).isEmpty(), invalid);
}
}
@Test
void duplicateJsonKeysAreRejectedBeforeValidation() {
assertThrows(IOException.class, () -> JSON.readTree("{\"schemaVersion\":1,\"schemaVersion\":2}"));
}
private static void walk(JsonNode node, Consumer<JsonNode> visitor) {
visitor.accept(node);
if (node.isContainerNode()) node.forEach(child -> walk(child, visitor));
}
}

View File

@ -0,0 +1,66 @@
package com.organizacion.orchestrator;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import tools.jackson.databind.json.JsonMapper;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** Read-only checks against an explicitly selected deployment, without credentials. */
class DeploymentSmokeCheck {
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER)
.build();
private static URI baseUri;
@BeforeAll
static void requireExplicitTarget() {
String configured = System.getProperty("smoke.baseUrl", "");
assertTrue(configured.startsWith("http://") || configured.startsWith("https://"),
"Provide -Dsmoke.baseUrl=http://host:port with -Pdeployment-smoke");
baseUri = URI.create(configured);
assertTrue(baseUri.getHost() != null && baseUri.getUserInfo() == null
&& baseUri.getQuery() == null && baseUri.getFragment() == null
&& (baseUri.getPath().isEmpty() || baseUri.getPath().equals("/")),
"smoke.baseUrl must be an origin without credentials, query, fragment or context path");
}
@ParameterizedTest
@ValueSource(strings = {"/actuator/health", "/actuator/health/readiness", "/actuator/health/liveness"})
void healthIsUp(String path) throws Exception {
var response = CLIENT.send(request(path).build(), HttpResponse.BodyHandlers.ofString());
assertEquals(200, response.statusCode(), path);
var json = JsonMapper.builder().build().readTree(response.body());
assertEquals("\"UP\"", json.path("status").toString(), path);
}
@Test
void requestWithoutTokenIsRejected() throws Exception {
var response = CLIENT.send(request("/api/v1/requests").build(), HttpResponse.BodyHandlers.discarding());
assertEquals(401, response.statusCode());
assertTrue(response.headers().firstValue("WWW-Authenticate").orElse("").startsWith("Bearer"));
}
@Test
void malformedBearerTokenIsRejected() throws Exception {
var response = CLIENT.send(request("/api/v1/requests")
.header("Authorization", "Bearer not-a-jwt").build(), HttpResponse.BodyHandlers.discarding());
assertEquals(401, response.statusCode());
}
private static HttpRequest.Builder request(String path) {
return HttpRequest.newBuilder(baseUri.resolve(path))
.timeout(Duration.ofSeconds(15))
.header("Accept", "application/json").GET();
}
}

View File

@ -1,6 +1,11 @@
# Pruebas
JUnit 5: clases *Test con Surefire durante test; clases *IT con Failsafe durante verify.
T01 incorpora el smoke test de arranque/health al disponer de Java 21 y PostgreSQL.
T02 incorpora ContractValidationTest: OpenAPI oficial, schemas 2020-12, referencias locales, ejemplos válidos/inválidos, cabeceras/seguridad contractual y límites de estructura. Se ejecuta en cada test/package/verify sin servicios externos. No equivale a probar el motor, autorización real o publicación semántica.
T01 incorpora DeploymentSmokeCheck: tres endpoints de salud UP, rechazo sin token y rechazo de token malformado.
Se ejecuta explícitamente con `./mvnw clean verify -Pdeployment-smoke -Dsmoke.baseUrl=http://host:port`.
El perfil exige una URL y falla ante servicio inaccesible, HTTP inesperado o salud distinta de UP; no omite pruebas.
Sólo efectúa GET sin credenciales y no modifica datos. No demuestra JWT válido ni la implementación de solicitudes.
El build normal/CI no contacta servidores privados: este smoke requiere un despliegue ya iniciado.
T03 incorpora Testcontainers PostgreSQL; nunca sustituir concurrencia o JSONB por H2.
No omitir integraciones silenciosamente cuando Docker no esté disponible.