Skip to main content

Visión general

El sistema de eventos es lo que eleva a ProDex de un simulador de flujo de procesos a un sistema capaz de modelar lógica operativa sofisticada. Está construido sobre cuatro primitivas:
  • Temas: mensajería pub/sub entre componentes
  • Variables de estado: estado mutable en tiempo de ejecución que vive en el modelo
  • Event Hooks: acciones que se disparan con los propios eventos de ciclo de vida de un componente
  • Event Listeners: acciones que se disparan cuando se emite un tema suscrito
Juntas habilitan patrones que no son expresables solo con flujo basado en conexiones: producción pull (kanban), contrapresión, cambios de capacidad guiados por turno, coordinación entre componentes. Un usuario que solo conoce fuentes, procesos y sumideros está usando una fracción de lo que la plataforma puede modelar. Si todavía te estás orientando en componentes y flujo, lee primero Resumen de la simulación: esta página cubre la capa de coordinación que se apoya sobre eso. Cada componente de flujo en el Modelador tiene una sección Event Hooks en sus propiedades, más una sección Event Listeners una vez que el modelo tiene al menos un tema: son objetos de primera clase distintos, no variantes de la misma cosa. Los Temas y las Variables de Estado aparecen en las secciones Variable y Temas de la Vista general del Modelador. Usa la fila Agregar Variable de Estado / Agregar Tema de una sección para crear una rápidamente, o el lápiz del encabezado (Ver y gestionar todo) para abrir el diálogo Búsquedas, que tiene cuatro pestañas: Constantes, Tablas de Búsqueda, Variables de Estado y Temas.

Temas

Un Tema es un canal con nombre al que los componentes pueden publicar y suscribirse. Cuando un componente publica en un tema, cada componente que tiene un Event Listener suscrito a ese tema reacciona. Los temas habilitan coordinación que no es expresable solo mediante conexiones de flujo: un componente puede disparar comportamiento en otro sin estar conectado directamente a él. Crea Temas desde la sección Temas de la Vista general o la pestaña Temas del diálogo Búsquedas. Una vez que existe un Tema, los componentes publican en él mediante una acción emit y se suscriben agregando un Event Listener cuyo Tema está definido a ese tema.
Los temas no llevan payload. Una acción emit toma solo un nombre de tema; un Event Listener se dispara con el nombre del tema únicamente, sin datos de mensaje. Si un listener aguas abajo necesita información sobre por qué se emitió el tema, el patrón canónico es definir una variable de estado justo antes del emit y hacer que el listener lea esa variable de estado.
Ejemplo, señal kanban: cuando un búfer aguas abajo cae por debajo de un umbral, un event hook emite el tema replenish. Una Fuente aguas arriba 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 con nombre que vive en el modelo (no en ninguna entidad individual). Las variables de estado persisten a través de las entidades: retienen su valor mientras 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áquina, niveles de inventario, señales de demanda, promedios móviles. Define Variables de Estado desde la sección Variable de la Vista general o la pestaña Variables de Estado del diálogo Búsquedas. Cada una tiene un Nombre (se exige SCREAMING_SNAKE_CASE — CURRENT_SHIFT, no currentShift), un Tipo (Número, Texto o Booleano), una Descripción opcional, y un Valor Inicial obligatorio que coincide con el tipo declarado. Una vez definidas, léelas por nombre desde cualquier expresión y escríbeles con una acción assign en cualquier Event Hook, Event Listener o acción programada.
En los datos de resultados, el valor inicial de cada variable de estado cae como una fila en t=0 en la tabla state_variable_activity, excepto las variables con alias hacia un submodelo mediante mapeos de variables de estado, que delegan al padre y no emiten fila propia.

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 de modelo.
Si quieres que el mismo valor sea visible para cada componente en todo 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 de ciclo de vida de ese propio componente. Los hooks viven en la sección Event Hooks de las propiedades de cada componente de flujo: haz clic en Agregar Hook, elige el Event, opcionalmente agrega una Condición (opcional), y configura una o más acciones con Agregar Acción.

Eventos canónicos por componente

Cada evento solo es válido en tipos de componente específicos y ejecuta sus expresiones en un contexto específico. En los datos de resultados, los mismos eventos aparecen con un prefijo on_ y guiones bajos: entity created en la UI es on_entity_created en la tabla event_lookup. Algunas cosas a notar:
  • entity entered / entity exited solo se disparan en Búfer y Estación. Los Procesos no los emiten: usa process started / process completed. Los Enrutadores no los emiten: usa entity routed.
  • Los eventos multi-entidad son donde viven las agregaciones. buffer contents changed en un Búfer, station contents changed en una Estación, batch assembled en un Combinador y batch created en un Separador corren todos en contexto multi-entidad: las condiciones y asignaciones ahí usan funciones de agregación (SUM, MEAN, COUNT, MAX, MIN, ANY, ALL, MODE, N_UNIQUE) sobre las entidades en alcance, por ejemplo MEAN(weight) para el promedio a través del lote. Una revisión de umbral de búfer es COUNT() > 10 en buffer contents changed, no una condición por entidad.
  • slot freed / slot working están en Recursos, no en Estaciones, y corren en contexto sin entidad. El nombre es inusual: slot working se dispara cuando un slot pasa de LIBRE a asignado.
  • Los valores por entidad en contexto multi-entidad (ENTITY_TYPE, ENTITY_AGE) solo son accesibles dentro del cuerpo de una agregación: MAX(ENTITY_AGE) funciona, ENTITY_AGE sin envolver no. En contexto de una sola entidad, ambos están disponibles directamente (ENTITY_AGE es SIM_TIME menos el tiempo de creación de la entidad).
  • Entity Type Filter (opcional) solo es válido en entity consumed del Combinador y entity created del Separador. Los demás event hooks se disparan para cada entidad sin importar el tipo.
Dentro de un mismo hook, las acciones corren de forma secuencial; entre hooks, el orden no está especificado. La lista actions[] de un solo hook o listener se ejecuta en orden, así que un assign seguido de un emit en la misma regla es seguro. Pero si dos hooks separados se suscriben al mismo evento, el orden en que corren no está especificado: no confíes en que los efectos secundarios de uno sean visibles para el otro.

Tipos de acción

Cuando un hook se dispara, ejecuta una o más acciones. El desplegable Acción ofrece lo siguiente (cuando difiere, el nombre entre paréntesis es cómo aparece la acción en los datos de resultados):
  • assign: escribe en una variable de estado. Ejemplo: assign(CURRENT_SHIFT, "night") o assign(WIP_COUNT, WIP_COUNT + 1).
  • assign attribute (assign_attribute): escribe en un atributo de la entidad para la que se disparó el hook, que conserva su id. El atributo debe estar declarado en el tipo de entidad y el valor debe coincidir con su tipo; los números se recortan a los límites declarados. Ejemplo: assign_attribute(visits, visits + 1) o assign_attribute(due_time, SIM_TIME + 43200). Válida solo en eventos de una sola entidad donde la entidad continúa aguas abajo: created de Fuente, entered / exited de Búfer y Estación, started / completed de Proceso, routed de Enrutador. No es válida en listeners. exited se dispara después de que el siguiente componente aceptó la entidad, así que los hooks de ese componente leen el valor anterior, y una escritura posterior a que un Sumidero terminó la entidad se descarta.
  • emit: publica un mensaje en un tema. Ejemplo: emit("replenish").
  • pause: en una Fuente o un Búfer, detiene la salida automática. En una Estación o un Proceso, detiene los trabajos en curso conservando sus slots de recursos y el trabajo pendiente; los trabajos en cola esperan. Pausar una Estación también pausa sus procesos. Pídele a Dexter que configure motivos de pausa con nombre cuando puedan coincidir varias causas.
  • resume: elimina un motivo de pausa. Una Estación o un Proceso solo reanuda el trabajo cuando se han eliminado todos los motivos de pausa del proceso y de su Estación y sus slots de recursos están disponibles.
  • release (release_entity): libera entidades a demanda. Válida en Fuentes (crea entidades nuevas) y Búferes (libera entidades encoladas). En un Búfer solo funciona cuando el Modo de Liberación del Búfer es Retención (liberación manual), donde es la única forma en que salen las entidades; en un Búfer definido como Push (liberación automática), no hace nada de forma silenciosa, porque las entidades ya fluyen por su cuenta, y se rechaza un Búfer Push que también usa pause o resume. Cantidad a liberar acepta un entero o una expresión DSL, evaluada en contexto sin entidad en una Fuente, y en contexto multi-entidad sobre el contenido actual en un Búfer (aplican las agregaciones). El resultado se trunca a un entero no negativo; cero o menos no libera nada. Un Filtro de expresión (opcional) opcional (booleano DSL por entidad) selecciona cuáles entidades encoladas califican: es exclusivo de Búfer y el validador lo rechaza en una Fuente.
  • set capacity (set_capacity): cambia la capacidad de un Recurso en tiempo de ejecución.
Por defecto, las disminuciones de capacidad dejan terminar los trabajos. Con la regla predeterminada ignore, un trabajo que ya usa un slot eliminado termina primero. Pídele a Dexter la regla preempt para interrumpir de inmediato los trabajos afectados; conservan sus slots y reanudan el trabajo pendiente cuando vuelve la capacidad. La misma elección de regla se aplica a las ventanas de capacidad del calendario de turnos.

Restricciones de acción por componente

No todas las acciones son válidas en todos los componentes: set capacity solo es válido en Recurso. release solo es válido en Fuente y Búfer; pause y resume también admiten Estación y Proceso. assign attribute necesita una entidad que continúe aguas abajo, y solo en los eventos listados arriba. assign y emit son válidos en todas partes.

Condiciones

Cada event hook tiene un campo Condición (opcional), una expresión booleana del DSL evaluada cada vez que se dispara el evento. Si es falsa, se omite la lista de acciones. La condición corre en el mismo contexto que las acciones (una sola entidad para hooks ligados a una entidad, sin entidad para hooks de slot de recurso, multi-entidad para hooks de lote y de cambio de contenido). Las condiciones viven a nivel de hook, no de acción: las acciones de un hook se ejecutan todas o se omiten todas.

SELF

Dentro de cualquier hook o listener, SELF resuelve al id del componente anfitrión en tiempo de ejecución. Eso hace que las reglas se puedan copiar y pegar entre componentes: BUFFER_LEVEL(SELF), STATION_WIP(SELF), RESOURCE_AVAILABLE(SELF) siguen todos a la regla dondequiera que esté conectada. SELF no está disponible en acciones programadas (no hay contexto de componente ahí) ni en los campos de nivel superior de un componente.

Event Listeners

Un Event Listener es una regla en un componente que dispara acciones cuando se emite un tema suscrito, desacoplado de cualquier evento de ciclo de vida. Los listeners viven en la sección Event Listeners de las propiedades de un componente, hermana de Event Hooks, con su propio botón Agregar Listener. La sección aparece una vez que el modelo tiene al menos un tema. Cada listener tiene:
  • Tema (obligatorio): un tema declarado
  • Condición (opcional): expresión booleana del DSL
  • Acciones: los mismos tipos de acción que los Event Hooks excepto assign attribute (un tema se dispara sin ninguna entidad en alcance), con las mismas restricciones por componente
Los Event Listeners siempre corren en contexto sin entidad, sin importar el componente anfitrión. Un listener en una Fuente no puede referenciar atributos de entidad, aunque el hook on_entity_created de una Fuente sí puede. Los listeners 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 parecen similares pero se comportan de forma distinta:

Las suscripciones son estáticas

Las suscripciones se declaran al construir el modelo y nunca cambian en tiempo de ejecución. No hay una acción subscribe o unsubscribe. Si necesitas listeners distintos activos bajo condiciones distintas, condiciónalos con la condición del listener.

El Nodo de modelo y el sistema de eventos

Los Nodos de modelo pueden alojar Event Listeners pero no admiten Event Hooks: un Nodo de modelo no tiene eventos de ciclo de vida, así que sus propiedades no muestran sección de Event Hooks y la validación rechaza cualquier hook en uno. Los Listeners (basados en temas) funcionan por completo y son la herramienta correcta para la coordinación a nivel de Nodo de modelo. Los temas y las variables de estado se conectan a través del límite del Nodo de modelo mediante mapeos: una emisión conectada aparece como dos filas en el conjunto de datos de emisión (origen + entrega).

Evitar bucles de eventos

Las reglas basadas en eventos pueden retroalimentarse a sí mismas. Ten en cuenta tres pautas:
  1. No dejes que el propio ciclo de vida de un componente dispare su propio release. Una Fuente cuyo hook entity created libera otra entidad, o un Búfer cuyo hook entity exited libera la siguiente entidad sin condición, es un bucle autoamplificado: haz que las liberaciones se disparen desde señales aguas abajo (un tema kanban) en su lugar.
  2. Mantén el grafo de temas como un DAG en tu cabeza. Si un listener en el tema A emite el tema B, y un listener en B emite A, cada emisión se propaga para siempre. Encadena los temas en una sola dirección y condiciona las rutas reentrantes con una condition sobre una variable de estado.
  3. Los ciclos en el grafo de flujo están bien. Enrutar entidades de vuelta a través de un Proceso aguas arriba (bucles de reproceso) es un modelo perfectamente válido: el riesgo son los bucles de eventos, no los bucles de conexión.

Acciones programadas

Una Programación también puede disparar acciones en momentos de simulación específicos. Las programaciones son un mecanismo separado de los Event Hooks: una programación dispara sus propias acciones en momentos declarados y no dispara ningún hook de ciclo de vida de componente: el tema es el puente entre los dos. Las acciones programadas están más restringidas que las acciones de Event Hook:
  • Solo assign y emit son válidas como acciones programadas.
  • Las acciones ligadas a un componente (pause, resume, set capacity, release) no están disponibles en programaciones. Usa un emit programado a un tema, y luego conecta un Event Listener que escuche ese tema y realice la acción ligada al componente.
Esta restricción mantiene las programaciones declarativas: una programación declara “en el momento T, el mundo está en el estado X”, y los cambios de estado se propagan a través del sistema de eventos.

Crear eventos con Dexter

Describe el comportamiento que quieres, “cuando el búfer de kitting caiga por debajo de 20, libera 5 más desde la Fuente”, y Dexter conecta los temas, variables de estado, hooks y listeners con los mismos eventos y acciones que describe esta página. Todo lo que construye aparece en las secciones Event Hooks y Event Listeners de los componentes, donde puedes revisarlo y editarlo.

El sistema de eventos en los datos de resultados

Tres tablas de resultados capturan lo que realmente hizo el sistema de eventos durante una corrida:
  • event_lookup: una tabla de dimensión con una fila por acción de cada hook y listener, escrita en la configuración inicial (nada se agrega durante la corrida). Las columnas incluyen component_id, config_type ("hook" / "listener"), event_or_topic, entity_type_id, condition, action_index, action_type, y las columnas tipadas variable_name / value_expr / topic / capacity_expr / attribute_name (nulas para las acciones pause/resume/release_entity).
  • topic_activity: una fila por cada emit ejecutado. trigger_source distingue schedule / hook / listener / bridge; trigger_component_id es nulo en las filas de programación y de puente; bridged_from lleva el tema del lado de origen (con su prefijo jerárquico) en las filas de puente. Las emisiones disparadas por programación llevan marcas de tiempo en segundos completos; las emisiones de hook y listener conservan precisión de subsegundo.
  • state_variable_activity: una fila por cada assign, con variable_name, value_type, y las columnas tipadas value_number / value_text / value_boolean. trigger_component_id es nulo en las asignaciones programadas y en las filas de valor inicial en t=0.
Si un cambio de turno o una señal kanban “no pareció dispararse”, estas tablas son donde revisar.

Patrones comunes

Producción kanban / pull. Los componentes aguas abajo le señalan a las fuentes aguas arriba cuándo necesitan más material. Combina una variable de estado (WIP actual) con un Event Hook que emite a un tema cuando el WIP cae por debajo de un umbral, más un Event Listener en la Fuente que escucha el tema y ejecuta una acción release. Contrapresión. Cuando un búfer aguas abajo está lleno, pausa o ralentiza las llegadas aguas arriba. 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 Fuente. Cambios de capacidad guiados por turno. Los recursos cambian de capacidad según la 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 nocturno. Coordinación entre componentes. Dos componentes distantes que no están conectados por flujo pueden seguir coordinándose a través de un tema compartido. Uno publica, el otro reacciona mediante un Event Listener.

Cuándo recurrir al sistema de eventos

La mayoría de los modelos simples no necesitan eventos: una Fuente, algunos Procesos, un Sumidero, y ya estás corriendo. Recurre a los eventos cuando:
  • Necesitas comportamiento que abarque varios componentes sin flujo directo
  • Necesitas que el modelo reaccione a reglas operativas (calendarios de turno, cambios de prioridad, aumentos de demanda)
  • Estás modelando sistemas lean o pull donde lo de aguas abajo jala de lo de aguas arriba
  • 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.