# Especificación de implementación: orquestador configurable de solicitudes Versión del documento: 1.0 · Fecha: 25 de octubre de 2026 Estado: especificación propuesta para construcción; no constituye una implementación. ## 1. Objetivo y alcance Construir un microservicio Spring Boot que ejecute múltiples máquinas de estados de autorización. El tipo de solicitud determina el flujo y la versión asignados al crear una instancia. Estados, eventos, tareas, condiciones, mapeos y transiciones se almacenan en PostgreSQL y se modifican publicando configuraciones, sin recompilar cuando las capacidades necesarias ya existan. Caso obligatorio: solicitud creada → asignar cliente consultando una API → capturar INE → capturar estado de cuenta → consultar buró → decidir ruta por scoring. Una nueva versión incorpora captura y validación de licencia antes del buró, sin modificar Java ni crear columnas para licencia. El motor no debe contener condiciones específicas de crédito, INE o licencia en su lógica central. Sí debe implementar capacidades genéricas: validar entrada, registrar documento, invocar una integración, evaluar decisiones y persistir resultados. Incluye backend, worker durable basado en PostgreSQL, contratos para front configurable, administración REST de flujos, auditoría y ejemplos. Excluye interfaz gráfica administrativa, OCR propio, conexión real al buró sin contrato y credenciales, almacenamiento binario de documentos, BPMN, tareas paralelas, compensaciones automáticas y migración automática de instancias. No incluir Drools inicialmente; ofrecer un puerto de decisiones para integrarlo posteriormente. ## 2. Decisiones tecnológicas - Java 21; Spring Boot en una versión estable mantenida y compatible con Java 21. En T01 fijar versión exacta y BOM, verificar compatibilidad de dependencias y documentarla. No usar versiones dinámicas, snapshots ni inventar el parche más reciente. - Maven Wrapper. Spring MVC, Bean Validation, Security OAuth2 Resource Server JWT, Actuator y cliente HTTP de Spring compatible con la versión seleccionada. - PostgreSQL 17 como versión mayor objetivo; fijar parche/imagen reproducible al implementar. Flyway para esquema; Hibernate ddl-auto=validate. - Spring Data JPA para agregados y JDBC para reclamación de trabajos con SKIP LOCKED. No mantener entidades JPA abiertas durante llamadas HTTP. - JSONB para definiciones y datos extensibles; columnas relacionales para identidad, versiones, estado, permisos de acceso e índices operativos. BigDecimal para comparaciones numéricas. - JUnit 5, Testcontainers PostgreSQL, WireMock o equivalente compatible, y pruebas de arquitectura si aportan valor. - OpenAPI 3.1 en archivo contractual; validar herramienta de documentación contra la versión de Boot. - No requiere Kafka ni Redis para el MVP. La tabla execution es una cola durable; una futura publicación a Kafka exige outbox transaccional. ## 3. Arquitectura y límites Paquete raíz sugerido: com.organizacion.orchestrator. Un módulo Maven, organizado por capacidades y capas internas. | Paquete | Responsabilidad | |---|---| | flow | Borradores, validación, publicación, activación y caché | | request | Instancias, autorización por recurso y consulta de tareas | | execution | Recepción de comandos, idempotencia, worker y recuperación | | decision | AST tipado, operadores y tablas de decisión | | action | ActionHandler, registro de handlers y mapeos | | integration | Clientes HTTP, catálogo permitido, normalización de resultados | | document | Referencias y verificación con servicio documental | | audit | Historial inmutable y consultas paginadas | | shared | Errores, identidad, reloj, identificadores y observabilidad | Puertos mínimos: ```java interface DecisionEvaluator { DecisionResult evaluate(DecisionDefinition definition, EvaluationContext context); } interface ActionHandler { String type(); ActionResult execute(ActionDefinition definition, ActionContext context); } interface FlowDefinitionValidator { ValidationReport validate(FlowDefinition definition); } ``` Usar records/objetos tipados para contratos, JSON Schema para entrada configurable y AST de condiciones. No ejecutar Java, SQL, JavaScript, SpEL irrestricto ni scripts suministrados en definiciones. Un campo de configuración no puede seleccionar clases Java. ## 4. Vocabulario y semántica - Estado de negocio: avance de la solicitud, por ejemplo INE_PENDIENTE. - Evento: intención recibida, por ejemplo REGISTRAR_INE. El backend obtiene estado e identidad, nunca los acepta como autoridad del front. - Transición: estado origen + evento + guardia previa + acciones ordenadas + selección de resultado + estado destino. - Guardia: condición previa sin efectos externos. Si no cumple, rechazar el evento antes de aceptarlo. - Acción: capacidad implementada con entrada/salida tipada y política de reintentos. - Decisión: evaluación pura; no invoca servicios ni modifica persistencia. - Tarea: metadatos para que el front muestre formulario/captura. No es otro motor independiente. - Resultado de negocio: CLIENTE_NO_ENCONTRADO, DOCUMENTO_INVALIDO, etc. Puede mantener el mismo estado con respuesta explícita. - Fallo técnico: timeout, autenticación con proveedor, respuesta inválida. No equivale a cliente inexistente ni a scoring bajo. - Estado de ejecución: separado del negocio; PENDING, RUNNING, RETRY_WAIT, SUCCEEDED, FAILED, RECONCILIATION_REQUIRED. MVP: una transición por evento aceptado; una sola ejecución activa por solicitud. Un evento puede encadenar varias acciones en orden, pero no genera transiciones automáticas recursivas. SUCCEEDED significa procesado técnicamente; puede incluir resultado de negocio no favorable. ## 5. Configuración de flujos Definición JSON completa e inmutable al publicar. Claves de estados y eventos son strings; nunca enums Java por flujo. Cada versión contiene estados, eventos, transiciones, decisiones y tareas. schemaVersion versiona el formato del DSL independientemente de la versión del proceso. Ejemplo coherente del núcleo de un flujo mínimo de scoring (válido funcionalmente; schemas referenciados se incluyen en el paquete de configuración): ```json { "schemaVersion": 1, "flowKey": "CREDITO", "version": 1, "initialState": "BURO_PENDIENTE", "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", "inputs": {"clienteId": {"path": "/request/data/clienteId"}}, "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": [] } ``` El esquema de salida de BURO_CONSULTA exige score numérico válido; score ausente no llega al default como si fuera bajo. Sus límites válidos deben proceder del contrato del proveedor, no asumir universalmente 0–100. Los umbrales 60/70 son ejemplos del negocio. Tarea documental de la versión 2, como fragmento para insertar en el flujo completo: ```json { "state": "LICENCIA_PENDIENTE", "event": "REGISTRAR_LICENCIA", "ui": { "component": "DOCUMENT_UPLOAD", "title": "Sube tu licencia de conducir", "documentType": "LICENCIA_CONDUCIR", "acceptedMimeTypes": ["application/pdf", "image/jpeg", "image/png"], "maxBytes": 10485760 }, "inputSchema": { "type": "object", "required": ["documentId"], "additionalProperties": false, "properties": {"documentId": {"type": "string", "format": "uuid"}} } } ``` El backend debe aplicar las mismas restricciones; ui es presentación. El evento referencia documentId, no URL ni resultado de validación. Una transición usa REGISTER_DOCUMENT, después INVOKE_INTEGRATION con perfil LICENCIA_CONDUCIR y normaliza VALID/INVALID. INVALID permanece en LICENCIA_PENDIENTE con motivo; VALID pasa a BURO_PENDIENTE. Error técnico no produce INVALID. ## 6. Lenguaje de condiciones y decisiones Operadores MVP: EQ, NEQ, GT, GTE, LT, LTE, IN, EXISTS, AND, OR, NOT. Referencias mediante JSON Pointer, sin evaluación de expresiones libres. Contexto de solo lectura: request.data, event.data, results y actor con atributos confiables. - No convertir implícitamente strings a números ni fechas. Comparaciones numéricas con BigDecimal. - EXISTS retorna false ante ruta ausente; otros operadores sobre ausente o tipos incompatibles producen EVALUATION_ERROR. Distinguir null de ausente; null solo comparable explícitamente con EQ/NEQ. - AND/OR cortocircuitan en orden; NOT tiene un operando. IN exige lista homogénea. - UNIQUE: cero coincidencias usa default obligatorio; más de una es AMBIGUOUS_DECISION y no cambia negocio. - FIRST: solo habilitable explícitamente; orden persistido define precedencia y queda auditado. - Validación estática detecta solapamientos de intervalos simples. No prometer resolver satisfacibilidad arbitraria; conservar verificación de ambigüedad en runtime. - Límites: definición 1 MiB, 200 estados, 500 transiciones, 50 acciones por transición, profundidad AST 20, 1.000 nodos por decisión, datos de solicitud 256 KiB. Configurables con máximos operativos. - Updates solo apuntan dentro de request.data; rechazar claves duplicadas, destinos solapados y cambios a identidad, versión, estado o ACL. Validar esquema de datos resultante antes del commit. ## 7. Persistencia PostgreSQL UUID generado por aplicación; timestamps timestamptz en UTC; IDs ajenos con tipos explícitos. El SQL siguiente establece el núcleo. Implementar como migraciones Flyway e incluir CHECKs, FKs e índices detallados; no usarlo como sustituto de las validaciones de publicación. ```sql CREATE SCHEMA orchestrator; CREATE TABLE orchestrator.flow_definition ( id uuid PRIMARY KEY, flow_key varchar(100) NOT NULL, version integer NOT NULL CHECK (version > 0), status varchar(20) NOT NULL CHECK (status IN ('DRAFT','PUBLISHED','RETIRED')), schema_version integer NOT NULL, definition jsonb NOT NULL CHECK (jsonb_typeof(definition) = 'object'), checksum varchar(64), revision bigint NOT NULL DEFAULT 0, created_at timestamptz NOT NULL DEFAULT now(), created_by varchar(200) NOT NULL, published_at timestamptz, UNIQUE (flow_key, version) ); CREATE TABLE orchestrator.request_type ( type_key varchar(100) PRIMARY KEY, active_flow_id uuid REFERENCES orchestrator.flow_definition(id), enabled boolean NOT NULL DEFAULT true, revision bigint NOT NULL DEFAULT 0 ); CREATE TABLE orchestrator.request_instance ( id uuid PRIMARY KEY, type_key varchar(100) NOT NULL REFERENCES orchestrator.request_type(type_key), flow_id uuid NOT NULL REFERENCES orchestrator.flow_definition(id), current_state varchar(100) NOT NULL, data jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(data) = 'object'), owner_subject varchar(200) NOT NULL, business_scope varchar(100) NOT NULL, revision bigint NOT NULL DEFAULT 0, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE orchestrator.execution ( id uuid PRIMARY KEY, request_id uuid NOT NULL REFERENCES orchestrator.request_instance(id), idempotency_key varchar(200) NOT NULL, payload_hash varchar(64) NOT NULL, actor_subject varchar(200) NOT NULL, actor_snapshot jsonb NOT NULL, event_key varchar(100) NOT NULL, event_data jsonb NOT NULL, transition_id varchar(100) NOT NULL, from_state varchar(100) NOT NULL, accepted_revision bigint NOT NULL, status varchar(40) NOT NULL CHECK (status IN ('PENDING','RUNNING','RETRY_WAIT','SUCCEEDED','FAILED','RECONCILIATION_REQUIRED')), attempts integer NOT NULL DEFAULT 0, next_attempt_at timestamptz NOT NULL DEFAULT now(), lease_owner varchar(200), lease_until timestamptz, fencing_token bigint NOT NULL DEFAULT 0, result jsonb, error_code varchar(100), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (request_id, idempotency_key) ); CREATE UNIQUE INDEX uq_request_active_execution ON orchestrator.execution(request_id) WHERE status IN ('PENDING','RUNNING','RETRY_WAIT','RECONCILIATION_REQUIRED'); CREATE INDEX ix_execution_due ON orchestrator.execution(next_attempt_at, created_at) WHERE status IN ('PENDING','RETRY_WAIT'); CREATE INDEX ix_execution_lease ON orchestrator.execution(lease_until) WHERE status = 'RUNNING'; CREATE INDEX ix_request_scope_state ON orchestrator.request_instance(business_scope, current_state, created_at, id); CREATE TABLE orchestrator.action_execution ( id uuid PRIMARY KEY, execution_id uuid NOT NULL REFERENCES orchestrator.execution(id), action_id varchar(100) NOT NULL, status varchar(30) NOT NULL CHECK (status IN ('PENDING','RUNNING','SUCCEEDED','FAILED','UNKNOWN')), attempt_count integer NOT NULL DEFAULT 0, external_idempotency_key varchar(200) NOT NULL, input_hash varchar(64) NOT NULL, result jsonb, provider_reference varchar(200), error_code varchar(100), started_at timestamptz, finished_at timestamptz, UNIQUE (execution_id, action_id) ); CREATE TABLE orchestrator.document_reference ( id uuid PRIMARY KEY, request_id uuid NOT NULL REFERENCES orchestrator.request_instance(id), document_type varchar(100) NOT NULL, storage_document_id varchar(200) NOT NULL, mime_type varchar(100) NOT NULL, size_bytes bigint NOT NULL CHECK (size_bytes >= 0), checksum varchar(128) NOT NULL, created_by varchar(200) NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (request_id, document_type, storage_document_id) ); CREATE TABLE orchestrator.audit_entry ( id uuid PRIMARY KEY, request_id uuid NOT NULL REFERENCES orchestrator.request_instance(id), execution_id uuid REFERENCES orchestrator.execution(id), kind varchar(100) NOT NULL, actor_subject varchar(200) NOT NULL, flow_id uuid NOT NULL REFERENCES orchestrator.flow_definition(id), from_state varchar(100), to_state varchar(100), detail jsonb NOT NULL, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX ix_audit_request ON orchestrator.audit_entry(request_id, created_at, id); ``` Añadir tablas admin_audit para publicación/activación (flow_id, acción, actor, checksums, instante, correlationId) y reconciliation_resolution para resoluciones autorizadas e idempotentes. La creación de solicitudes requiere registro durable de idempotencia propio con clave única por actor/scope y hash; crear instancia y registro en la misma transacción. No duplicar la versión de flujo en la instancia: flow_id referencia la fila inmutable. No almacenar archivos, tokens, JWT completos ni secretos en JSONB. Integration registry es configuración de despliegue con secretos por referencia; los flujos solo usan integrationKey y perfil permitido. Un trigger debe impedir UPDATE de definition, flow_key, version, schema_version y checksum después de publicar; permitir únicamente transición controlada PUBLISHED→RETIRED. Prohibir DELETE de versiones publicadas y escritura ordinaria al historial; migraciones usan un rol separado. RETIRED impide nuevas asignaciones y permite ejecutar instancias existentes. Sin borrado físico de versiones referenciadas. ## 8. Protocolo de ejecución durable ### 8.1 Aceptación del evento: transacción corta 1. Autenticar, verificar acceso a la solicitud y validar tamaño/formato. 2. Bloquear la fila de solicitud con SELECT FOR UPDATE. 3. Buscar Idempotency-Key: mismo hash y actor autorizado retorna ejecución existente, aun si cambió revision; mismo key con diferente comando retorna 409 IDEMPOTENCY_CONFLICT. El hash incluye evento, datos y revisión esperada con serialización canónica. La clave se conserva durante la retención de la solicitud. 4. Comprobar If-Match contra revision, ausencia de ejecución activa, estado/evento, rol y guardias con el snapshot actual. Nunca aceptar scoring ni validaciones de proveedor desde el front. 5. Insertar execution PENDING con transición, identidad mínima confiable y versión fijada por instancia; incrementar revision de solicitud y registrar EVENT_ACCEPTED en la misma transacción. 6. Confirmar y retornar 202 con Location a ejecución. No llamar APIs dentro de esta transacción. ### 8.2 Worker y acciones 1. Reclamar un lote pequeño con SELECT FOR UPDATE SKIP LOCKED, orden next_attempt_at/created_at, para PENDING o RETRY_WAIT vencidos; actualizar RUNNING, lease, contador y fencing_token. Confirmar antes de ejecutar HTTP. 2. Lease inicial 60 s, renovación cada 15 s; timeouts HTTP inferiores a lease y presupuesto total por acción definido. Usar tiempo de PostgreSQL para vencimientos. Valores configurables. 3. Ejecutar acciones secuenciales con checkpoint por acción. Una acción SUCCEEDED se reutiliza, no se repite. Guardar y verificar input_hash. 4. Clave externa estable derivada de executionId + actionId, idéntica en reintentos. Los intentos no modifican esa clave. 5. Toda escritura del worker verifica lease_owner y fencing_token vigente. Un worker obsoleto no puede finalizar; fencing local no evita duplicados externos, de ahí la idempotencia del proveedor. 6. Evaluar decisión sobre snapshot + resultados normalizados, producir actualizaciones candidatas y validar schema. 7. Transacción final: bloquear solicitud y comprobar ejecución activa, lease/token y estado/revisión esperados; actualizar datos y estado, incrementar revision, marcar SUCCEEDED y registrar acciones/decisión/transición de forma atómica. Mientras hay ejecución activa, rechazar otros eventos con 409 REQUEST_BUSY. No permitir PATCH libre de datos. Un evento nuevo tras un resultado inválido utiliza nueva clave; repetición de la misma clave consulta el mismo resultado. ### 8.3 Reintentos y recuperación - Reintentar solo fallos transitorios según contrato: timeout de lectura segura, conexión, 429 y ciertos 5xx. Máximo inicial 3 intentos totales por acción, backoff 2 s y 4 s con jitter y Retry-After acotado. Validar que la configuración no permita ciclos infinitos. - No reintentar ciegamente operaciones con efectos sin idempotencia/consulta de estado. Si hubo timeout o caída después del envío y antes de guardar respuesta, resultado UNKNOWN → RECONCILIATION_REQUIRED. - Un recuperador detecta leases vencidos: retoma consultas/idempotentes; bloquea para conciliación operaciones no seguras en curso. Conserva checkpoints previos. - FAILED solo libera la solicitud cuando se conoce que no hay efectos pendientes; conservar estado/datos de negocio previos e historial del fallo. Si existen efectos exitosos no compensados que impiden repetir el comando, usar RECONCILIATION_REQUIRED. - Endpoint de conciliación reservado: adjuntar evidencia de resultado verificado y reanudar, o cerrar sin transición únicamente cuando la ausencia/compensación de efectos esté verificada. No admitir cambiar manualmente a cualquier estado. - No prometer exactly-once entre base de datos y APIs. Semántica: entrega al menos una vez con idempotencia y conciliación de incertidumbre. ## 9. Catálogo de acciones e integraciones Handlers MVP: REGISTER_DOCUMENT, INVOKE_INTEGRATION y NO_OP (solo simulación). Cada tipo declara schema de configuración/entrada/salida y clasificación READ_ONLY, IDEMPOTENT_WRITE o NON_IDEMPOTENT_WRITE. La clasificación procede del catálogo confiable, nunca de un flujo editable. INVOKE_INTEGRATION resuelve integrationKey a endpoint, método, autenticación, timeouts y contrato permitidos. La publicación fija contractVersion/profileVersion; no cambiar semántica de una integración bajo una versión publicada. Rotación de secretos o DNS operativo puede ocurrir sin alterar el contrato. No aceptar URL, headers arbitrarios o credenciales del evento. Permitir mappings explícitos de campos, no plantillas ejecutables; bloquear cambios de host y redirecciones inesperadas. Registrar solo metadatos no sensibles. Adapters de ejemplo con mocks: | Integración | Entrada | Salida normalizada | |---|---|---| | CLIENTES_CONSULTA | clienteId | found boolean; customerId cuando found=true | | DOCUMENTO_VALIDA | documentId, perfil | status VALID/INVALID, reasonCodes; datos mínimos derivados | | BURO_CONSULTA | clienteId y referencia de consentimiento | score numérico y consultationId | 404 significa no encontrado únicamente si así lo define el contrato. Respuesta 200 sin campos obligatorios es fallo de contrato. Buró requiere consentimiento/requisitos del proveedor; esta especificación no define criterios regulatorios. REGISTER_DOCUMENT consulta el servicio documental y comprueba pertenencia a la solicitud/scope, tipo, MIME real, tamaño, checksum y estado de análisis antimalware. El front no puede vincular un documento ajeno. Sin servicio real, implementar stub explícito solo en perfil local/test. No ofrecer un éxito simulado en producción. ## 10. Contratos REST Base /api/v1, JSON, JWT salvo salud mínima. Fechas ISO-8601 UTC. Errores application/problem+json con code y correlationId. Paginación por cursor, máximo 100. | Método y ruta | Función / respuesta | |---|---| | POST /requests | Crear, resolver flujo activo y fijar versión; 201, Location y ETag | | GET /requests/{id} | Estado, versión de flujo, datos autorizados, revision, ejecución activa; 200 y ETag | | GET /requests/{id}/available-events | Eventos permitidos, inputSchema, ui y causas de bloqueo no sensibles | | POST /requests/{id}/events | Aceptar comando; 202 y Location de ejecución | | GET /executions/{id} | Estado técnico, resultado de negocio y error permitido | | GET /requests/{id}/history | Auditoría paginada y filtrada | | POST /admin/flows | Crear borrador con clave y versión únicas | | PUT /admin/flows/{id} | Editar borrador con If-Match | | POST /admin/flows/{id}/validate | Validación y errores por JSON Pointer | | POST /admin/flows/{id}/simulate | Evaluación pura con resultados de acciones suministrados por admin; sin llamadas reales | | POST /admin/flows/{id}/publish | Publicar si valida; checksum y auditoría | | PUT /admin/request-types/{key}/active-flow | Activación atómica con If-Match del tipo | | POST /admin/flows/{id}/retire | Impedir nuevas asignaciones; conservar existentes | | POST /admin/executions/{id}/resolve | Conciliación verificada e idempotente | POST /requests usa Idempotency-Key obligatorio, typeKey y datos iniciales validados; owner/scope se derivan o se autorizan explícitamente desde JWT, no se confían sin verificación. Ejemplo de comando: ```http POST /api/v1/requests/00000000-0000-0000-0000-000000000123/events Authorization: Bearer Idempotency-Key: asignar-cliente-001 If-Match: "7" Content-Type: application/json {"event":"ASIGNAR_CLIENTE","data":{"clienteId":"CLI-456"}} ``` ```json { "executionId": "00000000-0000-0000-0000-000000000789", "status": "PENDING", "requestId": "00000000-0000-0000-0000-000000000123" } ``` El front consulta ejecución con backoff; al finalizar vuelve a obtener solicitud y ETag. available-events es orientativo: el POST revalida. Si hay ejecución activa devolver lista vacía con blockedByExecutionId. El contrato de tareas permite al front renderizar DOCUMENT_UPLOAD y FORM; no incluye implementar dicho front. Códigos: 400 sintaxis; 401 sin autenticación; 403 acceso denegado; 404 recurso no visible/inexistente según política; 409 REQUEST_BUSY/INVALID_EVENT/IDEMPOTENCY_CONFLICT; 412 revisión incorrecta; 413 tamaño; 422 schema/guardia no satisfecha; 428 falta If-Match; 503 fallo al aceptar por infraestructura. Un fallo asíncrono se consulta en ejecución, no se transforma en éxito HTTP de negocio. ## 11. Publicación y versionado Validar: schemaVersion soportada, IDs únicos, estado inicial existente, destinos válidos, terminales sin eventos salientes, alcanzabilidad, estados sin salida no terminales, handlers/integraciones conocidos, mappings y schemas, roles válidos, todas las salidas de decisión cubiertas y límites. Cada pareja estado/evento identifica una transición; condiciones de selección van en decisión/guardia. Rechazar asignación de campos protegidos y perfiles inexistentes. Las capacidades de integración y sus schemas se resuelven en publicación; ausencia temporal de proveedor no invalida una definición previamente publicada, pero impide ejecutar correctamente la acción. DRAFT→PUBLISHED→RETIRED. Edición de borrador con locking optimista. Publicación serializada e inmutable, checksum SHA-256 del JSON canónico. Activación y retiro bloquean las filas pertinentes para impedir una nueva asignación a un flujo retirado. Creación bloquea/lee consistentemente request_type y flujo activo. Caché solo por flow_id/checksum; resolución de activo siempre coherente con PostgreSQL. No migrar solicitudes automáticamente. Para MVP: nuevas solicitudes usan v2, existentes conservan v1. Futuro mecanismo de migración requerirá mapa de estados, validación de datos, ausencia de ejecución activa, simulación, autorización y auditoría. ## 12. Seguridad y operación Roles de ejemplo: FLOW_EDITOR, FLOW_PUBLISHER, FLOW_ADMIN, ANALISTA y EXECUTION_OPERATOR; permisos técnicos distintos de roles configurables de negocio. JWT validado por issuer/audience/firma/vencimiento. Verificar scope/propiedad en cada recurso, incluyendo ejecución, documentos e historial. No derivar roles del JSON del evento. La autorización se registra al aceptar el comando; reintentos técnicos no necesitan reutilizar el JWT vencido. Política de revocación posterior queda explícita como mejora futura. Registrar correlationId, executionId, requestId, actionId, flujo/versión, tiempos, reintentos y resultado sin documentos, números de identificación ni score crudo en logs. Auditoría guarda IDs de reglas aplicadas y razones con acceso restringido; retención configurable y eliminación conforme a políticas de datos de la organización. Métricas: comandos aceptados/rechazados, antigüedad del pendiente más viejo, ejecuciones por estado, latencia por integrationKey, leases vencidos y conciliaciones. Evitar IDs de solicitud como etiquetas de métricas. Readiness depende de PostgreSQL; liveness no consulta proveedores. Apagado ordenado deja de reclamar trabajos y termina/renueva los vigentes dentro del presupuesto. Defaults operativos propuestos, a medir: 10 workers por instancia, lotes 10, polling 500 ms, timeout conexión 2 s y respuesta 10 s; no son SLO garantizados. Límite de body y datos aplicable antes de deserializar grandes cargas. TLS, secretos externos y roles DB de mínimo privilegio. Desarrollo incluye Docker Compose PostgreSQL y mocks; producción sin credenciales predeterminadas ni endpoints admin abiertos. ## 13. Casos de aceptación obligatorios | ID | Escenario | Evidencia esperada | |---|---|---| | A01 | Cliente encontrado | Acción consulta; persiste cliente y cambia CREADA→INE_PENDIENTE | | A02 | Cliente no encontrado | SUCCEEDED con outcome CLIENTE_NO_ENCONTRADO; conserva CREADA | | A03 | API cliente falla | Reintentos limitados; nunca se interpreta como inexistente | | A04 | Scoring 71/70/61/60 | N/M/M/REVISION_MANUAL respectivamente | | A05 | Score ausente/string | Fallo de contrato; no ruta por defecto de score bajo | | A06 | Dos reglas UNIQUE coinciden | AMBIGUOUS_DECISION; no transición | | A07 | Mismo evento/clave repetido | Mismo executionId y un único efecto lógico | | A08 | Misma clave, otros datos | 409 IDEMPOTENCY_CONFLICT | | A09 | Dos eventos simultáneos | Solo uno aceptado; otro 409 o 412 según orden | | A10 | Publicar v2 con licencia | Nuevas instancias exigen licencia; v1 conserva recorrido | | A11 | Documento inválido/ajeno | Inválido mantiene tarea con motivo; ajeno se rechaza sin vincular | | A12 | Reinicio durante ejecución | Recuperación desde checkpoint sin perder evento | | A13 | Caída tras efecto externo | Idempotencia evita duplicado o exige conciliación | | A14 | Dos workers / lease vencido | Worker obsoleto no confirma; un solo cambio final | | A15 | Rol/scope ajeno | No obtiene ni modifica recursos ajenos | | A16 | Flujo publicado editado | API y DB bloquean cambio de definición | | A17 | Activación/retirada concurrentes | Ninguna nueva solicitud referencia flujo retirado | | A18 | Agregar licencia | Solo nueva configuración y perfil soportado; sin cambio Java ni columnas | | A19 | Persistencia final falla | No transición parcial; recuperación con checkpoint y auditoría consistente | | A20 | Payload intenta escribir score | Rechazado por schema; resultados de proveedor protegidos | Pruebas reales PostgreSQL, no H2 para concurrencia/JSONB. Usar fault injection y barreras deterministas, no sleeps frágiles. Probar rollback antes/después de cada frontera crítica. No afirmar que pasó una prueba no ejecutada. ## 14. Lista ordenada de implementación Cada tarea comienza pendiente. Las dependencias forman el orden obligatorio de referencia; se permite subdividir, no omitir entregables. | ID | Tarea | Depende de | Criterio de cierre | |---|---|---|---| | T01 | Bootstrap y ADR de versiones | — | Maven Wrapper, Java 21, versiones fijadas, compile y health local | | T02 | Contratos OpenAPI y JSON Schema del DSL | T01 | Estados/eventos/decisiones/tareas definidos; ejemplos validados automáticamente | | T03 | Modelo PostgreSQL y Flyway | T02 | Migraciones desde cero; FKs, índices e inmutabilidad probados con Testcontainers | | T04 | Modelo de dominio y puertos | T02,T03 | Dominio sin clientes HTTP/JPA; DTOs separados y errores estables | | T05 | Evaluador de decisiones | T04 | Tipos, null/ausente, límites, UNIQUE/FIRST; A04–A06 | | T06 | Validador/publicación/activación | T03–T05 | Borradores con revisión, grafo validado, checksum, A16–A17 | | T07 | Seguridad y autorización por recurso | T04 | JWT real validado en pruebas; roles/scope aplicados a rutas admin y negocio | | T08 | Crear/consultar solicitudes | T06,T07 | Fija versión, idempotencia de creación, ETag, esquema de datos y acceso | | T09 | Aceptar eventos idempotentes | T08 | 202 durable, guardias, locking, A07–A09 sin invocar HTTP todavía | | T10 | Worker PostgreSQL y leases | T09 | Reclamación multiinstancia, fencing, checkpoints y recuperación inicial | | T11 | Handlers e integraciones HTTP | T10 | Catálogo permitido, contratos, timeouts y mocks; A01–A03 | | T12 | Reintentos y conciliación | T11 | Fallos inciertos tratados; endpoint protegido; A12–A14,A19 | | T13 | Documentos y tareas para front | T11,T12 | Referencias verificadas, eventos disponibles, A11,A20 | | T14 | Flujos demostrativos v1/v2 | T05,T06,T13 | Cliente/INE/estado cuenta/buró; v2 licencia; A10,A18 | | T15 | Auditoría y observabilidad | T09–T14 | Historial atómico, métricas y logs sin PII, checks operativos | | T16 | Verificación integral | T15 | A01–A20 automatizados; pruebas de reinicio/concurrencia reproducibles | | T17 | Empaquetado y documentación | T16 | Dockerfile, Compose, README, ejemplos HTTP y runbook ejecutables | Auditoría mínima debe implementarse junto a cada escritura desde T06; T15 completa consultas/operación. Seguridad debe preceder exposición de endpoints; no dejar protección para el final. T10 no se considera terminado si solo usa @Async o memoria. ### Entregables del repositorio - pom.xml, mvnw, mvnw.cmd y .mvn con versiones fijas. - src/main/java, src/test/java y src/main/resources/db/migration. - contracts/openapi.yaml y schemas/flow-definition.schema.json. - examples/flows/credito-v1.json, credito-v2-licencia.json y compra-v1.json completos, válidos y publicables. - examples/http con creación/publicación/activación/eventos/consulta y scoring de frontera. - docs/ARCHITECTURE.md, docs/DECISIONS.md, docs/IMPLEMENTATION_PLAN.md, docs/PROGRESS.md y docs/RUNBOOK.md. - Dockerfile, compose.yaml, .env.example sin secretos y CI. No es necesario crear microservicios independientes para cada componente; son módulos internos del orquestador. ## 15. Instrucciones para llevar la secuencia Estas instrucciones deben copiarse a AGENTS.md o al archivo de instrucciones del agente de programación del repositorio. Son parte normativa del encargo de implementación. 1. Leer esta especificación completa, instrucciones existentes y estado del repositorio. No reemplazar trabajo del usuario ni introducir otro stack. 2. Crear docs/IMPLEMENTATION_PLAN.md con T01–T17 y subtareas verificables. Crear docs/PROGRESS.md con columnas ID, estado, dependencias, archivos, comandos/checks, resultado, bloqueo y siguiente paso. Estados: PENDING, IN_PROGRESS, BLOCKED, DONE. 3. Elegir la primera tarea pendiente cuyas dependencias estén DONE. Mantener una única tarea principal IN_PROGRESS. No iniciar interfaz administrativa, Kafka o Drools para resolver requisitos del MVP. 4. Antes de codificar, identificar criterios de cierre y prueba pertinente. Implementar una porción completa; actualizar migraciones, contrato y pruebas cuando el comportamiento lo requiera. 5. Ejecutar verificaciones de la tarea. Al fallar, corregir dentro de esa tarea; no marcar DONE por compilación si requiere concurrencia o integración. Documentar diferencias entre stub y proveedor real. 6. Registrar evidencia concreta: comando, resultado, fecha y archivos. Los bloqueos externos no equivalen a prueba aprobada. Cuando no existan credenciales/contrato de un proveedor, completar adapter y pruebas con mocks, marcar integración real pendiente y no inventar respuestas. 7. Marcar DONE solo al cumplir criterios y no dejar regresiones conocidas. Actualizar siguiente tarea. Continuar autónomamente con tareas autorizadas; preguntar solo por decisiones de negocio que no puedan inferirse, no por cada fase. 8. Al retomar una sesión, leer PROGRESS y revisar cambios actuales. Reanudar tarea IN_PROGRESS/BLOCKED; evitar regenerar todo el proyecto o repetir verificaciones ya vigentes sin motivo. 9. Si hay una desviación necesaria, registrar ADR con motivo, efecto y alternativas; no cambiar la semántica de idempotencia, versiones o seguridad silenciosamente. 10. No hacer despliegue productivo, conectar un buró real ni enviar documentos personales para la demo sin autorización y configuración correspondiente. La entrega solicitada es implementación local verificable. Plantilla inicial de progreso: ```markdown | ID | Estado | Evidencia | Bloqueo | Siguiente acción | |---|---|---|---|---| | T01 | PENDING | — | — | Verificar versiones y crear proyecto | ``` Comandos de referencia, que la implementación debe hacer funcionar y documentar: ```bash ./mvnw clean verify docker compose up -d postgres mocks ./mvnw spring-boot:run -Dspring-boot.run.profiles=local ``` La suite de integración puede iniciar PostgreSQL/mocks mediante Testcontainers; Docker es requisito de esas pruebas. Definir si verify ejecuta integración con Failsafe y mantener ese comportamiento en CI. Usar issuer JWT de prueba realista en local, sin introducir un bypass de seguridad disponible en producción. ## 16. Definición final de terminado Entrega completa cuando un checkout limpio permite levantar el entorno, aplicar migraciones, ejecutar la suite y reproducir: publicación/activación de dos tipos de solicitud; cliente encontrado/no encontrado; captura INE y estado de cuenta; scoring por fronteras; publicación v2 con licencia; convivencia v1/v2; repetición idempotente; dos eventos concurrentes; recuperación tras reinicio y conciliación de un efecto incierto. README debe indicar límites reales: nuevo tipo de tarea requiere soporte del front; nueva capacidad de validación puede requerir adapter; cambios de umbrales/destinos y nuevos pasos con capacidades existentes son configuración. No afirmar que todo cambio de negocio se hace sin código. ## 17. Fuentes técnicas y decisiones propuestas Fuentes oficiales consultadas como referencia; las políticas del motor en este documento son decisiones de diseño propuestas, no garantías de las bibliotecas. - Spring Boot, requisitos de sistema: https://docs.spring.io/spring-boot/system-requirements.html — verificar y fijar release concreta en T01. - PostgreSQL 17, locking explícito: https://www.postgresql.org/docs/17/explicit-locking.html — fundamento para bloqueos de fila; el protocolo de leases/fencing debe implementarse. - PostgreSQL 17, JSON: https://www.postgresql.org/docs/17/datatype-json.html — almacenamiento JSONB; schemas y semántica del DSL los valida la aplicación.