orquestador/Specs_Orquestador_SpringBoo...

525 lines
37 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <token>
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.