Lila Modeler — Semántica del motor v1
Estado: normativo para v1 (hitos M0–M3). Fecha: 2026-09-03.
Este documento define qué hace exactamente el motor antes de que exista una línea de sim.ts.
Es la fuente de verdad para packages/engine/src/core/ y para los tests de test/semantics.test.ts.
Deriva de LILA_MODELER_ESTRUCTURA.md secciones 3 (paridad Bizagi), 4 (ADR-009…023) y 6 (diseño del
motor); si algo aquí contradijera ese documento, gana el documento y este archivo se corrige.
Cada regla tiene un identificador estable (R-XXX-n) y el ticket que la prueba. El resumen
regla → ticket está en la sección 18. Los códigos de error (E-…) y de aviso (W-…) están en la
sección 17.
Convenciones de lectura: error aborta (validate devuelve errors, la CLI sale con código 1,
simulate no corre); aviso no aborta y viaja en RunResult.warnings[].
1. Reglas duras
Valen en todo el documento y en todo el código.
- R-DURA-1 — Segundos. Todo tiempo del escenario y del
RunResultes un número de segundos (float64), salvorun.start, que es un instante ISO-8601 con offset. Las duraciones de distribuciones,warmup,duration,processingTime,interTriggerTimer, las esperas y los tiempos del event log son segundos. (prueba: LILA-013) - R-DURA-2 —
baseTimeUnites presentación.run.baseTimeUnit(sec|min|hour|day) no cambia ningún cálculo: solo la forma en que la CLI y la UI imprimen números (format.ts). Dos escenarios idénticos salvobaseTimeUnitproducen el mismoRunResultnumérico. (prueba: LILA-013) - R-DURA-3 — Dinero. Todos los costos están expresados en
run.currency. El motor no convierte divisas ni conoce tipos de cambio;currencyes una etiqueta que viaja al resultado y al CSV. (prueba: LILA-036) - R-DURA-4 — El
idBPMN es la única clave. Las claves descenario.elementsson atributosidde BPMN (nodos o sequence flows). El nombre nunca es clave y nunca desambigua: dos elementos pueden llamarse igual. Los ids se conservan en todo el pipeline; los ids ajenos que no son NCName se sanitizan con un mapa reversible (ADR-012) y el escenario se escribe contra el id sanitizado. (prueba: LILA-013, LILA-017) - R-DURA-5 —
elementsfaltante es error, sobrante es aviso. Una clave deelementsque no existe en el IR produce errorE-ELEMENTO-DESCONOCIDOcitando el id. Un elemento del IR sin entrada enelementstoma los defaults degradantes (sección 14) y produce aviso solo cuando la ausencia cambia la semántica (start sininterTriggerTimer). En un archivo con varios procesos se simula el que contiene la mayoría de las claves deelementsde los escenarios del proceso (su unión: la app,run,compare,validatey las exportaciones de un.lilacuentan todos los escenarios del proceso; una corrida de un.bpmn, los escenarios dados), esté donde esté su pool; sin claves, o con empate, el primer proceso ejecutable y no vacío, y si no, el primero no vacío. Una clave de otro proceso sigue siendoE-ELEMENTO-DESCONOCIDO, y su mensaje nombra ese proceso y el simulado (#546). (prueba: LILA-013, LILA-042, #546) - R-DURA-6 — Pureza.
simulate(ir, scenario, opts)es una función pura: mismos argumentos, mismo resultado. No lee reloj, ni disco, ni red, niMath.random, ni variables globales. (prueba: LILA-029, LILA-032)
2. Perfil BPMN soportado en v1
Es la lista de la sección 3 del documento de estructura. Todo lo que aparece aquí tiene semántica definida; todo lo demás cae en la sección 3 de este documento.
| Construcción BPMN | Tipo en el IR | Semántica |
|---|---|---|
bpmn:startEvent sin disparador (none) |
start |
generador de casos (sección 10) |
bpmn:startEvent con timerEventDefinition |
start |
generador de casos, idéntico al none |
bpmn:endEvent sin disparador (none) |
end |
consume el token (§9) |
bpmn:endEvent con terminateEventDefinition |
terminate |
mata todos los tokens del caso (§9) |
bpmn:intermediateCatchEvent con timerEventDefinition |
timer |
retardo sin recurso (§9) |
bpmn:boundaryEvent interruptor, con un único timerEventDefinition, adjunto a una tarea y con flujo de salida |
timer con attachedTo |
plazo que corta la tarea (§9) |
bpmn:boundaryEvent no interruptor (cancelActivity="false" o "0"), con un único timerEventDefinition, adjunto a una tarea y con flujo de salida |
timer con attachedTo e interrupting: false |
plazo que crea un token en paralelo y deja la tarea corriendo (§9) |
bpmn:eventBasedGateway exclusivo y que no instancia, con cada flujo de salida hacia un evento de rama |
eventGateway |
corre sus ramas; el primer retardo que vence se lleva el token (§9.1) |
bpmn:intermediateCatchEvent con timerEventDefinition o messageEventDefinition, como rama de ese gateway |
timer |
espera con el tiempo propio de la rama, sin recurso (§9.1) |
bpmn:task y todas sus variantes (userTask, serviceTask, sendTask, receiveTask, manualTask, scriptTask, businessRuleTask) |
task |
trabajo con duración y recursos (§11) |
bpmn:callActivity |
task |
tarea con tiempo global (§4) |
bpmn:subProcess embebido (triggeredByEvent="false", sin marcadores) |
— | aplanado (§4) |
bpmn:exclusiveGateway |
xor |
divergente: §6; convergente: mezcla pass-through |
bpmn:inclusiveGateway |
or |
§7 |
bpmn:parallelGateway |
and |
§8 |
bpmn:sequenceFlow (con isDefault cuando el gateway lo declara) |
flow |
arista; lleva probability |
bpmn:laneSet / bpmn:lane |
lane en el nodo |
solo etiqueta; sin efecto en la simulación |
bpmn:participant (pools) |
— | se simula un solo proceso (R-DURA-5); los procesos de los demás pools se leen y conservan, no se simulan |
bpmn:documentation, bpmn:textAnnotation, bpmn:association, bpmn:group, bpmn:dataObject*, bpmn:dataStore*, bpmn:*DI |
— | se leen y se preservan, no afectan a la simulación |
- R-PERF-1 — Toda variante de tarea es
task. El tipo concreto de tarea no cambia nada del motor: solo suprocessingTime, susresourcesy sufixedCost. (prueba: LILA-018) - R-PERF-2 — Un gateway convergente de tipo
xor(una sola salida, varias entradas) es una mezcla sin espera: cada token que llega sale inmediatamente por la única salida, sin sincronizar y sin consumir tiempo. (prueba: LILA-026) - R-PERF-3 —
bpmn:messageFlowno transporta tokens. Los flujos de mensaje entre pools se ignoran y producen avisoW-MSGFLOWuna vez por archivo, citando cuántos se ignoraron. Se simula un solo proceso (R-DURA-5), así que un token no “salta” de pool. (prueba: LILA-021, LILA-163) - R-PERF-4 —
conditionExpressionse ignora. Las condiciones de los sequence flows no se evalúan en v1: el ramaje es probabilístico, y lasconditionsdel escenario (§6.1) rutean por los flujos que el caso ya recorrió, no por sus datos. Un flujo conconditionExpressionproduce avisoW-CONDcitando el id del flujo. (prueba: LILA-021, LILA-163) - R-PERF-5 — Varios start events son válidos. Cada
startconinterTriggerTimerotriggerCountgenera su propio flujo de llegadas, con su propiotriggerCounty su propio stream de números aleatorios. Unstartsin ninguno de los dos no genera nada y produce avisoW-START-SIN-LLEGADAS. (prueba: LILA-026, LILA-186)
3. Elementos no soportados y texto exacto del error
ADR-021: todo lo que no está en la sección 2 produce error de validación explícito, nunca un fallo silencioso. El texto sigue el estilo de Bizagi (“no soportado por el simulador”).
R-NOSOP-1 — Plantilla exacta del mensaje. Desde LILA-211 el motor habla dos idiomas: el inglés es el idioma por defecto y el español una traducción. Los dos textos son normativos, cada uno para su idioma. Sin nombre, en
eny enes:{id} ({qname}): {construction} not supported by the simulator.{id} ({qname}): {construcción} no soportado por el simulador.Con nombre (
nameno vacío), eneny enes:{id} ({qname}, "{name}"): {construction} not supported by the simulator.{id} ({qname}, "{name}"): {construcción} no soportado por el simulador.{qname}es el nombre calificado BPMN (bpmn:boundaryEvent).{construcción}es exactamente el texto de la tabla siguiente, en la columna del idioma. El código del error esE-NOSOP. (prueba: LILA-021, LILA-163, LILA-211)R-NOSOP-2 — Catálogo cerrado de
{construcción}. Ningún otro texto es válido. Elides el que viaja como dato enParseResult.unsupported[].construction(LILA-211); el texto que se enseña sale del catálogo del idioma:
| Construcción detectada | id |
{construction} (en) |
{construcción} (es) |
|---|---|---|---|
bpmn:boundaryEvent que no sea un temporizador con una salida adjunto a una tarea, interrumpa o no: de mensaje/error/señal/…, sobre un subproceso o sin flujo de salida |
boundaryEvent |
event attached to an activity (boundary event) |
evento adjunto a actividad (boundary event) |
messageEventDefinition en cualquier evento que no sea un evento de rama de un event-based gateway soportado (§9.1) |
messageEvent |
message event |
evento de mensaje |
signalEventDefinition |
signalEvent |
signal event |
evento de señal |
linkEventDefinition |
linkEvent |
link event |
evento de enlace |
errorEventDefinition |
errorEvent |
error event |
evento de error |
escalationEventDefinition |
escalationEvent |
escalation event |
evento de escalamiento |
compensateEventDefinition |
compensationEvent |
compensation event |
evento de compensación |
conditionalEventDefinition |
conditionalEvent |
conditional event |
evento condicional |
cancelEventDefinition |
cancelEvent |
cancel event |
evento de cancelación |
multipleEventDefinition / parallelMultipleEventDefinition |
multipleTriggerEvent |
event with multiple triggers |
evento con disparadores múltiples |
bpmn:intermediateThrowEvent (sin disparador o con cualquiera) |
intermediateThrowEvent |
intermediate throw event |
evento intermedio de lanzamiento |
bpmn:eventBasedGateway que no sea exclusivo, sin instanciar y con cada flujo de salida hacia un evento de rama de tiempo o de mensaje: instantiate="true", eventGatewayType="Parallel", cualquier otro elemento de rama, o sin flujo de salida |
eventBasedGateway |
event-based gateway |
gateway basado en eventos |
bpmn:complexGateway |
complexGateway |
complex gateway |
gateway complejo |
multiInstanceLoopCharacteristics |
multiInstanceMarker |
multi-instance marker |
marcador de multi-instancia |
standardLoopCharacteristics |
loopMarker |
loop marker on the activity |
marcador de bucle en la actividad |
bpmn:transaction |
transactionSubProcess |
transaction subprocess |
subproceso transaccional |
bpmn:adHocSubProcess |
adHocSubProcess |
ad-hoc subprocess |
subproceso ad-hoc |
bpmn:subProcess con triggeredByEvent="true" |
eventSubProcess |
event subprocess |
subproceso de eventos |
bpmn:choreographyTask, bpmn:choreography, bpmn:globalChoreographyTask |
choreographyDiagram |
choreography diagram |
diagrama de coreografía |
bpmn:conversation, bpmn:callConversation, bpmn:subConversation |
conversationDiagram |
conversation diagram |
diagrama de conversación |
startQuantity distinto de 1 |
startQuantity |
startQuantity attribute other than 1 |
atributo `startQuantity` distinto de 1 |
completionQuantity distinto de 1 |
completionQuantity |
completionQuantity attribute other than 1 |
atributo `completionQuantity` distinto de 1 |
bpmn:endEvent con un disparador que no sea none ni terminate |
endEventTrigger |
end event with that trigger |
evento de fin con ese disparador |
bpmn:startEvent con un disparador que no sea none ni timer |
startEventTrigger |
start event with that trigger |
evento de inicio con ese disparador |
El detalle que distingue cada fila del catálogo se conserva desde el parser; los fixtures que
verifican el texto exacto de las 24 filas son parte de LILA-163. La fila 25 del catálogo,
outOfProfile (element outside the v1 profile / elemento fuera del perfil v1), no la produce
el parser: es el respaldo para las listas unsupported construidas a mano.
Ejemplo literal del mensaje que emite validate(ir), en en y en es:
Boundary_3a1f (bpmn:boundaryEvent, "Vence el plazo"): event attached to an activity (boundary event) not supported by the simulator.
Boundary_3a1f (bpmn:boundaryEvent, "Vence el plazo"): evento adjunto a actividad (boundary event) no soportado por el simulador.
R-NOSOP-3 — Un solo error por elemento, todos los elementos en una pasada.
validate(ir)no se detiene en el primero: devuelve la lista completa, ordenada por orden de aparición en el XML, para que el usuario arregle el archivo de una vez. (prueba: LILA-021, LILA-163)R-NOSOP-4 — Nada de degradación silenciosa. Un elemento no soportado nunca se convierte en
taskde duración 0 ni se “salta”.simulate()se niega a correr sivalidate()devolvió al menos un error. (prueba: LILA-021, LILA-045)R-NOSOP-5 — Errores estructurales (mismo tratamiento, otros códigos): flujo colgante (
E-FLUJO-COLGANTE), id duplicado (E-ID-DUPLICADO), gateway sin salidas o sin entradas (E-GATEWAY-SIN-ARISTAS), nodo inalcanzable desde algúnstart(E-INALCANZABLE), proceso sinstart(E-SIN-START), proceso sinendniterminate(E-SIN-END). (prueba: LILA-021)R-NOSOP-6 — Lo que el lector XML descarta también es error.
bpmn-moddleno lanza cuando no puede leer una parte del archivo: la reporta como warning defromXMLy sigue.parseBpmnconserva esos avisos tal cual enir.source.warnings(texto de moddle en una línea, más el id afectado y la propiedad rota cuando moddle los da) yvalidate(ir)los clasifica en dos, sin descartar ninguno:Error
E-PARSE-INCOMPLETOcuando el aviso implica que se perdió un nodo o un flujo del proceso simulado: un elemento entero que moddle tiró poridilegal o duplicado (unparsable content … nested error: illegal ID <X>/duplicate ID <X>) y que sería nodo o flujo según el perfil de la sección 2, o una referencia sin resolver sobre una propiedad de topología (bpmn:sourceRef,bpmn:targetRef,bpmn:attachedToRef,bpmn:flowNodeRef). Texto exacto, eneny enes:{id}: the XML reader discarded model content, which was left incomplete: {aviso}.{id}: el lector XML descartó contenido del modelo, que quedó incompleto: {aviso}.Aviso
W-PARSEen todo lo demás: referencias sin resolver a construcciones que el perfil de la sección 2 ya ignora (bpmn:messageRef,bpmn:dataStoreRef,bpmn:categoryValueRef) y tipos que moddle no conoce (unparsable content … unknown type <bpmn:LoopCounter>, típico de los exports de Bizagi). También lo que se descarta de la capa de diagrama (bpmndi:,di:,dc:,dd:), lo que se descarta fuera de todobpmn:process(bpmn:message,bpmn:signal,bpmn:error,bpmn:category,bpmn:participant, …) y los elementos que la sección 2 lee sin simular (bpmn:dataObject*,bpmn:dataStore*,bpmn:textAnnotation,bpmn:association,bpmn:group,bpmn:documentation,bpmn:extensionElements,bpmn:laneSet,bpmn:lane), aunque sea por id ilegal o duplicado: ninguno es nodo ni flujo del grafo de tokens, así que perderlos no quita ni un nodo ni un flujo (debpmn:laneel IR solo se queda el nombre enNode.lane, que la CLI usa para mostrar). Texto exacto, eneny enes:{id}: XML reader notice, with no loss of nodes or flows: {aviso}.{id}: aviso del lector XML, sin pérdida de nodos ni flujos: {aviso}.Aviso
W-PARSE, referencia a un flujo ausente: una referencia sin resolver sobrebpmn:incomingobpmn:outgoingno descarta nada por sí misma — el elemento nombra un flujo que no está en el modelo cargado, y el IR derivaincoming/outgoingde los flujos que sí existen. Si ese flujo se perdió, el error lo emite el aviso que lo descartó, no este. Texto exacto, eneny enes:{id}: the XML reader did not find a flow this element declares; the graph is built without it: {aviso}.{id}: el lector XML no encontró un flujo que este elemento declara; el grafo se construye sin él: {aviso}.Aviso
W-PARSE, aviso de otro proceso: los avisos son del archivo entero y el IR es de un proceso (los demás viajan enParseResult.ignoredProcessIds). Lo que se descartó en otrobpmn:processno deja incompleto el que se simula, así que nunca aborta y el aviso dice de qué proceso viene. Texto exacto, eneny enes:{id}: XML reader notice in {proceso}, another process of the file that Lila does not simulate: {aviso}.{id}: aviso del lector XML en {proceso}, otro proceso del archivo que Lila no simula: {aviso}.Aviso
W-XOR-DEFAULT-ROTO: unbpmn:defaultque apunta a un flujo inexistente no descarta nada del grafo; solo se pierde la marcaisDefault, y la sección 6 (R-XOR-1/R-XOR-2) reparte igual sin ella. Texto exacto, eneny enes:{id}: the declared default flow does not exist; the default-flow mark (`isDefault`) is ignored and the split follows the rules of a XOR without a default flow: {aviso}.{id}: el flujo por defecto declarado no existe; se ignora la marca de flujo por defecto (`isDefault`) y el reparto sigue las reglas de un XOR sin flujo por defecto: {aviso}.
{aviso}es el mensaje de bpmn-moddle literal, aplanado a una sola línea.{id}es el id del elemento que moddle señala (o el que aparece dentro del mensaje) y, si no hay ninguno, el id del proceso;{proceso}es el id delbpmn:processdonde ocurrió el aviso, que se ubica por la posición absoluta que da el propio aviso (detected line: N column: C) dentro de los tramos<bpmn:process …>…</bpmn:process>del archivo, de modo que un export minificado o con los atributos partidos en varias líneas se clasifica igual que uno indentado. Si un aviso no dice dónde ocurrió, se atribuye al proceso simulado: falla cerrado. Un modelo que perdió nodos o flujos del proceso simulado nunca valida en verde:E-PARSE-INCOMPLETOaborta ylila validatesale con 1. Los ids no NCName no entran aquí:sanitizeIds(R-DURA-4, LILA-017/020) los reescribe antes de llegar a moddle. (prueba: LILA-185, LILA-196)
4. Aplanado: subproceso embebido y call activity
- R-PLAN-1 — Subproceso embebido, aplanado. Un
bpmn:subProcessembebido desaparece como nodo. Sus flujos entrantes se reconectan alstartinterno y los flujos salientes del subproceso cuelgan delendinterno. Los nodos internos conservan suidy recibensubprocessId= id del subproceso que los contiene (el más interno si hay anidamiento). El aplanado es recursivo. (prueba: LILA-019) - R-PLAN-2 — Start y end internos son pass-through. El
starty elendde un subproceso aplanado no consumen tiempo, no consumen recursos y no cuentan como casos: reenvían el token. Con variosendinternos, todos apuntan a las salidas del subproceso. (prueba: LILA-019) - R-PLAN-3 — El subproceso no tiene tiempo propio.
elements[subProcessId].processingTime(oresources, ofixedCost) es errorE-SUBPROC-PARAMETROcitando el id: el tiempo del subproceso es la suma de lo que ocurre dentro. Las métricas por subproceso se agregan desdesubprocessId. El lint lo caza aunque el subproceso ya no sea un nodo del IR: sus nodos citan de quién vienen (ProcessIR.nodes[x].subprocessId). Límite conocido: ese campo guarda solo el subproceso inmediato, así que con subprocesos anidados el lint reconoce el más interno; declarar parámetros en uno exterior sale hoy comoE-ELEMENTO-DESCONOCIDO. Cerrarlo pide llevar la cadena completa al IR (bpmn/parse.ts), que es otro ticket. (prueba: LILA-019, LILA-198) - R-PLAN-4 — Call activity = tarea con tiempo global. Un
bpmn:callActivityse traduce atasky no se expande el proceso llamado, aunque esté en el archivo. Su duración es suprocessingTimey puede tener recursos yfixedCostcomo cualquier tarea. Es la regla de Bizagi para subprocesos reusables. (prueba: LILA-019) - R-PLAN-5 — El aplanado no cambia los números. Un proceso con subproceso embebido y el mismo
proceso aplanado a mano producen el mismo
RunResultsalvo los ids de los nodos internos. (prueba: LILA-019)
5. Caso, tokens y reloj
R-TOK-1 — Reloj. El tiempo de simulación es un
float64en segundos desderun.start, que vale 0. Los instantes ISO del event log se derivan al exportar sumando segundos arun.start. (prueba: LILA-037)R-TOK-2 — Un caso es un conjunto de tokens. Un caso nace con un token en su
start. Los gateways crean y destruyen tokens. El caso termina en el instante en que su número de tokens llega a 0 (o porterminate, §9). ElcaseIdes un entero monótono por corrida, asignado en orden de llegada. (prueba: LILA-026)R-TOK-3 — Orden de eventos. El heap ordena por
(t, seq)conseqmonótono creciente asignado en el momento de insertar. A igualt, sale primero el evento insertado antes. No hay otro criterio de desempate en ninguna parte del motor. (prueba: LILA-023, LILA-030)R-TOK-4 — Tránsito por flujos instantáneo. Recorrer un
sequenceFlowconsume 0 segundos e incrementaflows[id].count. Los gateways consumen 0 segundos. (prueba: LILA-028)R-TOK-5 — Instantes por token y tarea.
enabled= instante en que el token llega al nodo;started= instante en que empieza a consumirse la duración;ended= instante en que termina. Para nodos sin recurso ni calendario,enabled = started. (prueba: LILA-033, LILA-036)R-TOK-6 — Identidad y lifecycle de actividad. Cada entrada a una tarea o timer crea un
activityInstanceIdopaco y único dentro de la replicación, derivado de un contador. Todas las asignaciones de pools de esa ocurrencia comparten el id y los mismos instantes. Al cierre normal se emitestatus = "completed";terminateemitestatus = "terminated", un temporizador de borde interruptor emitestatus = "interrupted"(§9, R-BND-5), y la parada o cancelación emitestatus = "inFlight". En los dos últimos,startedAt = nulldistingue una instancia que seguía en cola de una ya iniciada yendedAt = null;observedUntilfija el corte. (decisión: ADR-025; prueba: LILA-033, LILA-037)R-TOK-7 — Trabajo sin avance temporal. Cada caso puede procesar como máximo
max(100_000, 1_024 × (número de nodos IR + número de flujos IR))eventos efectivosenter,doneyboundaryen un instante por réplica. El siguiente evento aborta la simulación conE-LIMITE-SIN-AVANCE, indicando nodo BPMN, caso, réplica con índice desde cero, instante en segundos y presupuesto. No cuentan eventos cancelados/obsoletos, llegadas ni cambios de capacidad. Cualquier instante representable estrictamente mayor reinicia el contador del caso, sin epsilon. También se protegen los casos de warmup. Los casos no comparten presupuesto: las llegadas simultáneas no agotan un límite global. Es un límite determinista de trabajo, no una prueba de ciclo infinito: un caso finito excepcionalmente costoso en un instante también puede superarlo. Revisa ciclos y tiempos de procesamiento. No introduce sorteos ni cambios de orden de eventos. (test: #368)
6. Gateway exclusivo (XOR) divergente
Sea un xor con salidas f1…fn en orden de documento (el orden de bpmn:outgoing en el XML,
conservado en ir.nodes[g].outgoing), y p(fi) el probability declarado en
scenario.elements[fi].
- R-XOR-1 — Sin probabilidades, reparto equitativo. Si ningún
fideclaraprobability, cada uno recibe1/n(elisDefaultincluido). (prueba: LILA-026) - R-XOR-2 — El residuo va al flujo que falta. Sea
Sla suma de las probabilidades declaradas yUel conjunto de flujos sinprobability. Si|U| = 1, ese flujo recibemax(0, 1 − S). Es el caso normal delisDefault: el flujoisDefaultrecibe el residuo. (prueba: LILA-026) - R-XOR-3 — Varios flujos sin probabilidad. Si
|U| ≥ 2, el residuomax(0, 1 − S)se reparte por igual entre ellos (elisDefaultrecibe la misma parte que los demás) y se emite avisoW-XOR-RESIDUO-COMPARTIDOcitando el id del gateway y los ids deU. (prueba: LILA-026, LILA-042) - R-XOR-4 — Normalización con aviso. Si tras R-XOR-1…3 la suma
Tde las probabilidades del gateway difiere de 1 en más de1e-9, todas se dividen entreTy se emite avisoW-XOR-NORMALIZADAcon el id del gateway y el valor original deT. La simulación continúa con las probabilidades normalizadas. (prueba: LILA-026, LILA-042) - R-XOR-5 — Suma cero es error. Si
T = 0(todas declaradas en 0 y sin residuo), es errorE-XOR-SUMA-CEROcitando el gateway: no hay ruta posible. (prueba: LILA-042) - R-XOR-6 — Rango.
probabilityfuera de[0, 1]lo rechaza el lint devalidateScenario(E-PROB-RANGO), no el motor. El esquema no acota el rango a propósito (LILA-198): si lo hiciera, el defecto saldría como un error genérico de zod, sin el código del catálogo. (prueba: LILA-013, LILA-198) - R-XOR-7 — Sorteo. Se toma un uniforme
u ∈ [0,1)del stream del gateway y se elige el primerfital queu < Σ_{j≤i} p(fj), recorriendo en orden de documento. Por error de redondeo, si ningúnficumple, se elige el último conp > 0. Un token, un sorteo, una salida. (prueba: LILA-026, LILA-030) - R-XOR-8 —
probabilitysolo en sequence flows.probabilityen un nodo es errorE-PROB-EN-NODO; en un flujo cuyo origen no esxorniores avisoW-PROB-IGNORADA. (prueba: LILA-013, LILA-042, LILA-198)
6.1 Ruteo condicionado al desenlace previo del caso (ADR-028)
Un flujo que sale de un XOR divergente puede declarar conditions: [{ flowTaken, probability }]
en vez de —o además de— su probability a secas. Es lo mínimo necesario para fusionar dos ramas
que estaban duplicadas solo para volver a distinguirlas más adelante (el par de rechazo del ejemplo
de la solicitud de servicio), sin introducir variables de caso ni un lenguaje de expresiones.
- R-COND-1 — Probabilidad declarada efectiva. Cuando un token llega a un XOR divergente, la
probabilidad declarada de cada salida
fies laprobabilityde la primera entrada deconditions[fi]cuyoflowTakenel caso ya recorrió. Si no coincide ninguna —o el flujo no declaraconditions— es laprobabilitya secas del flujo, que puede no existir. (prueba: E22) - R-COND-2 — Lo demás es R-XOR-1…5, sin cambios. Ese vector efectivo, calculado por caso,
pasa por el reparto equitativo, el residuo al único flujo sin declarar, el residuo compartido con
W-XOR-RESIDUO-COMPARTIDO, la normalización conW-XOR-NORMALIZADAy el camino de la suma cero exactamente igual que las probabilidades declaradas. No hay una regla aparte para una compuerta condicionada. (prueba: E22) - R-COND-3 — «Recorrido» es un conjunto. Un flujo está recorrido en cuanto algún token del caso se emitió por él (R-TOK-4), casos de warm-up incluidos. Es un conjunto, no un contador ni una pila: un bucle no lo reinicia y recorrer un flujo dos veces es igual que una. (prueba: E22)
- R-COND-4 — Dónde se admite. Solo en un sequence flow que sale de un XOR divergente: en un
nodo sigue siendo el campo reservado de §15 (
E-RESERVADO, con el texto de siempre) y en cualquier otro flujo esE-CAMPO-NO-APLICA. UnflowTakenque no es un flujo del modelo esE-REF-DESCONOCIDA; unflowTakenque no puede preceder al gateway —no se alcanza recorriendo el IR hacia atrás desde él— es avisoW-COND-INALCANZABLE: por ningún camino secuencial aplica la condición, pero una rama paralela (AND) sí puede recorrer el flujo, así que el aviso es una heurística. Unaprobabilityfuera de[0, 1]esE-PROB-RANGO, como cualquier otra (R-XOR-6). (prueba: E22) - R-COND-5 — Un sorteo, el mismo stream. La compuerta sigue sacando un único uniforme de su
propio stream (R-XOR-7);
conditionscambia los pesos, nunca cuántos números aleatorios se consumen. Un escenario que no declaraconditionsda, por tanto, resultados idénticos bit a bit a los de antes de ADR-028. (prueba: E22, golden)
7. Gateway inclusivo (OR)
- R-OR-1 — Fork: probabilidades independientes. En un
ordivergente, cada salidafise sortea de forma independiente con su propiap(fi): un uniforme por salida, tomados del stream del gateway en orden de documento. No se normaliza: la suma puede ser cualquier valor en[0, n]. (prueba: LILA-026) - R-OR-2 — Salida sin
probabilityen un OR vale 1. Unorsin ninguna probabilidad declarada se comporta como unandfork (todas las ramas). Se emite avisoW-OR-SIN-PROBABILIDADla primera vez. (prueba: LILA-026) Cuando algunas salidas declaranprobabilityy otras no, el lint de escenario (scenario.ts, nocore/) emiteW-OR-PROB-PARCIALpor cada flujo sin declarar, nombrando el gateway: el flujo se toma igual, pero quien declaró unas probabilidades casi nunca quiere dejar otras siempre activas. Unorcon todas las salidas declaradas, o ninguna, no tiene nada parcial y no recibeW-OR-PROB-PARCIAL. (prueba: #398) - R-OR-3 — Al menos una salida. Si los
nsorteos fallan, se activa el flujoisDefault; si no hayisDefault, el de mayorp, con desempate por orden de documento. Se cuenta y se reporta avisoW-OR-VACIOcon el id del gateway y el número de veces que ocurrió. (prueba: LILA-026) - R-OR-4 — Registro de activación. Al activar
ksalidas, el fork registra(caso, forkId, activationId, k)conactivationIdmonótono, y marca cada token emitido con eseactivationId. Los tokens hijos heredan la marca al pasar por gateways posteriores; un token puede llevar varias marcas apiladas (forks OR anidados: se apilan y se desapilan en orden LIFO). (prueba: LILA-026) - R-OR-5 — Join: espera a los del fork emparejado. Un
orconvergente cuenta los tokens que llegan por(caso, activationId)de la marca más reciente y dispara cuando ha recibidoktokens, emitiendo un token por su salida y desapilando la marca. Es la semántica práctica de Bizagi/Prosimos y se documenta como simplificación: el motor no evalúa alcanzabilidad estructural (la semántica OR-join completa del estándar BPMN) porque sin condiciones evaluables no aporta nada. (prueba: LILA-026) - R-OR-6 — Join sin fork emparejado = mezcla. Si un token llega a un
orconvergente sin marca activa (por ejemplo, viene de unxor, o el fork está fuera del ciclo), el join se comporta como pass-through: cada token sale inmediatamente. Se emite avisoW-OR-JOIN-SIN-FORKcon el id del join. (prueba: LILA-026) - R-OR-7 — Reinicio por loop. El contador de un
orjoin vive en(caso, activationId): una nueva vuelta del ciclo genera unactivationIdnuevo, así que el contador de la vuelta anterior no interfiere. (prueba: LILA-026) - R-OR-8 — Tokens huérfanos al parar. Los tokens que quedan esperando en un join cuando la
corrida termina cuentan como caso
inFlighty disparan avisoW-JOIN-BLOQUEADOcon el id del join y el número de casos afectados. (prueba: LILA-026, LILA-028)
8. Gateway paralelo (AND)
- R-AND-1 — Fork. Un
anddivergente emite un token por cada salida, todos en el mismo instante, sin sorteo.probabilityen las salidas de unandes avisoW-PROB-IGNORADA. (prueba: LILA-026) - R-AND-2 — Join por contador
(caso, join). Unandconvergente mantiene un contador por pareja(caso, joinId). Cada token que llega lo incrementa y se consume. Cuando el contador alcanza el número de flujos entrantes del join en el IR, se emite un token por la salida y el contador se reinicia a 0. (prueba: LILA-026) - R-AND-3 — Loops. El reinicio de R-AND-2 es lo que hace correcto el comportamiento en ciclos: la segunda vuelta vuelve a contar desde 0 y no dispara con tokens de la vuelta anterior. No hay “memoria” entre vueltas ni por rama. (prueba: LILA-026)
- R-AND-4 — El AND join no distingue por rama. Dos tokens que llegan por la misma rama entrante
(posible en modelos mal formados con ciclos) cuentan como dos. El motor no lo corrige; si al
terminar la corrida quedan contadores parciales, aplica
W-JOIN-BLOQUEADO. (prueba: LILA-026) - R-AND-5 — Duración de la sección paralela. Con ramas de duración determinista, la sección
fork → ramas → joindura exactamente el máximo de las ramas (no hay costo de sincronización). (prueba: LILA-026) - R-AND-6 — Join con una sola entrada. Un
andcon una entrada y una salida es pass-through. (prueba: LILA-026)
9. Timer, end y terminate
- R-EVT-1 — Timer = retardo sin recurso. Un
timerretiene el token duranteelements[id].processingTimesegundos y lo suelta por su única salida. No consume recursos. Declararresourcesen untimeres errorE-TIMER-RECURSOcitando el id. (prueba: LILA-026, LILA-198) - R-EVT-2 — Timer sin tiempo. Un
timersinprocessingTimeretarda 0 segundos y produce avisoW-TIMER-SIN-TIEMPO. (prueba: LILA-026) - R-EVT-3 — El timer corre 24×7 salvo que declare calendario. Por defecto el retardo del timer
transcurre en tiempo de reloj (un plazo legal corre también de noche). Si el elemento declara
elements[id].calendar, el retardo consume solo tiempo abierto y el tiempo cerrado se acumula enoffHoursWait. (prueba: LILA-041) - R-EVT-4 — End consume el token. Un
endconsume el token que llega y no hace nada más. El caso se marcacompletedcuando su número de tokens llega a 0, no cuando el primer token toca unend. Un modelo con AND fork y dosendtermina el caso al llegar el segundo token. (prueba: LILA-026, LILA-028) - R-EVT-5 — Terminate mata el caso. Un
terminatedestruye todos los tokens del caso, sus contadores de join y sus marcas de activación OR, cancela sus eventos futuros y libera de inmediato los recursos que el caso tuviera ocupados, cargando el costo por hora hasta ese instante. El caso cuenta comocompletedconendedAt= ese instante.terminateno afecta a otros casos ni detiene la corrida. Losdonependientes de las actividades que mató se consumen sin adelantar el reloj, como en R-BND-9. (prueba: LILA-026, #345) - R-EVT-6 — Tareas en curso al morir el caso. La tarea interrumpida por
terminatecuenta comostartedy no comocompleteden su elemento; no aporta a las estadísticas deprocessing. (prueba: LILA-026, LILA-028) - R-BND-1 — Temporizador de borde interruptor. Un
bpmn:boundaryEventcon exactamente untimerEventDefinition, interruptor (cancelActivityausente otrue), adjunto a una tarea soportada —nunca a un subproceso embebido, que se aplana (§4)— y con al menos un flujo de salida, entra al perfil como nodotimerconattachedTo= el id del host y sinincoming: su token lo crea el host, no un flujo. Es alcanzable exactamente cuando lo es su host, así que nunca esE-INALCANZABLEpor sí solo. Cualquier otro boundary event sigue siendoE-NOSOPcon la construcciónboundaryEvent(§3). (prueba: #81) - R-BND-2 — El plazo cuenta desde que se habilita el host. Se mide desde el
enabledAtde la actividad host, no desde que consigue sus recursos: un token que espera en la cola de un pool ocupado ya está consumiendo su plazo. (prueba: #81) - R-BND-3 — Tiempo de reloj y flujo de aleatorios propio. El retardo transcurre 24×7 salvo que
el borde declare
elements[id].calendar, igual que cualquier otro timer (R-EVT-3). Se muestrea del flujo del id del propio borde, así que añadir un borde no desplaza los sorteos de ningún otro elemento (§16). (prueba: #81) - R-BND-4 — Solo dispara mientras la actividad host siga abierta. Si el host ya completó, lo
mató un
terminateo lo cortó la parada de la corrida, el evento de vencimiento se descarta al salir del heap. (prueba: #81) - R-BND-5 — Qué le hace el vencimiento al host. La actividad host se cierra con
status = "interrupted",endedAt = nullyobservedUntil= el instante del corte; su solicitud de recurso se libera si estaba concedida y se retira si seguía en cola, cobrando el costo por hora hasta ese instante; elfixedCostdel elemento no se cobra, porque solo lo cobra una actividad completada (§13). El host cuentastartedy nuncacompleted, igual que conterminate(R-EVT-6). (prueba: #81) - R-BND-6 — El token sigue por el borde. El borde cuenta un
startedy uncompleteden el mismo instante (no consume tiempo, como un gateway) y el token sale por su primer flujo de salida con las marcas de activación OR que traía el token del host, de modo que un OR join aguas abajo sigue cerrando. El borde no emite fila propia en el event log. (prueba: #81) - R-BND-7 — Empate: gana la interrupción. El evento de vencimiento se encola antes que el
donedel host, así que con instantes iguales(t, seq)(R-TOK-3) saca primero al borde: un plazo exactamente igual a la duración de la tarea la interrumpe. (prueba: #81) - R-BND-8 — Un borde sin tiempo nunca vence. Un borde sin
processingTimeno agenda nada y produce el avisoW-BORDE-SIN-TIEMPO, citando el borde y su host. (prueba: #81) - R-BND-9 — Un vencimiento muerto no mueve el reloj. Un evento de vencimiento cuya actividad
ya está cerrada se consume sin adelantar el reloj, así que una corrida sin
run.durationno se estira hasta un plazo que nunca iba a vencer (R-ARR-3). Eldonedel propio host, que queda pendiente tras la interrupción, se consume igual. (prueba: #81, #345) - R-BND-10 — Temporizador de borde no interruptor. El mismo boundary event de R-BND-1 pero
con
cancelActivity="false": exactamente untimerEventDefinition, adjunto a una tarea soportada y con al menos un flujo de salida. Solo se lee el léxico propio dexsd:boolean, y del texto del XML:cancelActivityausente,"true"o"1"interrumpe,"false"o"0"no interrumpe, y cualquier otro literal ("TRUE","", una errata) no está soportado y sigue siendoE-NOSOPcon construcciónboundaryEvent(§3). Entra al perfil como el mismo nodotimerconattachedToy sinincoming, marcadointerrupting: false(ausente es interruptor, que es el valor por omisión decancelActivityen BPMN). R-BND-2 (el plazo cuenta desde elenabledAtdel host), R-BND-3 (tiempo de reloj y flujo de aleatorios propio), R-BND-4 (solo dispara mientras la actividad host siga abierta), R-BND-8 (sinprocessingTimeno dispara) y R-BND-9 (un vencimiento muerto no mueve el reloj) valen palabra por palabra. Se agenda un plazo por ocurrencia del host, así que el borde dispara como máximo una vez por ocurrencia y nunca se repite: un temporizador de borde cíclico no está en el perfil. (prueba: #81) - R-BND-11 — El host sigue corriendo; el vencimiento añade un token. El vencimiento no toca
al host: su actividad no se cierra, conserva su asignación de recursos, completa a su hora y
cuenta
completedconstatus = "completed"— no hay filainterrupted. Lo que hace el vencimiento es meter en el caso un token nuevo, que sale por el primer flujo saliente del borde. El borde se comporta igual que en R-BND-6: unstartedy uncompleteden el mismo instante, y ni una fila propia en el event log. (prueba: #81) - R-BND-12 — El token nuevo no lleva marcas de activación OR. A diferencia de R-BND-6, donde
el token del borde es el del host y hereda sus marcas, aquí el host conserva su token y sus
marcas. El token que crea el borde no lo activó ningún fork, así que nace con la pila de marcas
vacía: un OR join aguas abajo sigue esperando exactamente los
ktokens que activó su fork (R-OR-5) y sigue cerrando, y el token nuevo que llegue a ese join es una mezcla (R-OR-6, avisoW-OR-JOIN-SIN-FORK). Copiar las marcas del host haría que el join contasek + 1tokens de una misma activación y se bloquearía. (prueba: #81) - R-BND-13 — Empate en el instante exacto del fin del host: el borde dispara. Exactamente la
regla de R-BND-7 y el mismo mecanismo: el evento de vencimiento se encola antes que el
donedel host, así que con instantes iguales(t, seq)(R-TOK-3) saca primero al borde, que todavía encuentra abierta la actividad host. Un plazo exactamente igual a la duración de la tarea dispara, por tanto, y el host completa en ese mismo instante. (prueba: #81) - R-BND-14 — Más tokens a la vez, un solo caso. Un vencimiento sube el número de tokens vivos
del caso, así que el caso no termina cuando el primer token toca un
end: termina cuando se queda sin tokens (R-EVT-4). Las dos ramas tienen que llegar a unendpara que el caso cierre; el caso se cuenta una vez y su desenlace (process.byEndEvent) se atribuye alendque consumió el último token. Los joins AND y OR no cambian: un AND join sigue esperando un token por flujo entrante (R-AND-2) —que es como la rama del borde se reincorpora a la del host— y un OR join cuenta por marca de activación (R-OR-5, R-BND-12). Un token que siga viajando al parar la corrida deja el casoinFlight, como cualquier otro token (R-OR-8, R-AND-4). (prueba: #81)
9.1 Gateway basado en eventos
El gateway que espera a lo que pase primero. Bizagi lo simula dándole un tiempo a cada evento intermedio que sigue al gateway y dejando que gane el primero que vence; lo único que deja fuera es el gateway seguido de eventos intermedios none o de tareas (help.bizagi.com/platform/en/simulation_in_bizagi.htm). Lila sigue esa convención, que es lo que hace simulable la construcción sin mensajes, señales ni bus de eventos.
- R-EVG-1 — Gateway basado en eventos dentro del perfil. Un
bpmn:eventBasedGatewayentra al perfil cuando se cumplen las tres cosas: es exclusivo (eventGatewayTypeausente o"Exclusive"), no instancia el proceso (instantiateausente,"false"o"0") y cada uno de sus flujos de salida va a un evento de rama: unbpmn:intermediateCatchEventcon exactamente un disparador,timerEventDefinitionomessageEventDefinition, exactamente un flujo entrante —el del gateway— y exactamente un flujo de salida. El gateway es entonces un nodoeventGatewayy cada evento de rama un nodotimer(§2). Como concancelActivityen R-BND-10, deinstantiatesolo se lee el espacio léxico dexsd:booleany directamente del texto del XML, así que"true"y"1"instancian y cualquier otro literal ("TRUE","", una errata) no está soportado. La regla es todo o nada: una rama que sea otra cosa —una tarea, un evento intermedio none/de señal/condicional, otro gateway, un subproceso— deja fuera al gateway entero conE-NOSOPy construccióneventBasedGateway(§3), y cada elemento de la rama conserva su propia fila del catálogo. El flujo entrante único es lo que hace que una rama sea una rama y nada más: solo se alcanza a través de su gateway, así que ningún token puede entrar en ella como en un timer suelto. Un evento intermedio de captura de mensaje en cualquier otro sitio se queda enE-NOSOP(§3). (prueba: #81) - R-EVG-2 — Cada rama es un retardo con su propio tiempo. El retardo de un evento de rama es
elements[<id del evento>].processingTime, el mismo campo que usa un timer intermedio (R-EVT-1), y transcurre 24×7 salvo que el evento declareelements[<id del evento>].calendar(R-EVT-3). Una rama de mensaje se modela por tanto como una espera de ese tiempo: es la convención de Bizagi, y Lila no simula el mensaje en sí —no hay emisor, ni correlación, ni cola de mensajes—. Una rama no consume recursos: declararresourcesen ella esE-TIMER-RECURSO, igual que en cualquier otro timer. (prueba: #81) - R-EVG-3 — Todas las ramas se arman a la vez y solo la ganadora deja rastro. Cuando el token
llega al gateway se arman todas las ramas en ese mismo instante, cada una con un retardo
sorteado del flujo de aleatorios de su propio id (R-DET-2), así que añadir o quitar una rama
no mueve los sorteos de ningún otro elemento (§16). Gana la rama cuyo instante de disparo sea el
más temprano: su token sigue por el flujo de salida de ese evento y las demás ramas se descartan
sin
started, sincompleted, sin fila del event log y sin conteo de flujo. El gateway cuenta unstartedy uncompleteden el instante en que le llega el token, como cualquier otro gateway, y no emite fila. La ganadora cuenta unstartedal armarse y uncompletedal disparar, y emite la fila del event log de cualquier otro timer (enabledAt=startedAt= el instante del gateway,endedAt= el disparo, sin recurso). El número de tokens del caso no cambia: entra uno, sale uno. Unaprobabilityen los flujos de salida del gateway, incluido0, se ignora y produceW-PROB-IGNORADA, citando el flujo y el gateway: lo que enruta el token es la carrera, nunca un sorteo (al contrario que en R-XOR-1). La ausencia deprobabilityno produce este aviso. (prueba: #81, #369) - R-EVG-4 — La carrera es de instantes, y el empate lo gana el primer flujo. Lo que se compara
es el instante en que dispararía cada rama, no el retardo en bruto, así que una rama con su
propio calendario corre en el mismo reloj que las demás. Con dos instantes iguales gana la rama
que aparece primero en el
outgoingdel gateway, que es el orden en que sus flujos aparecen en el documento: el mismo desempate explícito por orden de inserción que(t, seq)en R-TOK-3, y nunca un sorteo. (prueba: #81) - R-EVG-5 — Una rama sin tiempo nunca dispara. Un evento de rama sin
processingTimeno tiene retardo que vencer, así que queda fuera de la carrera y produce el avisoW-TIMER-SIN-TIEMPO(el código que ya tenía un timer intermedio sin tiempo, con el texto de la rama: nunca dispara, en vez de retardar 0 segundos), citando al evento y a su gateway. Las ramas que sí tienen tiempo corren como siempre. (prueba: #81) - R-EVG-6 — Sin ninguna rama que pueda disparar, el token se queda en el gateway. Si ninguna
rama del gateway declara
processingTime, nada puede vencer y el token se queda ahí: el caso no llega a ningúnendy cuenta comoinFlightal parar, como cualquier otro token que siga viajando (R-OR-8). No es un error, así que una corrida que solo valida rutas (sin tiempos en ninguna parte, R-DEG-3) sigue corriendo; al cerrar la replicación, el avisoW-JOIN-BLOQUEADO—el código del token que quedó esperando— cita al gateway y cuántos casos retuvo. (prueba: #81) - R-EVG-7 — Una carrera por pasada. Entre pasadas no se guarda nada: un gateway dentro de un bucle vuelve a armar sus ramas, con un sorteo nuevo por rama, cada vez que le llega un token, y las carreras de dos pasadas distintas (o de dos casos distintos) no comparten más que los flujos de aleatorios de los que sortean. (prueba: #81)
10. Llegadas, parada, warmup y replicaciones
- R-ARR-1 — Un generador por start. Cada
startconinterTriggerTimergenera casos. La primera llegada ocurre ent = 0; la siguiente ent + muestracon una muestra nueva deinterTriggerTimertomada del stream de esestart. UnstartcontriggerCounty sininterTriggerTimerequivale al defaultinterTriggerTimer: {"type":"constant","value":0}, es decirtriggerCountllegadas ent = 0: es lo que hace el nivel 1 de Bizagi, cuya configuración son solo «porcentajes de activación en cada flujo saliente de gateways exclusivos/inclusivos y "Max. arrival count" en el Start Event», sin ningún campo de tiempo con el que espaciarlas (help.bizagi.com/platform/en/level_1_example.htm). Solo elstartsin ninguno de los dos campos no genera nada y avisaW-START-SIN-LLEGADAS. (prueba: LILA-026, LILA-186) - R-ARR-2 — Fin de la generación. Un generador deja de emitir cuando ocurre lo primero de:
(a) ha emitido
triggerCountcasos; (b) el instante de la siguiente llegada es≥ t_stop. (prueba: LILA-026) - R-ARR-3 — Parada de la corrida.
t_stop = run.durationsi está definido; si no, la corrida termina cuando el heap se vacía. Con ambos definidos, manda lo primero que ocurra: el heap vacío también termina la corrida antes derun.duration. Es errorE-SIN-PARADAque no haya nirun.durationni ningúntriggerCounten un start (solo elstartmonta generador, R-ARR-1: untriggerCounten untimerintermedio esE-CAMPO-NO-APLICAy no cuenta como parada). (prueba: LILA-026, LILA-013) - R-ARR-4 — Ejemplo normativo.
duration = 3600,triggerCount = 10000, llegadas constantes cada 10 s ⇒ llegadas ent = 0, 10, …, 3590⇒started = 360. (prueba: LILA-026) - R-ARR-5 — En vuelo al parar. En
t_stopse descartan los eventos pendientes. Los casos iniciados y no terminados cuentan enstartedy no encompleted;inFlight = started − completed. Sus tareas a medias no aportan aprocessingni aresourceWait. Es el criterio de Bizagi. (prueba: LILA-026, LILA-028) - R-ARR-6 — Llegadas con calendario. Si el
startdeclaracalendar, una llegada que cae en tiempo cerrado se desplaza al siguiente instante abierto (nextOpen); no se pierde y no se acumulan varias en el instante de apertura salvo que el propio muestreo las genere. La cadencia sigue midiéndose en tiempo de reloj. (prueba: LILA-041) - R-ARR-7 — Warmup.
run.warmup(segundos desderun.start) excluye de todas las estadísticas los casos iniciados antes dewarmup, pero esos casos existen: ocupan recursos, hacen cola y afectan a los demás. Un caso iniciado enwarmup − 1no cuenta aunque termine después. Tampoco aporta directamente a costos ni integrales:queueLengthy utilización integran solo el estado atribuible a la cohorte medida en[warmup, t_stop]; la ocupación pre-warmup sí puede retrasar indirectamente a esa cohorte. (prueba: LILA-027, LILA-033) - R-ARR-8 — Replicaciones.
run.replications = Rcorre R veces la misma configuración; la replicaciónr(0-indexada) usa streams derivados de(seed, r, elementId). Por KPI se reportann(las replicaciones que lo observaron, una observación cada una),meany —solo conn ≥ 2—sd(muestral,n − 1) yci95 = mean ± t(0,975; n−1) · sd / √n. En un KPI incondicionaln = R; uno condicional (las duraciones de una tarea, el tiempo de ciclo y de espera del proceso o de un desenlace, listados enRESULTS_FORMAT.md§ 8) solo lo observan las replicaciones donde ocurrió, y el cero de conjunto vacío de las demás no es una observación. ConR = 1no hayci95. Los casos no se comparten entre replicaciones: cada una parte del estado vacío. (prueba: LILA-027, #356) - R-ARR-9 — Agregado público multi-réplica. En
simulate(), cada campo numérico top-level es la media del mismo campo agregado por replicación, sobre las replicaciones que lo observaron (R-ARR-8), y 0 si ninguna lo observó; no es la primera replicación ni una muestra agrupada de todos los casos. En una corrida completa coincide con elmeandel mismo path enreplications.kpis: los dos salen de la misma selección de observaciones. Cuando algunas replicaciones observaron una tarea, un timer, el proceso o un desenlace y otras no, la corrida avisaW-REPLICACIONES-SIN-OBSERVACIONES. (prueba: LILA-029, #356; decisión: ADR-024) - R-ARR-10 — Cancelación cooperativa.
opts.signalse comprueba entre eventos y entre replicaciones. El resultado parcial llevacancelled: trueycompletedReplications; el top-level conserva la réplica parcial, peroreplications.kpissolo usa replicaciones completas y se omite si hay menos de dos. Cada evento DES se cierra atómicamente: una señal activada desdeonEventsurte efecto antes del siguiente evento, no entre completar una tarea y recorrer sus flujos salientes instantáneos. (prueba: LILA-029)
11. Recursos
scenario.resources[pool] = { name, type: "role"|"equipment", capacity, costPerHour, fixedCost, calendar }. En la tarea: resources: [{ ref, quantity }] y selection: "and" | "or".
- R-REC-1 — Pool con capacidad entera.
capacityes obligatorio y es un entero≥ 1, o la lista de tramos[{ calendar, capacity }]de R-CAL-11, en la que cadacapacity_ies a su vez un entero≥ 1y la capacidad del pool varía con el reloj. Un pool es un contador de unidades idénticas: no hay identidad individual de recurso en v1 (el event log registra el pool, no la unidad), y eso no cambia con la capacidad variable —lo que varía es cuántas unidades hay, no cuáles—. Fuera de R-CAL-11 el resto de esta sección leecapacitypor un único camino y no distingue las dos formas. (prueba: LILA-033, LILA-164) - R-REC-2 — Defaults de la asignación.
quantityausente vale 1.selectionausente vale"and". Con un solo pool,andyorson equivalentes.quantity > capacitydel pool es errorE-REC-CANTIDADcitando tarea y pool (esperaría para siempre); con capacidad por intervalos el tope es el máximo de la semana, no la suma de los tramos (R-CAL-11). Unarefa un pool inexistente es errorE-REC-DESCONOCIDO, un pool repetido en la misma tarea esE-REC-DUPLICADOy unacapacityque no sea entero ≥ 1 esE-REC-CAPACIDAD. Los cuatro se comprueban en un preflight antes de cualquier callback público: una corrida no puede emitir filas ni progreso y fallar después. Desde LILA-034 múltiples pools con selección ausente o"and"son válidos y desde LILA-035 también lo es"or".quantity > capacityes error también en una alternativa OR: la alternativa nunca podría arrancar y el escenario está mal declarado aunque otra sí quepa. (prueba: LILA-013, LILA-033, LILA-034, LILA-035, LILA-042) - R-REC-3 — Cola FIFO por instante de habilitación. Cada pool tiene una cola ordenada por
(enabled, seq)ascendente, dondeseqes el contador monótono del evento que habilitó al token. Comoseqes único, el orden es total y determinista: no hay empates reales. (prueba: LILA-033, LILA-023) - R-REC-4 — Selección AND: atómica, sin retención parcial. La tarea entra en la cola de todos
sus pools. Arranca cuando todos ellos tienen simultáneamente
quantityunidades libres; en ese instante se descuentan todas de golpe. Nunca se retiene un recurso mientras se espera otro, así que no puede haber deadlock. Las filas yassignmentsconservan el orden declarado en el escenario aunque el índice interno use todos los pools. (prueba: LILA-034) - R-REC-5 — FIFO con salto en la asignación AND. En cada liberación se recorre la cola en orden
FIFO global
(enabled, seq)y arranca el primer candidato satisfacible; un candidato que no puede arrancar no bloquea a los que van detrás. Es una desviación deliberada del FIFO estricto: sin ella, un candidato multi-pool bloqueado congelaría el pool entero. El salto es entre firmas de requisitos distintas: todas las solicitudes que piden un único pool comparten una sola cola FIFO por pool, así que una cabeza single-pool que no cabe sí bloquea a las que van detrás en ese mismo pool, aunque pidan menos unidades (ADR-026). (prueba: LILA-034, LILA-033) - R-REC-6 — Selección OR. La tarea se encola en todos los pools alternativos y arranca con
el primero que tenga
quantityunidades libres; al arrancar se retira de las demás colas y solo ocupa unidades del pool elegido. Cada alternativa entra en la cola FIFO de su pool con el mismo(enabled, seq), así que OR, AND y single-pool compiten en igualdad en cada pool (R-REC-5). Desempate: cuando en el mismo instante hay varias alternativas libres, gana la que aparece primero en el arrayresourcesde la tarea (orden de documento del escenario). Es la única regla: da igual por qué liberación llegó la disponibilidad, porque todas las liberaciones de un mismo instante se aplican antes de planificar. El pool efectivamente usado se registra enresourceIddel event log y una OR produce exactamente una fila de asignación (R-REC-11); sus costos son los del pool usado. (prueba: LILA-035) - R-REC-7 — Ocupación y liberación. Las unidades se ocupan en
startedy se liberan enended(o al morir el caso, R-EVT-5). No hay apropiación (preemptes campo reservado, §15) ni prioridades: una tarea empezada nunca se interrumpe salvo porterminate. (prueba: LILA-033) - R-REC-8 — Espera por recurso.
resourceWait = started − enabled − offHoursWait[enabled, started]. Sin calendarios,offHoursWait = 0y queda la definición de la sección 6 del documento de estructura:resourceWait = started − enabled. (prueba: LILA-036, LILA-041) - R-REC-9 — Un mismo pool no se pide dos veces. Dos entradas con la misma
refen una tarea es errorE-REC-DUPLICADO: la cantidad se expresa conquantity. (prueba: LILA-013) - R-REC-10 — Elementos sin recursos.
start,end,terminate, gateways ytimernunca consumen recursos. Solotask(y por tantocallActivity) admiteresources. (prueba: LILA-021, LILA-026) - R-REC-11 — Filas por asignación. Cada asignación efectiva de pool produce una fila plana con
resourceIdyresourceQuantity; una actividad sin pool produce exactamente una fila sentinel conresourceId,resourceQuantityyallocationIndexennull. Una actividad AND/OR que se cierra mientras aún espera también emite una sola sentinel, porque todavía no existe asignación; una vez iniciada emite sus asignaciones efectivas. Las filas se agrupan poractivityInstanceId, nunca mediante un array anidado. Métricas de actividad y caso deduplican por(replication, activityInstanceId); costos y ocupación de recurso sí se suman por fila. (decisión: ADR-025; prueba: LILA-033, LILA-034, LILA-037)
12. Calendarios (ADR-016)
Bizagi no documenta su semántica de calendarios; esta es la de Lila, ajustable si alguien aporta el comportamiento real de L-Sim/Bizagi.
scenario.calendars[nombre] = { intervals: [{ days: ["MON"…"SUN"], from: "HH:MM", to: "HH:MM" }], holidays?: [...] }.
Desde #82 un intervalo también puede repetirse por mes o por año (monthDays, monthWeekdays,
dates, R-CAL-12/13), y holidays cierra días enteros (R-CAL-14).
R-CAL-1 — Patrón semanal relativo a
run.start. Los días y horas se interpretan en el mismo offset UTC querun.start. No hay DST ni zonas horarias por recurso en v1 (timezonees un campo reservado, §15, y R-CAL-15 fija el límite). El patrón se repite indefinidamente. La recurrencia mensual y anual y los festivos (R-CAL-12 … R-CAL-14) se leen en ese mismo offset. (prueba: LILA-040)R-CAL-2 — Intervalos.
frominclusivo,toexclusivo.to > fromes obligatorio (R13 deSCENARIO_FORMAT.md): ningún intervalo cruza la medianoche, y una ventana nocturna se declara como dos intervalos (p. ej.22:00–24:00del lunes y00:00–06:00del martes).toadmite"24:00", la medianoche del día siguiente, y es el único sitio donde se admite: sin ella el formato no sabría decir "hasta el final del día" —el tope deHH:MMes23:59ytoes exclusivo— y tanto un 24×7 escrito a mano como esa ventana nocturna perderían 60 s cada noche en silencio. Los intervalos de un mismo calendario se normalizan uniendo solapes y adyacencias, así que los dos de la ventana nocturna quedan como uno solo. Un calendario conintervals: []es errorE-CAL-VACIO(nunca abriría). (prueba: LILA-040, LILA-041, LILA-042)R-CAL-3 — Primitivas.
isOpen(t),nextOpen(t)(el propiotsi ya está abierto) yaddWorkingTime(t, d)(instante en que se han consumidodsegundos abiertos desdet; cond ≤ 0devuelvettal cual, aunque esté cerrado, y sidtermina justo al cerrar un intervalo devuelve ese cierre). Derivadas:openTime(a, b)(segundos abiertos contenidos en[a, b), base deoffHoursWaity deavailableTime) eintersect(cal1, cal2)(calendario de intervalos comunes, R-CAL-4). Son las únicas operaciones de calendario del motor: no hay eventos de apertura/cierre en el heap, la disponibilidad se resuelve al planificar. (prueba: LILA-040)R-CAL-4 — Una tarea solo arranca en horario abierto.
started = nextOpen(instante en que hay recursos). El calendario aplicable a una tarea es la intersección de los calendarios de los pools que ocupa (selección AND) o el del pool asignado (selección OR); si además el elemento declaraelements[id].calendar, se intersecta también. Sin recursos, el del elemento; sin ninguno, 24×7. Si la intersección queda vacía la tarea nunca podría arrancar: es errorE-CAL-VACIOcitando la tarea; en selección OR se comprueba cada alternativa por separado, porque cualquiera de ellas basta para arrancar. La intersección asume que los dos calendarios compartenoffset, que en v1 es global porque sale derun.start; si algún díatimezonepor recurso deja de ser un campo reservado (§15), habrá que reconciliar los offsets antes de intersecar. (prueba: LILA-041)R-CAL-5 — El processingTime se pausa y se reanuda. La duración muestreada se consume solo en tiempo abierto:
ended = addWorkingTime(started, d). Al cerrar el turno la tarea se congela y reanuda en la siguiente apertura. Ejemplo normativo: tarea de 2 h que arranca a las 17:30 con calendario 9:00–18:00 ⇒ termina a las 10:30 del siguiente día hábil. (prueba: LILA-040, LILA-041)R-CAL-6 — La unidad de recurso queda reservada desde la concesión y durante el cierre. La unidad se descuenta del pool en el instante en que el planificador la concede, no en
started: si la concesión cae en tiempo cerrado, la unidad queda reservada mientras la tarea espera a la apertura y no se reasigna a otro token. Tampoco se reasigna mientras la tarea está pausada fuera de horario. En ninguno de los dos tramos acumulabusyTimeni costo por hora. (prueba: LILA-041, LILA-036)R-CAL-7 —
offHoursWaitseparado deresourceWait.offHoursWaitde una fila del log es el tiempo cerrado contenido en[enabled, ended], sumando el cerrado antes de arrancar y el cerrado durante el procesamiento. Ejemplo normativo (el de R-CAL-5):offHoursWait = 15 h(18:00→09:00) yresourceWait = 0. (prueba: LILA-041)R-CAL-8 — Identidad de tiempos. Para toda fila del log:
ended − enabled = resourceWait + offHoursWait + processing, dondeprocessinges la duración muestreada (tiempo abierto efectivamente trabajado). (prueba: LILA-036, LILA-041)R-CAL-9 — Utilización sobre horas disponibles. Para un pool,
utilization = busyTime / Σᵢ (capacityᵢ × openTimeᵢ), donde el sumatorio recorre los tramos de capacidad del pool (R-CAL-11) yopenTimeᵢes el tiempo abierto del calendario del tramoidentro de la ventana de medida[warmup, t_stop]. Con la forma numérica hay un solo tramo y la fórmula colapsa enbusyTime / (capacity × availableTime), conavailableTimeel tiempo abierto del calendario del pool dentro de esa misma ventana (o el tiempo total de la ventana si no tiene calendario). La ventana es siempre[warmup, t_stop], no la duración declarada del escenario: con una corrida que para al agotarse las llegadas (R-ARR-3) las dos difieren y las utilizaciones no son las mismas. Bizagi usa la duración declarada en su nivel 4 —y[warmup, t_stop]en el nivel 3—; la conversión es exacta y está endocs/BIZAGI_PARITY.md§ D7:util_bizagi = util_lila × ventana_lila / duración_declarada. Lila no cambia de denominador por eso. Es la única definición que hace comparables los niveles 3 y 4 de Bizagi.La métrica es atribuible a la cohorte medida, no un sensor del estado físico del pool:
busyTimeexcluye por R-ARR-7 los casos nacidos antes delwarmup, mientras el denominador conserva toda la capacidad disponible de[warmup, t_stop]. Por ello puede valer 0 aunque un caso de calentamiento mantenga ocupado el pool durante toda la ventana; descontar esa ocupación del denominador mezclaría cohortes y haría que ya no representase capacidad disponible.Tampoco está acotada artificialmente a 1. Con capacidad por turnos, una tarea iniciada antes de una bajada sigue ejecutándose por R-CAL-11: el
busyTimeobservado puede superarΣᵢ capacityᵢ × openTimeᵢy la utilización será> 1. Ese exceso es evidencia de trabajo no interrumpido sobre la plantilla posterior; el resultado conserva el valor y emiteW-UTILIZACION-MAYOR-UNOpor pool. Solo se tolera el ruido de coma flotante de unas pocas ULP alrededor de 1 al decidir si emitir el aviso. (prueba: LILA-041, LILA-036, LILA-164, LILA-204)R-CAL-10 — Matriz recurso × calendario con calendario por defecto. Cada pool puede declarar
calendar; si no lo hace, usa el calendario llamadodefaultsi existe, y si no existe, 24×7. Unacalendarque no existe encalendarses error citando el pool:E-REF-DESCONOCIDAsi lo caza el lint estático devalidateScenario(el camino normal, R9 deSCENARIO_FORMAT.md) yE-CAL-DESCONOCIDOsi lo caza el guardia decore/sim.ts, que no puede importar el validador y se defiende solo. Unificar los dos códigos en uno tocacore/. (prueba: LILA-041, LILA-042)R-CAL-11 — Capacidad por turno dentro de un mismo pool.
resources[pool].capacityadmite, además del entero, la lista de tramos[{ calendar, capacity }](§ 2.4 y R16 deSCENARIO_FORMAT.md): 3 enfermeras de día y 1 de noche son un pool, no tres. Es el «Resources → Calendars → quantity» de Bizagi, el que hace falta para el nivel 4. El contrato completo:- Apertura por unión. El pool está abierto cuando lo está cualquiera de los
calendarios de sus tramos. Si los turnos cubren las 24 h el pool es un 24×7 y ninguna tarea que
lo use tiene
offHoursWait. Ese calendario-unión es el que entra en la intersección de R-CAL-4. - Capacidad en
tpor suma. La capacidad en el instantetes la suma de loscapacity_icuyoscalendar_iestán abiertos ent. Dos calendarios que se solapan suman —a diferencia de los intervalos de un mismo calendario, que se unen (R-CAL-2)—, porque cada tramo declara un grupo distinto de unidades del mismo rol:[{dia, 2}, {24x7, 1}]son 3 unidades de día y 1 de noche. - Durante el cierre del pool entero vale la capacidad del primer instante abierto posterior.
En un
tcerrado para todos los tramos la capacidad no es 0 sino la del siguientenextOpen(t). Es lo que conserva R-CAL-6 —la unidad concedida en tiempo cerrado sigue reservada, no desaparece bajo los pies del token que espera la apertura— y lo que deja el caso de un solo tramo bit a bit igual al de M3, es decir R-DEG-2 intacta. - Cerrar un tramo no interrumpe nada. Al bajar la capacidad, las tareas en curso siguen:
no hay apropiación (R-REC-7), así que
usedpuede quedar temporalmente por encima de la capacidad del instante hasta que terminen. Lo que la bajada sí impide es conceder nuevas unidades: hasta queusedvuelva a caer por debajo de la capacidad del instante no arranca nadie más. - Un solo evento de calendario en el heap. La subida de capacidad de un pool variable es el único evento de calendario que existe (despierta la cola); una bajada no planifica nada, porque no habilita a nadie. El resto de la disponibilidad se sigue resolviendo al planificar (R-CAL-3).
- Validación.
capacitypor intervalos ycalendardel pool son excluyentes (E-CAPACIDAD-Y-CALENDARIO, R16): el calendario ya va en cada tramo. Cadacalendar_idebe existir (R9,E-REF-DESCONOCIDAen el lint yE-CAL-DESCONOCIDOen el guardia decore/, igual que R-CAL-10). Cadacapacity_ies entero≥ 1y la lista no puede estar vacía (E-REC-CAPACIDAD).quantityde una tarea se valida contra el máximo de la semana, no contra la suma declarada: 3 + 1 unidades en turnos disjuntos nunca son 4 simultáneas. - Equivalencia con la forma numérica.
{ capacity: 3, calendar: "dia" }y{ capacity: [{ calendar: "dia", capacity: 3 }] }producen exactamente el mismo resultado; el entero es el caso de un solo tramo y no subeversion(§ 9 deSCENARIO_FORMAT.md). - Utilización y costo. Se reportan por rol, no por turno, y el denominador es el de
R-CAL-9:
busyTime / Σᵢ (capacityᵢ × openTimeᵢ)sobre[warmup, t_stop]. (prueba: LILA-164)
- Apertura por unión. El pool está abierto cuando lo está cualquiera de los
calendarios de sus tramos. Si los turnos cubren las 24 h el pool es un 24×7 y ninguna tarea que
lo use tiene
R-CAL-12 — Recurrencia mensual (#82). Además de
days, un intervalo puede repetirse por mes:monthDays: [n…]abre el díande cada mes (1…31), o contando desde el final (-1es el último día,-2el anterior). Un día que no existe en un mes —el 31 de un mes de 30 días, el 30 de febrero— se salta, nunca se mueve a otro día: la misma regla queBYMONTHDAYde iCalendar.monthWeekdays: [{ nth, day }]abre elnthdía de la semana del mes (1…5), o contando desde el final (-1es el último):{ nth: -1, day: "FRI" }es el último viernes. Un mes sin quinto lunes se salta{ nth: 5, day: "MON" }. (prueba: #82)R-CAL-13 — Recurrencia anual (#82).
dates: ["MM-DD"…]abre esa fecha cada año."02-29"es válida y solo abre en años bisiestos (regla gregoriana: 2100 no lo es); una fecha que no existe nunca, como"02-30", es error de esquema. Cada intervalo lleva exactamente un selector de días —days,monthDays,monthWeekdaysodates—; mezclarlos en un calendario se hace con varios intervalos, que se unen como cualquier otro (R-CAL-2). (prueba: #82)R-CAL-14 — Festivos (#82).
holidays: ["YYYY-MM-DD" | "MM-DD"…]cierra esos días civiles enteros (00:00–24:00 en el offset derun.start), digan lo que digan los intervalos:"YYYY-MM-DD"una vez,"MM-DD"cada año. Un festivo en un día que ya estaba cerrado no cambia nada. Una tarea que llegaría a un festivo se pausa y se reanuda en la apertura siguiente (R-CAL-5), y el tiempo cerrado cuenta comooffHoursWait(R-CAL-7) y no como tiempo disponible (R-CAL-9). El festivo de un tramo de capacidad baja solo ese tramo (R-CAL-11). Un calendario cuyas aperturas caen todas en sus propios festivos nunca abre:E-CAL-VACIO, que el lint reporta encalendars.<nombre>ycore/al compilar. Un calendario solo condaysy sin festivos es exactamente el calendario semanal de R-CAL-1: mismo camino de código, mismos bytes (R-DEG-2). También lo es uno cuyos únicos festivos son fechas puntuales anteriores arun.starto en días de la semana en que su patrón nunca abre: se descartan al compilar. Cualquier otro festivo toma el camino fechado, cuyos resultados pueden diferir de los semanales en el último ULP (unos 10⁻¹⁵ relativos) aunque el festivo caiga después del final de la corrida, porque el motor no conoce ese final al compilar un calendario. El texto del error de un calendario que solo abre en sus festivos escalendars.<nombre>: todas las aperturas del calendario caen en uno de sus festivos.Todo lo fechado es aritmética de días civiles, sinDateniIntl(R-DET-5): el resultado no depende de la zona horaria del proceso que lo corre. (prueba: #82)R-CAL-15 — Sin zona horaria propia ni DST (límite). Toda fecha y hora de un calendario se lee en el offset UTC fijo de
run.start, durante toda la corrida. Una corrida que cruza un cambio de horario conserva ese offset: el reloj civil del lugar se mueve una hora y el calendario no lo sigue. La zona horaria propia con DST (timezoneencalendarsyrun) sigue siendo un campo reservado (§15): hacerlo bien exige la base de datos de zonas IANA dentro decore/, que hoy no tiene ninguna. Alternativa: partir el escenario en el cambio, o correr a mano los intervalos afectados. (prueba: #82)
13. Costos
- R-COST-1 — Costo por elemento.
element.fixedCostTotal = elements[id].fixedCost × completados(solo los completados dentro de la ventana de estadísticas). Se carga enended. (prueba: LILA-036) - R-COST-2 — Costo por recurso.
resource.fixedCost = pool.fixedCost × usos, donde usos es la suma dequantitysobre las filas del log que ocupan el pool (una tarea que ocupa 2 unidades son 2 usos);resource.unitCost = pool.costPerHour × busyTime / 3600;resource.totalCost = fixedCost + unitCost.busyTimees tiempo abierto ocupado, sumado sobre las unidades ocupadas (una tarea que ocupaquantity = 2durante 1 h aporta 2 h). (prueba: LILA-036) - R-COST-3 — Costo de una fila del log.
row.cost = row.elementCost + row.resourceCost.elementCostvale el fijo del elemento solo en la primera fila de la instancia (orden del arrayresources) y 0 en las demás; para el sentinel es el fijo del elemento.resourceCost = pool.fixedCost × quantity + pool.costPerHour × quantity × busyTime_fila / 3600si el pool llegó a ocuparse, y 0 mientras siguió en cola. Así el fijo del elemento no se duplica en AND y cada componente es reconstruible. (decisión: ADR-025; prueba: LILA-037, LILA-036) - R-COST-4 — Costo por caso y total.
costo(caso) = Σ row.cost de sus filas;process.costPerCase= media sobre los casos completados de la ventana;process.totalCost = Σ row.costde todas las filas de la ventana, incluidas las parciales de casos en vuelo.element.fixedCostTotal = Σ row.elementCost, noΣ row.cost. De ahí sale la identidad verificabletotalCost = Σ fijo × usos + Σ porHora × horas ocupadas. (prueba: LILA-036) - R-COST-5 — Costos ausentes.
fixedCostycostPerHourausentes valen 0. Costos negativos los rechaza el esquema. (prueba: LILA-013) - R-COST-6 — Nada de costo por espera. En v1 esperar no cuesta: un recurso ocioso o una cola no generan costo. (prueba: LILA-036)
14. Degradación
El motor no tiene “niveles”: el escenario que no dice algo obtiene el comportamiento neutro. Es lo que hace que el mismo modelo sirva de nivel 1 a nivel 4 de Bizagi.
- R-DEG-1 — Sin
resourcesen el escenario ⇒ capacidad infinita. Ninguna tarea espera; todaresourceWaites 0; no hay tablas de recurso ni costos por hora. El resultado debe ser idéntico bit a bit al del mismo escenario corrido por el motor de M1. (prueba: LILA-039) - R-DEG-2 — Sin
calendars⇒ 24×7.isOpensiempre verdadero,offHoursWait = 0,availableTime= duración de la ventana. El resultado debe ser idéntico bit a bit al del mismo escenario corrido por el motor de M2. (prueba: LILA-043) - R-DEG-3 — Sin
processingTimeen una tarea ⇒ duración 0 más avisoW-TAREA-SIN-TIEMPOcitando el id. La tarea sigue ocupando recursos durante 0 segundos. Si el escenario no declara ningúnprocessingTime—validación de rutas, como el nivel 1 de Bizagi— el aviso es uno solo que lista los ids: un aviso por tarea ahí es ruido por diseño, no un olvido concreto. (prueba: LILA-042, LILA-198) - R-DEG-4 — Sin
probability⇒ §6 y §7. Sinwarmup⇒ 0. Sinreplications⇒ 1. Sinseed⇒seed = 1y avisoW-SIN-SEED: la corrida sigue siendo determinista y reproducible, pero el escenario no dice con qué semilla. Por esorun.seedno lleva default en el esquema: con default no se podría distinguir "no declarada" de "declarada en 1"; el 1 lo aplica el motor. (prueba: LILA-013, LILA-030, LILA-198) - R-DEG-5 — La degradación nunca inventa. Ningún default introduce esperas, costos ni variabilidad: todos son el elemento neutro de su operación. (prueba: LILA-039, LILA-043)
15. Campos reservados
El esquema del escenario los acepta (para que un archivo escrito hoy siga validando mañana) pero el motor los rechaza con error claro mientras no estén implementados (ADR-015, LILA-013).
R-RES-1 — Lista v1:
priority,preempt,batch(sección 6 del documento de estructura) mástimezoneencalendarsyrun(ADR-016, LILA-013).holidaysestuvo en esta lista hasta que #82 lo implementó (R-CAL-14).conditionses reservado solo en elementos que no son un flujo que sale de un XOR divergente: ahí está implementado (§6.1, ADR-028) y en el resto sigue dandoE-RESERVADO. (prueba: LILA-013, E22)R-RES-2 — Texto exacto del error.
campo reservado, no soportado por el simulador en v1 ({ruta}).{ruta}es la ruta JSON del campo desde la raíz del escenario resuelto, con el id del elemento o del pool. Ejemplos literales:campo reservado, no soportado por el simulador en v1 (elements.Task_TomarPedido.priority). campo reservado, no soportado por el simulador en v1 (resources.cajero.preempt). campo reservado, no soportado por el simulador en v1 (calendars.oficina.timezone).Código
E-RESERVADO. (prueba: LILA-013)R-RES-3 — Se rechaza en
resolveScenario, no ensimulate. El error aparece antes de correr y aborta; nunca se ignora en silencio. Un campo reservado presente pero con valornull(borrado porextends) no dispara el error. (prueba: LILA-013, LILA-014)R-RES-4 — Campos desconocidos. Una clave no reconocida por el esquema y que no está en la lista de reservados es error de esquema
E-CLAVE-DESCONOCIDA(el esquema es cerrado): protege contra erratas silenciosas del tipocapacty: 3. Única excepción:__proto__,constructoryprototype, que la fusión deextendsdescarta antes del esquema y por tanto en silencio (SCENARIO_FORMAT.md§ 6). (prueba: LILA-013, LILA-198; excepción: LILA-204)
16. Determinismo (ADR-017)
- R-DET-1 — Dos fuentes de orden, ambas explícitas. El orden de eventos es
(t, seq)(R-TOK-3) y el orden de colas es(enabled, seq)(R-REC-3). No hay ninguna estructura iterada por orden de inserción de unMapni por orden alfabético de ids en el camino de una decisión. (prueba: LILA-030, LILA-023) - R-DET-2 — Un stream por elemento. El PRNG (mulberry32/xoshiro) se siembra con
hash(seed, replication, elementId). Cada elemento consume solo su stream:interTriggerTimerdelstart,processingTimede la tarea o el timer, los sorteos de ramaje del gateway. (prueba: LILA-024) - R-DET-3 — Common random numbers. Consecuencia de R-DET-2: añadir un cajero (o cambiar una capacidad, un costo o un calendario) no cambia la secuencia de números de los elementos no tocados, así que un what-if se lee limpio. Es un requisito, no un efecto colateral. (prueba: LILA-024, LILA-038)
- R-DET-4 — Consumo estable de uniformes. Las distribuciones cerradas consumen un número fijo de
uniformes por muestra;
normalytruncatedNormalusan Box-Muller sin cachear el segundo valor (2 uniformes por muestra siempre). Las de rechazo (gamma,beta,poisson,binomial) consumen un número variable: eso solo desalinea el stream de ese elemento, nunca el de otro. (prueba: LILA-025, LILA-024) - R-DET-5 — Nunca
Math.randomniDate. Ni encore/, ni en la CLI, ni en el Worker. ElRunResultno contiene marcas de tiempo de reloj real. (prueba: LILA-032, LILA-030) - R-DET-6 — Garantía de bytes. Con la misma semilla y la misma entrada,
lila run --jsonproduce bytes idénticos dentro de una misma plataforma y arquitectura, en Node 22 y 24. La igualdad de bytes no cruza arquitecturas:Math.log,Math.exp,Math.cosyMath.powno están correctamente redondeadas y difieren en el último bit entre, por ejemplo,linux/x64ydarwin/arm64(soloMath.sqrtlo está, por el ISA). En nivel 1 y 2 esa deriva se cancela —processinges una diferencia de dos instantes desplazados por igual— y los goldens de M1 coinciden byte a byte en las dos; en nivel 3 no, porqueresourceWaitresta el instante de un caso al de otro: un ULP en el reloj de llegadas sobrevive hastaresourceWait.total. Por eso el golden de nivel 3 (test/golden/pedido-nivel3.seed-42.json) lleva los bytes de la plataforma del CI,linux/x64, se compara byte a byte cuandoprocess.env.CIestá definido, y con tolerancia relativa1e-9fuera de él. Entre navegadores la igualdad también es solo estadística. (prueba: LILA-030, LILA-043) - R-DET-7 — Distribuciones. Las 14 con parámetros nombrados y en segundos:
constant{value},uniform{min,max},triangular{min,mode,max},exponential{mean},normal{mean,sd}(truncada a≥ 0; avisoW-NORMAL-NEGATIVAsiP(x<0) > 1 %),truncatedNormal{mean,sd,min,max},lognormal{mean,sd}(media y desviación de la variable, no de su logaritmo),gamma{shape,scale},erlang{k,mean},weibull{shape,scale},beta{alpha,beta,min,max},poisson{mean},binomial{n,p},user{points:[{value,probability}]}(empírica discreta, probabilidades normalizadas con aviso si no suman 1). Toda muestra de duración se trunca a≥ 0. (prueba: LILA-025)
17. Catálogo de errores y avisos
Desde LILA-211 los textos de todos estos códigos viven en un catálogo por idioma
(packages/engine/src/messages/, con el subconjunto que necesita core/ en
packages/engine/src/core/messages/). El inglés es el idioma por defecto y el español una
traducción; los dos son normativos, cada uno para su idioma, y esta sección da los dos textos donde
los fija literalmente. El código (E-…, W-…) y el id de regla (R-…) no se traducen nunca.
Un test (packages/engine/test/messages.test.ts) mantiene en paso esta sección, el catálogo y el
código: los 61 códigos del catálogo son exactamente los que emite packages/engine/src, en y
es declaran las mismas entradas, y ningún literal "CÓDIGO: …" vive fuera del catálogo.
Errores (abortan; los de validación se devuelven en errors[], los de ejecución se lanzan; la CLI sale con 1):
| Código | Cuándo |
|---|---|
E-NOSOP |
elemento fuera del perfil (§3, texto exacto en R-NOSOP-1/2) |
E-PARSE-INCOMPLETO |
el lector XML descartó parte del modelo (§3, R-NOSOP-6) |
E-FLUJO-COLGANTE |
sequence flow sin origen o sin destino |
E-ID-DUPLICADO |
dos elementos con el mismo id |
E-GATEWAY-SIN-ARISTAS |
gateway sin entradas o sin salidas |
E-INALCANZABLE |
nodo no alcanzable desde ningún start |
E-LIMITE-SIN-AVANCE |
presupuesto de trabajo agotado para un caso en un instante durante la ejecución (R-TOK-7); aborta runReplication y simulate |
E-SIN-START / E-SIN-END |
proceso sin start, o sin end ni terminate |
E-ELEMENTO-DESCONOCIDO |
clave de elements que no existe en el IR |
E-CLAVE-DESCONOCIDA |
clave no reconocida por el esquema |
E-PROB-RANGO |
probability fuera de [0,1] |
E-PROB-EN-NODO |
probability declarada en un nodo |
E-XOR-SUMA-CERO |
XOR cuyas probabilidades suman 0 |
E-SUBPROC-PARAMETRO |
processingTime/resources/fixedCost en un subproceso embebido |
E-TIMER-RECURSO |
resources en un timer |
E-REC-DESCONOCIDO |
ref a un pool inexistente |
E-REC-DUPLICADO |
el mismo pool dos veces en una tarea |
E-REC-CANTIDAD |
quantity mayor que la capacity del pool (con capacidad por intervalos, mayor que el máximo de la semana, R-CAL-11) |
E-REC-CAPACIDAD |
capacity que no es entero ≥ 1, o lista de tramos vacía (R-REC-1, R-CAL-11) |
E-CAPACIDAD-Y-CALENDARIO |
capacity por intervalos y calendar del pool declarados a la vez (R-CAL-11, R16 de SCENARIO_FORMAT.md) |
E-REF-DESCONOCIDA |
calendar que no existe en calendars (lint de validateScenario), incluido el de cada tramo de capacity |
E-CAL-DESCONOCIDO |
lo mismo, cazado por el guardia de core/sim.ts (ver R-CAL-10 y R-CAL-11) |
E-CAMPO-NO-APLICA |
campo declarado en un elemento que no lo admite (R4, R5, R14) |
E-CAL-VACIO |
calendario sin intervalos, calendario cuyas aperturas caen todas en sus propios festivos (R-CAL-14, cita calendars.<nombre>), o intersección de calendarios vacía (cita la tarea) |
E-SIN-PARADA |
ni run.duration ni ningún triggerCount |
E-RESERVADO |
campo reservado (§15, texto exacto en R-RES-2) |
Textos exactos de los dos errores de R-CAL-11 (packages/engine/src/scenario.ts para el lint,
packages/engine/src/core/sim.ts para el guardia de core/, que no puede importar el validador),
en en y en es:
capacity by intervals and calendar are mutually exclusive; the calendar belongs in each slice (resources.<pool>.capacity).
E-CAPACIDAD-Y-CALENDARIO: capacity by intervals and calendar are mutually exclusive; the calendar belongs in each slice (<pool>).
E-REC-CAPACIDAD: <pool>: capacity must declare at least one slice.
E-REC-CAPACIDAD: <pool>: capacity must be an integer greater than or equal to 1.
la capacidad por intervalos y el calendario son excluyentes; el calendario va en cada tramo (resources.<pool>.capacity).
E-CAPACIDAD-Y-CALENDARIO: la capacidad por intervalos y el calendario son excluyentes; el calendario va en cada tramo (<pool>).
E-REC-CAPACIDAD: <pool>: la capacidad debe declarar al menos un tramo.
E-REC-CAPACIDAD: <pool>: la capacidad debe ser un entero mayor o igual que 1.
La primera línea es el message del problema que devuelve validateScenario (el code viaja
aparte, en su propio campo, como en el resto del lint); las tres siguientes son las excepciones de
core/, que sí llevan el código dentro del mensaje. El lint estático no emite E-REC-CAPACIDAD:
un capacity que no es entero ≥ 1 o una lista vacía los rechaza antes el esquema zod con su
mensaje genérico, y E-REC-CAPACIDAD es el guardia de core/ para quien construye el escenario a
mano. Es el único desajuste que queda; ver el párrafo final de esta sección.
E-LIMITE-SIN-AVANCE es un error de ejecución y puede ocurrir después de emitir callbacks; no es un error del validador estático.
Los demás errores de la tabla salen del lint (validateScenario) o del validador del IR, salvo
E-CLAVE-DESCONOCIDA, que lo caza el esquema cerrado antes del lint: el código va delante del
mensaje en la línea que imprime la CLI, y lo formatea schemaIssueLines (LILA-198).
Esos dos son todos los textos de E-REC-CAPACIDAD que emite packages/engine/src. Los dos
guardias internos de core/calendar.ts —compileCapacity sin tramos y nextCapacityRise con un
horario constante— son invariantes de la API de core/, inalcanzables desde un escenario (los caza
antes assertSupportedResourceScenario, y el segundo solo se llama con horario no constante), así
que lanzan sin código de catálogo. Antes lanzaban con E-REC-CAPACIDAD y sin pool: dos textos
que esta sección no recogía. La exhaustividad la fija un test (LILA-204).
Avisos (no abortan; viajan en RunResult.warnings[], siempre con el id del elemento implicado y,
cuando se repiten por caso, con un contador agregado en vez de una línea por ocurrencia):
W-MSGFLOW, W-COND, W-START-SIN-LLEGADAS, W-XOR-RESIDUO-COMPARTIDO, W-XOR-NORMALIZADA,
W-PROB-IGNORADA (probabilidad declarada en una salida de AND o gateway basado en eventos,
R-AND-1/R-EVG-3; la variante /event del catálogo conserva el código público y explica que
enruta la carrera entre eventos), W-OR-SIN-PROBABILIDAD, W-OR-VACIO, W-OR-JOIN-SIN-FORK, W-JOIN-BLOQUEADO
(un token que quedó esperando en un join, R-OR-8/R-AND-4, o en un gateway basado en eventos cuyas
ramas no tienen tiempo, R-EVG-6: cada caso tiene su propio texto),
W-TIMER-SIN-TIEMPO (un timer intermedio sin processingTime retarda 0 segundos, R-EVT-2; como
rama de un gateway basado en eventos nunca dispara, R-EVG-5),
W-BORDE-SIN-TIEMPO (temporizador de borde sin processingTime: nunca
dispara sobre su host, interrumpa o no, R-BND-8), W-TAREA-SIN-TIEMPO, W-NORMAL-NEGATIVA, W-USER-NORMALIZADA,
W-SIN-SEED, W-ELEMENTO-SIN-PARAMETROS, W-COND-INALCANZABLE (un flowTaken que no puede
preceder a su gateway, R-COND-4), W-OR-PROB-PARCIAL (un OR con probability en algunas salidas
pero no en todas, R-OR-2), W-UTILIZACION-MAYOR-UNO, W-PARSE,
W-XOR-DEFAULT-ROTO (bpmn:default que apunta a un flujo inexistente: se ignora la marca
isDefault, texto exacto en §3 R-NOSOP-6, junto con los tres textos de W-PARSE),
W-RECURSO-SATURADO, W-REPLICACIONES-SIN-OBSERVACIONES (R-ARR-9).
W-ELEMENTO-SIN-PARAMETROS (R3, #360) solo avisa cuando falta la entrada de una tarea,
temporizador o start, o la de un XOR/OR divergente sin probability ni conditions no vacías
en ninguna salida. Un probability: 0 cuenta como configuración. No avisa por finales,
terminate, AND, gateways de eventos ni gateways de paso/convergencia. Las reglas específicas
de probabilidades y los avisos de tiempo ausente siguen aplicándose; no cambia ningún cálculo.
W-RECURSO-SATURADO avisa de que un pool nunca alcanza estado estacionario: llega más trabajo
del que puede despachar y su cola crece con la duración de la corrida. Texto exacto, uno por pool
y por corrida:
W-RECURSO-SATURADO: <poolId>: the queue grows without settling (λ/μ·c ≈ X)
W-RECURSO-SATURADO: <poolId>: la cola crece sin estabilizarse (λ/μ·c ≈ X)
Cuando el aviso salta por la ocupación del pool y no por ρ (ver más abajo), el mismo código imprime la variante que habla de ocupación, para que el número que muestra no contradiga a la frase:
W-RECURSO-SATURADO: <poolId>: the queue grows without settling (utilization ≈ Y %)
W-RECURSO-SATURADO: <poolId>: la cola crece sin estabilizarse (ocupación ≈ Y %)
λ/μ·c es la notación de colas para la forma de la razón —llegadas sobre capacidad de
servicio— y no la carga teórica de la demanda que declara el escenario: las dos cantidades se leen
del log del horizonte simulado (la demanda atribuida al pool sobre las unidades que concedió, ver
más abajo), así que el número se mueve con la duración de la corrida y con el estrangulamiento que
el propio modelo produce. Las superficies que muestran el aviso lo dicen al lado, porque la
notación invita a la otra lectura (#357); el texto de arriba no cambia.
La señal es el pool lleno: sin unidades libres suficientes para conceder, o sea con menos
disponibles que la menor quantity con que alguna tarea lo pide —un pool de capacity 3 pedido de
dos en dos está lleno con dos unidades ocupadas, porque la tercera no la puede tomar nadie—. El
umbral es del pool: si otra tarea pide ese mismo pool de una en una manda esa quantity, y
entonces la tarea que pide de dos en dos puede estar bloqueada sin que el pool cuente como lleno
—el aviso deja de salir, nunca sale de más—. Una
instancia en cola se atribuye solo a los pools que estuvieron llenos —tiempo de reloj: quien
conserva la unidad durante el cierre del calendario (R-CAL-8) tampoco la tiene libre— durante al
menos la mitad de su espera con el pool abierto (su resourceWait, ya sin el tiempo de calendario
cerrado, R-REC-8); con calendario el umbral es por tanto más laxo que en una corrida 24×7. Por eso
un pool ocioso atado por AND, que hereda la cola entera de la instancia (R-REC-4), y una
alternativa OR libre, en cuya cola la instancia también está (R-REC-6), llegan al criterio con
demanda cero: el aviso no puede contradecir a resources[poolId].utilization. Una OR reparte
además su demanda entre las alternativas que sí estaban llenas, porque consume exactamente una.
Sobre esa demanda atribuida se exige ρ = demanda atribuida / unidades concedidas ≥ 1,1 o
resources[poolId].utilization ≥ 0,9 y, además, una de estas dos: que la cola atribuida media en la segunda mitad de [warmup, t_stop] supere la
capacidad efectiva del pool (su Σᵢ capacityᵢ × openTimeᵢ de R-CAL-9 dividida entre sus propias
horas abiertas, o sea unidades y no unidades diluidas por el calendario) y sea al menos 1,5 veces
la de la primera mitad; o que las instancias atribuidas que seguían en cola al cortar sean al
menos el 25 % de las unidades concedidas y la cola de la segunda mitad no sea menor que la de
la primera —un lote de llegadas simultáneas (R-ARR-1) deja mucho pendiente al corte con la cola
bajando, y eso es trabajo despachándose, no falta de estado estacionario—. Las colas se
promedian sobre el tiempo en que el pool estuvo lleno, que es el único en que la cola de un pool
significa algo y deja fuera el offHoursWait sin tener que restarlo aparte. Una cola estacionaria larga no avisa: M/M/1 con
ρ = 0,8 tiene Lq = 3,2 y sus dos mitades miden lo mismo.
La ocupación es la segunda puerta porque un pool que se autoestrangula frena su propia demanda atribuida: cuando la mayoría de sus tareas van detrás de otra tarea servida por él mismo, la cola de arriba es la que impide que el trabajo llegue, así que ρ se queda justo por debajo del umbral mientras el pool está lleno el 95 % del tiempo y la cola crece corrida tras corrida. La ocupación no distingue «justo al límite» de «el doble de lo que puede despachar», y por eso no decide sola: sigue teniendo que cumplirse el crecimiento o el pendiente de arriba. (prueba: #320)
Con varias replicaciones la decisión se toma una sola vez sobre la media de esas cantidades, no
réplica a réplica: la saturación es una propiedad del pool y de la corrida, y deduplicar avisos
dejaría que una sola réplica que cruza un umbral por azar decidiera por las treinta. X es el ρ de
esa media, con un decimal; Y es la ocupación media en porcentaje entero.
Es un aviso, no un error: no cambia ninguna métrica. Lo que señala es que resourceWait y
bottlenecks de ese pool son números que crecen con la duración de la corrida y no son comparables
con los de un pool estable. (prueba: LILA-191)
W-REPLICACIONES-SIN-OBSERVACIONES avisa de que las estadísticas de tiempo de un sujeto se
observaron solo en algunas replicaciones (R-ARR-8/R-ARR-9, #356): un task o un timer que no es
de borde y no completó ninguna instancia, el proceso sin ningún caso completado, o un desenlace de
process.byEndEvent al que no llegó ningún caso. Sus estadísticas promedian entonces solo las
replicaciones donde ocurrió. Sale una vez por sujeto y por corrida, solo cuando 0 < n < R, donde
R cuenta las replicaciones que incluye el top-level (también la parcial de una corrida cancelada)
y n las que observaron al sujeto; orden: nodos del IR, después el proceso, después los
desenlaces. m = R − n. Los tres conteos son enteros y se formatean antes de interpolarse, así que
los dos idiomas imprimen los mismos números. Textos exactos, en y es:
W-REPLICACIONES-SIN-OBSERVACIONES: <nodeId>: no instance completed in <m> of <R> replications; its time statistics average only the other <n>.
W-REPLICACIONES-SIN-OBSERVACIONES: process: no case completed in <m> of <R> replications; its cycle time, wait time, cost per case and service level average only the other <n>.
W-REPLICACIONES-SIN-OBSERVACIONES: <endId>: no case ended here in <m> of <R> replications; its time statistics average only the other <n>.
W-REPLICACIONES-SIN-OBSERVACIONES: <nodeId>: ninguna instancia se completó en <m> de <R> replicaciones; sus estadísticas de tiempo promedian solo las otras <n>.
W-REPLICACIONES-SIN-OBSERVACIONES: proceso: ningún caso se completó en <m> de <R> replicaciones; su tiempo de ciclo, su espera, su costo por caso y su nivel de servicio promedian solo las otras <n>.
W-REPLICACIONES-SIN-OBSERVACIONES: <endId>: ningún caso terminó aquí en <m> de <R> replicaciones; sus estadísticas de tiempo promedian solo las otras <n>.
W-START-SIN-LLEGADAS salta solo cuando el start no declara ni interTriggerTimer ni
triggerCount: con triggerCount a solas hay llegadas (todas en t = 0, R-ARR-1) y no hay aviso.
W-TAREA-SIN-TIEMPO es la excepción a la línea por elemento: cuando el escenario no declara
ningún processingTime, el aviso es uno solo y lista los ids de todas las tareas (R-DEG-3,
LILA-198).
Guardias internos
Once códigos del catálogo no están en las tablas de arriba porque no son defectos que un
modelo o un escenario puedan producir: son guardias de la API interna de core/, que solo saltan
cuando quien llama construye la entrada a mano y rompe un invariante. Señalan un error de
programación del consumidor, no algo que el usuario pueda corregir en su modelo, y por eso no
entran en la tabla de errores del usuario ni en la lista de avisos. Son:
| Código | Guardia |
|---|---|
E-REF-INEXISTENTE |
un nodo declara un flujo entrante o saliente que no está en el IR (core/ir.ts) |
E-REC-LIBERACION |
se libera una solicitud que no tiene asignación activa (core/resources.ts) |
E-REC-SOLICITUD-DUPLICADA |
se registra dos veces el mismo id de solicitud (core/resources.ts) |
E-REC-SIN-ASIGNACION |
una solicitud concedida se quedó sin pool (core/resources.ts) |
E-REC-ESTADO |
uso negativo en un pool (core/resources.ts) |
E-REPLICACIONES-INSUFICIENTES |
menos de 2 valores o replicaciones para el IC 95 % (core/replications.ts) |
E-REPLICACIONES-VACIAS |
no hay resultados que agregar (core/run.ts, meanRunResults) |
E-KPI-INCONSISTENTE |
dos replicaciones con distinto conjunto de KPI (core/replications.ts) |
E-KPI-NO-FINITO |
un KPI de una replicación no es finito (core/replications.ts) |
E-AGREGADO-NO-NUMERICO |
la estructura de métricas no es promediable (core/run.ts, meanShape) |
E-COMPARE-VACIO |
compare() sin ningún resultado (core/compare.ts) |
La lista vive también en el código, en INTERNAL_CODES (packages/engine/src/messages/index.ts),
y un test comprueba que esos once son exactamente los códigos del catálogo que esta sección no
documenta como públicos (LILA-211).
Los códigos de este catálogo son los que emite el código de hoy, con dos salvedades declaradas:
E-REC-CAPACIDAD, que el lint estático no emite (párrafo de arriba, LILA-164), y el par
E-REF-DESCONOCIDA / E-CAL-DESCONOCIDO, dos códigos para lo mismo según lo cace el lint o el
guardia de core/ (unificarlos toca core/; ver R-CAL-10).
18. Regla → ticket que la prueba
| Regla | Qué fija | Ticket que la prueba |
|---|---|---|
| R-DURA-1, R-DURA-2 | segundos; baseTimeUnit solo presentación |
LILA-013 |
| R-DURA-3 | dinero en run.currency |
LILA-036 |
| R-DURA-4 | el id BPMN es la única clave |
LILA-013, LILA-017 |
| R-DURA-5 | elements faltante = error, sobrante = aviso |
LILA-013, LILA-042 |
| R-DURA-6 | pureza de simulate |
LILA-029, LILA-032 |
| R-PERF-1 | toda variante de tarea → task |
LILA-018 |
| R-PERF-2 | XOR convergente = mezcla sin espera | LILA-026 |
| R-PERF-3 | message flow ignorado | LILA-021, LILA-163 |
| R-PERF-4 | conditionExpression ignorada |
LILA-021, LILA-163 |
| R-PERF-5 | varios starts | LILA-026 |
| R-NOSOP-1 … R-NOSOP-3 | texto exacto y catálogo de no soportados | LILA-021, LILA-163 |
| R-NOSOP-4, R-NOSOP-5 | no degradar; errores estructurales | LILA-021 |
| R-NOSOP-6 | avisos de bpmn-moddle: E-PARSE-INCOMPLETO / W-PARSE / W-XOR-DEFAULT-ROTO |
LILA-185, LILA-196 |
| R-PLAN-1, R-PLAN-2, R-PLAN-5 | subproceso embebido aplanado | LILA-019 |
| R-PLAN-3 | subproceso sin tiempo propio | LILA-019 (E-SUBPROC-PARAMETRO: LILA-198) |
| R-PLAN-4 | call activity = tarea con tiempo global | LILA-019 |
| R-TOK-1 | reloj en segundos desde run.start |
LILA-037 |
| R-TOK-2 | caso = conjunto de tokens | LILA-026 |
| R-TOK-3 | heap (t, seq) |
LILA-023, LILA-030 |
| R-TOK-4 | tránsito instantáneo y flows.count |
LILA-028 |
| R-TOK-5, R-TOK-6 | enabled/started/ended; identidad y lifecycle parcial |
LILA-033, LILA-037 |
| R-TOK-7 | presupuesto por caso sin avance temporal | #368 |
| R-XOR-1 … R-XOR-5, R-XOR-7 | XOR: equitativo, residuo al default, normalización, sorteo | LILA-026 (normalización y avisos: LILA-042) |
| R-XOR-6, R-XOR-8 | rango y ubicación de probability |
LILA-013, LILA-042, LILA-198 |
| R-COND-1 … R-COND-5 | ruteo condicionado al desenlace previo del caso (ADR-028) | E22 (packages/engine/test/conditions.test.ts) |
| R-OR-1 … R-OR-7 | OR fork/join, emparejamiento y loops | LILA-026 |
| R-OR-8 | tokens huérfanos al parar | LILA-026, LILA-028 |
| R-AND-1 … R-AND-6 | AND fork/join, contador (caso, join), loops |
LILA-026 |
| R-EVT-1 … R-EVT-2 | timer = retardo sin recurso | LILA-026 (E-TIMER-RECURSO: LILA-198) |
| R-EVT-3 | timer 24×7 salvo calendario propio | LILA-041 |
| R-EVT-4 | end consume token; caso termina con 0 tokens | LILA-026, LILA-028 |
| R-EVT-5, R-EVT-6 | terminate | LILA-026 |
| R-BND-1 … R-BND-9 | temporizador de borde interruptor | #81 |
| R-BND-10 … R-BND-14 | temporizador de borde no interruptor | #81 |
| R-EVG-1 … R-EVG-7 | gateway basado en eventos con ramas de tiempo y de mensaje | #81 (packages/engine/test/event-gateway.test.ts) |
| R-ARR-1 … R-ARR-5 | llegadas y parada (duration | triggerCount, lo primero) |
LILA-026 (triggerCount sin timer: LILA-186) |
| R-ARR-6 | llegadas con calendario | LILA-041 |
| R-ARR-7 | warmup | LILA-027 |
| R-ARR-8 | replicaciones e IC 95 %; n y KPI condicionales |
LILA-027, #356 |
| R-ARR-9, R-ARR-10 | agregado público y cancelación | LILA-029, #356 |
| R-REC-1 … R-REC-3 | pools, defaults y FIFO (enabled, seq) |
LILA-033 (defaults: LILA-013) |
| R-REC-4, R-REC-5 | AND atómico sin retención parcial; sin deadlock | LILA-034 |
| R-REC-6 | selección OR | LILA-035 |
| R-REC-7 | ocupación/liberación, sin apropiación | LILA-033 |
| R-REC-8 | resourceWait = started − enabled − offHoursWait |
LILA-036, LILA-041 |
| R-REC-9, R-REC-10 | pool duplicado; qué elementos admiten recursos | LILA-013, LILA-021 |
| R-REC-11 | filas planas por asignación y sentinel sin recurso | LILA-033, LILA-037 |
W-RECURSO-SATURADO |
pool sin estado estacionario: cola atribuida solo a los pools que estuvieron llenos, decidida sobre la media de las réplicas | LILA-191 |
| R-CAL-1, R-CAL-2, R-CAL-3 | patrón semanal, intervalos (to > from, to admite 24:00), primitivas y derivadas |
LILA-040 (24:00: LILA-041) |
| R-CAL-4 … R-CAL-8 | arranque en horario abierto, pausa/reanudación, offHoursWait |
LILA-041 (caso 17:30: LILA-040) |
| R-CAL-9 | utilización atribuible a la cohorte sobre horas disponibles; puede superar 1 sin apropiación (Σᵢ capacityᵢ × openTimeᵢ en [warmup, t_stop]) |
LILA-041, LILA-036, LILA-204 (denominador por tramos: LILA-164) |
| R-CAL-10 | matriz recurso × calendario y calendario por defecto | LILA-041, LILA-042 |
| R-CAL-11 | capacidad por turno dentro de un mismo pool (unión, suma, cierre y validación) | LILA-164 |
| R-CAL-12 … R-CAL-15 | recurrencia mensual y anual, festivos y el límite del offset fijo (sin DST) | #82 (LILA-082) |
| R-COST-1 … R-COST-4 | costos por elemento, recurso, fila y caso | LILA-036 (fila del log: LILA-037) |
| R-COST-5, R-COST-6 | costos ausentes = 0; esperar no cuesta | LILA-013, LILA-036 |
| R-DEG-1 | sin recursos ⇒ capacidad infinita, bit a bit igual a M1 | LILA-039 |
| R-DEG-2 | sin calendarios ⇒ 24×7, igual a M2 (bytes en linux/x64; ver R-DET-6) | LILA-043 (motor: LILA-041) |
| R-DEG-3 … R-DEG-5 | defaults neutros | LILA-042, LILA-013 (aviso agregado y W-SIN-SEED: LILA-198) |
| R-RES-1 … R-RES-4 | campos reservados y su texto de error | LILA-013 (null de extends: LILA-014; E-CLAVE-DESCONOCIDA: LILA-198) |
| R-DET-1 | orden explícito en eventos y colas | LILA-030, LILA-023 |
| R-DET-2, R-DET-3 | stream por elemento; common random numbers | LILA-024 (what-if: LILA-038) |
| R-DET-4, R-DET-7 | consumo de uniformes y las 14 distribuciones | LILA-025 |
| R-DET-5, R-DET-6 | sin Math.random/Date; bytes idénticos por plataforma y arquitectura |
LILA-032, LILA-030, LILA-043 |
| §2 a §14 en conjunto | paridad con los ejemplos oficiales de Bizagi (niveles 1–4, ±5 %) | LILA-044 |
| §11 + §12 | validación numérica contra M/M/1 y Erlang-C | LILA-050, LILA-011 |
19. Lo que este documento no decide
- Nombres de columna y unidades del
RunResult, del event log y del CSV:docs/RESULTS_FORMAT.md(LILA-005) ydocs/BIZAGI_PARITY.md(LILA-007). - Forma exacta del JSON del escenario, defaults del esquema y mapeo a BPSim 2.0 / qbp / Bizagi:
docs/SCENARIO_FORMAT.md(LILA-004). - Namespace
lila:y política de ids:docs/BPMN_EXTENSION.md(LILA-006).