orquestador/Specs_Orquestador_SpringBoo...

37 KiB
Raw Permalink Blame History

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:

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

{
  "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:

{
  "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.

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:

POST /api/v1/requests/00000000-0000-0000-0000-000000000123/events
Authorization: Bearer <token>
Idempotency-Key: asignar-cliente-001
If-Match: "7"
Content-Type: application/json

{"event":"ASIGNAR_CLIENTE","data":{"clienteId":"CLI-456"}}
{
  "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:

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

./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.