Visión general
El Sistema de Eventos (Event System) es lo que eleva a ProDex de un simulador de flujo de procesos a un sistema capaz de modelar lógica operativa sofisticada. Se construye sobre cuatro primitivas:
- Temas (Topics) — mensajería pub/sub entre componentes
- Variables de estado (State Variables) — estado mutable en tiempo de ejecución que vive en el modelo
- Ganchos de evento (Event Hooks) — acciones que se disparan en los eventos del ciclo de vida del propio componente
- Oyentes de evento (Event Listeners) — acciones que se disparan cuando se emite un tema suscrito
Juntos habilitan patrones que no se pueden expresar solo con flujo basado en conexiones: producción por demanda (kanban), contrapresión, cambios de capacidad por turno y coordinación entre componentes. Un usuario que solo conoce fuentes, procesos y sumideros está aprovechando una fracción de lo que la plataforma puede modelar.
Cada componente de flujo en el Modelador (Modeler) expone tanto una sección de Event Hooks como una de Event Listeners en su panel de configuración: son objetos distintos de primera clase, no variantes de la misma cosa. Los Temas y las Variables de estado se gestionan en el modal Lookups, abierto desde el botón Lookups en la parte inferior del panel Library del Modelador. El modal tiene cuatro pestañas: Constants, Lookup Tables, State Variables y Topics.
Temas
Un Tema es un canal nombrado al que los componentes pueden publicar y suscribirse. Cuando un componente publica en un tema, cada componente que tenga un Event Listener suscrito a ese tema reacciona.
Los Temas permiten coordinación que no puede expresarse solo a través de conexiones de flujo: un componente puede disparar comportamiento en otro sin estar conectado directamente.
Crea y renombra Temas en la pestaña Topics del modal Lookups. Una vez que un Tema existe, los componentes publican en él mediante una acción emit y se suscriben añadiendo un Event Listener cuyo campo topic lo referencia.
Los Temas no llevan carga útil. Una acción emit toma solo un nombre de tema; un Event Listener se dispara solo con el nombre del tema sin datos de mensaje. Si un oyente downstream necesita información sobre por qué se emitió el tema, el patrón canónico es asignar una variable de estado justo antes del emit y que el oyente lea esa variable de estado.
Ejemplo — señal kanban: cuando un búfer (Buffer) downstream cae por debajo de un umbral, un event hook emite el tema replenish. Una Fuente (Source) upstream tiene un Event Listener suscrito a replenish que ejecuta una acción release, generando más material.
Variables de estado
Una Variable de estado es un valor mutable y nombrado que vive en el modelo (no en ninguna entidad individual). Las variables de estado persisten a través de las entidades: retienen su valor a medida que las entidades fluyen por el modelo, y pueden leerse desde cualquier expresión.
Usa variables de estado para cualquier cosa que represente el estado del sistema en lugar del estado de una sola entidad: turno actual, contadores de tiempo de actividad de máquinas, niveles de inventario, señales de demanda, promedios móviles.
Define Variables de estado en la pestaña State Variables del modal Lookups. Cada una tiene un nombre, un type (uno de number, text, boolean) y un initial_value obligatorio que coincida con el tipo declarado. Una vez definidas, léelas por nombre desde cualquier expresión y escríbelas con una acción assign en cualquier Event Hook, Event Listener o acción programada.
Variables de estado vs. atributos de entidad
- Atributo de entidad — pertenece a una entidad específica y se mueve con ella a través del flujo. Se define en el tipo de entidad.
- Variable de estado — pertenece al modelo y persiste independientemente de las entidades. Se define a nivel del modelo a través del modal Lookups.
Si quieres que el mismo valor sea visible para cada componente en cada momento, usa una variable de estado. Si quieres que el valor siga a una entidad específica mientras se mueve por el flujo, usa un atributo.
Event Hooks
Un Event Hook es una regla en un componente que dispara acciones cuando ocurre uno de los eventos del ciclo de vida de ese mismo componente. Los hooks viven en la sección Event Hooks del panel de configuración de cada componente de flujo: elige el evento de un desplegable, opcionalmente añade una condición y configura una o más acciones.
Eventos canónicos por componente
Cada evento solo es válido en tipos de componentes específicos y ejecuta sus expresiones en un contexto específico. Las etiquetas de la UI eliminan el prefijo on_ y usan espacios: entity created en la UI corresponde a on_entity_created en el esquema.
| Componente | Eventos | Contexto |
|---|
| Source | entity created | entidad-única (la entidad recién creada) |
| Combiner | entity consumed, batch assembled, entity created | entidad-única / multi-entidad (para batch assembled) / entidad-única (salida) |
| Separator | entity consumed, entity created, batch created | entidad-única / entidad-única (cada salida) / multi-entidad (todas las salidas) |
| Transformer | entity consumed, entity created | entidad-única |
| Buffer | entity entered, entity exited | entidad-única |
| Station | entity entered, entity exited | entidad-única |
| Process | process started, process completed | entidad-única |
| Router | entity routed | entidad-única |
| Resource | slot freed, slot working | sin-entidad |
| Sink | entity terminated | entidad-única |
Algunas cosas a notar:
entity entered / entity exited solo se disparan en Buffer y Station. Los Procesos no los emiten — usa process started / process completed. Los Routers no los emiten — usa entity routed.
slot freed / slot working son de Recursos (Resource), no de Estaciones. La denominación es inusual: slot working se dispara cuando un slot pasa de FREE a asignado.
batch created es un evento del Separator en contexto multi-entidad sobre las salidas. El evento paralelo del Combiner es batch assembled, también multi-entidad pero sobre las entradas.
- El filtro
entity_type solo es válido en on_entity_consumed del Combiner y on_entity_created del Separator. Otros event hooks se disparan para cada entidad independientemente del tipo.
El orden de ejecución de los hooks no está definido cuando múltiples hooks se disparan en el mismo evento. Si dos hooks en el mismo componente se suscriben al mismo evento, el orden en que se ejecutan no está especificado: no te bases en que los efectos secundarios de uno se vuelvan visibles para el otro dentro de un solo evento.
Tipos de acción
Cuando un hook se dispara, ejecuta una o más acciones. El desplegable Action ofrece (etiquetas de UI en código, nombres de esquema entre paréntesis):
assign — escribir en una variable de estado. Ejemplo: assign(CURRENT_SHIFT, "night") o assign(WIP_COUNT, WIP_COUNT + 1).
emit — publicar un mensaje en un tema. Ejemplo: emit("replenish").
pause — pausar un componente (deja de aceptar y procesar).
resume — reanudar un componente pausado.
release (release_entity) — liberar una entidad a demanda. Válido en Fuentes (libera una nueva entidad) y Búferes (libera la siguiente entidad encolada de un búfer en modo de pausa o retención).
set capacity (set_capacity) — cambiar la capacidad de un Recurso en tiempo de ejecución.
Restricciones de acción por componente
No todas las acciones son válidas en todos los componentes:
| Componente | assign | emit | release | pause | resume | set capacity |
|---|
| Source | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Buffer | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Resource | ✓ | ✓ | | | | ✓ |
| Station, Process, Router, Sink, Combiner, Separator, Transformer | ✓ | ✓ | | | | |
En resumen: set capacity solo es válido en Resource. release / pause / resume solo son válidos en Source y Buffer. assign y emit son válidos en todas partes.
Condiciones
Cada event hook tiene un campo condition opcional: una expresión booleana del DSL que se evalúa cada vez que se dispara el evento. Si es falsa, se omite la lista de acciones. La condición se ejecuta en el mismo contexto que las acciones (entidad-única para hooks vinculados a entidad, sin-entidad para hooks de slot de recurso, multi-entidad para hooks de lote).
Las condiciones viven a nivel de hook, no de acción: las acciones de un hook se ejecutan todas o se omiten todas.
Event Listeners
Un Event Listener es una regla en un componente que dispara acciones cuando se emite un tema suscrito, desacoplado de cualquier evento del ciclo de vida. Los oyentes viven en la sección Event Listeners del panel de configuración de cada componente de flujo: un hermano de los Event Hooks, con su propio botón Add Listener.
Cada oyente tiene:
topic (obligatorio) — el nombre de un tema declarado
condition (opcional) — expresión booleana del DSL
actions[] — los mismos seis tipos de acción que los Event Hooks, con las mismas restricciones por componente
Los Event Listeners siempre se ejecutan en contexto sin-entidad, independientemente del componente anfitrión. Un oyente en una Source no puede referenciar atributos de entidad, aunque el hook on_entity_created de una Source sí pueda. Los oyentes son reacciones a señales a nivel de modelo, no a entidades específicas: no hay ninguna entidad en alcance a menos que una acción la traiga explícitamente (por ejemplo, release).
Hooks vs. Listeners
Las dos primitivas de reacción se ven similares pero se comportan de manera diferente:
| Event Hook | Event Listener |
|---|
| Disparado por | Evento del ciclo de vida del propio componente | Emisión de tema |
| Contexto | Varía según el evento (sin-entidad / única / multi) | Siempre sin-entidad |
| Dónde vive | Arreglo event_hooks[] del componente | Arreglo event_listeners[] del componente |
| Suscripción | Implícita (el componente dispara su propio ciclo de vida) | Explícita (el campo topic referencia un tema declarado) |
| Cuándo usarlo | Reaccionar al comportamiento de este componente | Reaccionar a señales de coordinación a nivel de modelo |
Las suscripciones son estáticas
Las suscripciones se declaran en tiempo de construcción del modelo y nunca cambian en tiempo de ejecución. No hay acción subscribe ni unsubscribe. Si necesitas que diferentes oyentes estén activos bajo diferentes condiciones, contrólalo con el campo condition del oyente.
ModelNode y el Sistema de Eventos
Los Nodos de modelo (Model Nodes) pueden hospedar Event Listeners pero no Event Hooks de ciclo de vida: el esquema acepta un arreglo event_hooks[] en un ModelNode, pero no hay eventos canónicos de ciclo de vida definidos para ModelNodes, por lo que cualquier hook autorizado fallaría la validación. Los oyentes (basados en tema) funcionan completamente y son la herramienta correcta para coordinación a nivel de ModelNode.
Validación de loop-back
ProDex previene tres patrones específicos descontrolados:
- Bucles directos propios — el hook
on_entity_created de una Source no puede ejecutar una acción release; el hook on_entity_exited de un Buffer no puede ejecutar una acción release. Estas dos combinaciones específicas están prohibidas por el validador.
- Aciclicidad del grafo de temas — el grafo dirigido de emisiones de temas (donde existe una arista del tema A al tema B cuando un oyente de A emite B) debe ser acíclico. Los ciclos se capturan en la validación, antes de que se ejecute el simulador.
- Los ciclos del grafo de flujo están explícitamente permitidos. Enrutar entidades de vuelta a través de un Proceso upstream (bucles de retrabajo) es un modelo válido: el validador solo marca bucles dirigidos por eventos, no ciclos del grafo de conexiones.
Acciones programadas
Un Calendario (Schedule) también puede disparar acciones en momentos específicos de la simulación, independientemente de cualquier evento del ciclo de vida del componente. Las acciones programadas son más restringidas que las acciones de Event Hook:
- Solo
assign y emit son válidas como acciones programadas.
- Las acciones vinculadas a componente (
pause, resume, set capacity, release) no están disponibles en calendarios. Usa un emit programado a un tema, luego conecta un Event Listener que escuche ese tema y realice la acción vinculada al componente.
Esta restricción mantiene los calendarios declarativos: un calendario declara “en el tiempo T, el mundo está en estado X”, y los cambios de estado se propagan a través del sistema de eventos.
Patrones comunes
Kanban / Producción por demanda. Los componentes downstream señalan a las fuentes upstream cuando necesitan más material. Combina una variable de estado (WIP actual) con un Event Hook que emite a un tema cuando WIP cae por debajo de un umbral, más un Event Listener en la Source que escucha el tema y ejecuta una acción release.
Contrapresión. Cuando un búfer downstream está lleno, pausa o ralentiza las llegadas upstream. Un Event Hook observa los cambios de nivel del búfer y actualiza una variable de estado que lee la expresión de tasa de llegada de la Source.
Cambios de capacidad por turno. Los Recursos cambian de capacidad por hora del día. Un emit programado en un tema shift_start_night se dispara en el límite del turno; un Event Listener en el Recurso escucha ese tema y ejecuta set capacity con el valor de la noche.
Coordinación entre componentes. Dos componentes distantes que no están conectados por flujo aún pueden coordinarse a través de un tema compartido. Uno publica, el otro reacciona vía un Event Listener.
Cuándo recurrir al Sistema de Eventos
La mayoría de los modelos simples no necesitan eventos: una Source, unos cuantos Procesos, un Sumidero (Sink) y estás funcionando. Recurre a los eventos cuando:
- Necesitas comportamiento que abarca múltiples componentes sin flujo directo
- Necesitas que el modelo reaccione a reglas operativas (calendarios de turnos, cambios de prioridad, picos de demanda)
- Estás modelando sistemas lean o de demanda donde downstream pide a upstream
- Necesitas rastrear y reaccionar a métricas transversales (WIP móvil, utilización)
Si te encuentras duplicando lógica en muchas expresiones para coordinar comportamiento, esa es una señal para introducir una Variable de estado o un Tema.