Patrones de Diseño de Flujos

Documentation

Patrones de Diseño de Flujos

Saber qué hace un nodo o un tipo de borde no es lo mismo que saber cuál usar. Esta guía reúne decisiones recurrentes que surgen al traducir un proceso real a un diagrama de flujo de dotBEP, el tipo de juicio que es fácil equivocar en un primer intento y que conviene detectar antes de construir el flujo, no después.

Si aún no has leído Ejecución del BEP, empieza por ahí. Ese doc define qué son los eventos, efectos, automatizaciones y triggers. Esta guía asume que ya conoces el vocabulario y se enfoca en cuándo usar cada uno.


Patrón 1: Los cambios de estado externos que deben mantenerse sincronizados requieren una automatización, no un efecto

Si una transición necesita actualizar el estado de un sistema externo (marcar una incidencia como “en progreso” en ACC, actualizar el estado de una página en Notion, publicar un comentario en un ticket), no lo modeles como un efecto.

Los efectos son de disparo y olvido (fire-and-forget): el motor los dispara y no espera ni reacciona al resultado. Si la llamada falla, la instancia de flujo sigue avanzando como si hubiera tenido éxito, y el sistema externo queda silenciosamente desincronizado del registro de dotBEP. Nadie es notificado.

Modélalo como un nodo automation en su lugar. El flujo espera el resultado real antes de continuar, y una llamada fallida nunca queda silenciosamente ignorada como sí puede pasar con un efecto fallido.

No agregues un nodo decision después de la automatización solo para verificar si la llamada tuvo éxito técnicamente. Eso solía ser la única forma de hacer visible una falla, pero ya no lo es: la falla de una automatización (el handler lanzó un error, o no había handler registrado para ella) es rastreada de forma nativa por el motor. Un intento fallido queda registrado en el historial de la instancia, y la instancia queda detenida en el nodo automation, consultable como automationPending (con un contador de intentos fallidos y el último error). No se necesita ninguna bifurcación en el diagrama para hacer eso visible.

mark_status_in_acc   (automation: actualiza el estado de la incidencia en ACC)
  │ [al tener éxito, emite "status-updated" y continúa directo al siguiente nodo]

(siguiente nodo)

Un nodo automation tiene exactamente un borde saliente, y no tiene que apuntar a un decision. Apúntalo directo a lo que sigue en el proceso. Ver el Patrón 2 para el único caso en que un decision después de una automatización sí está justificado.

Reserva los efectos para acciones donde la desactualización es aceptable si fallan: notificaciones, registro de logs, cualquier cosa donde “esto podría no haberse enviado” no comprometa la integridad del proceso. Un mensaje de Slack avisándole a un revisor que su paso está listo es un buen efecto. Actualizar el estado autoritativo de una incidencia rastreada en el sistema del que proviene no lo es.


Patrón 2: Un decision solo bifurca sobre lo que produjo el nodo inmediatamente anterior

Un nodo decision bifurca según el payload emitido por el nodo justo anterior, nada más. Ese payload solo puede llevar lo que ese nodo específico (su actor o su handler) realmente produjo al completarse. Antes de agregar un decision después de cualquier nodo, verifica que lo que pregunta el guard esté genuinamente presente en ese payload, no asumido, inferido, o tomado prestado de alguien o algo que todavía no ha actuado. Esto aparece en dos formas comunes, y fáciles de equivocar.

Output de una automatización. El borde saliente único de una automatización no necesita un decision detrás (ver Patrón 1). Agrega uno solo cuando el output de la automatización tenga una necesidad de bifurcación real para el proceso, una que exista independientemente de si la llamada tuvo éxito técnicamente, por ejemplo una automatización de detección de choques que devuelve un nivel de severidad y el proceso trata genuinamente distinto los choques de alta y baja severidad más adelante. No uses un decision aquí para verificar éxito o fallo. Eso no es una bifurcación de negocio, es el manejo nativo de fallas del motor descrito en el Patrón 1, y ocurre haya o no un decision después de la automatización.

inspect_clash          (automation: verificación geométrica, devuelve { severity: "high" | "low" })
  │ [emite "clash-inspected" con { severity }]

decision "¿Qué tan severo es el choque?"
  ├─ high → escalate_to_director   (process: requiere involucrar directamente al Director de Obra)
  └─ low  → resolve_clash          (process: el Coordinador BIM lo maneja de forma rutinaria)

Si alguna vez tienes duda sobre si una bifurcación después de una automatización pertenece aquí, pregúntate si esa bifurcación seguiría teniendo sentido si la automatización nunca pudiera fallar. Si la respuesta es no, no es una bifurcación de negocio, es manejo de fallas, y no pertenece al diagrama.

Actor de proceso. La misma regla aplica cuando el nodo antes del decision es un process: el actor que alimenta el decision debe ser el actor cuyo juicio el guard está evaluando realmente. Esto se rompe cuando un nodo process realizado por un actor se conecta directamente a un decision que en realidad trata sobre el juicio de un actor distinto, por ejemplo “Contratista de Obra: Corregir el modelo y publicarlo en Compartido” alimentando directamente “¿El Coordinador BIM aprueba la corrección?”. El Contratista, al completar su corrección, no puede de ninguna forma emitir si el Coordinador BIM la aprueba. Esa aprobación todavía no ha ocurrido, y le pertenece a una persona que no ha tenido su propio nodo en el diagrama.

Incorrecto:
correct_model_and_publish (process, actor: Contratista de Obra)

decision "¿El Coordinador BIM aprueba la corrección?"   ← nada anterior produjo esto
  ├─ sí → ...
  └─ no → ...

Correcto:
correct_model_and_publish (process, actor: Contratista de Obra)

review_correction (process, actor: Coordinador BIM, payload: { approved: yes | no })

decision "¿El Coordinador BIM aprueba la corrección?"   ← lee el output propio de review_correction
  ├─ sí → ...
  └─ no → ...

La solución es insertar un nodo process intermedio para el actor cuyo juicio realmente necesita el decision, uno donde ese actor realice su propio paso de revisión o aprobación y su payload sea lo que lee el decision. Cada vez que el guard de un decision en realidad pregunte “qué decidió la persona X”, esa persona X necesita su propio nodo produciendo ese payload inmediatamente antes del decision, no un decision atornillado al final de la acción de otra persona que no tiene relación.


Patrón 3: Ningún borde saliente de un decision alimentado por una automatización puede apuntar de vuelta a esa automatización

Esto solo aplica cuando el Patrón 2 coloca un decision justo después de una automatización. No conectes ninguno de los bordes salientes de ese decision de vuelta a la automatización que lo alimenta, en ninguna rama. La regla es estructural: si el borde entrante de un nodo decision viene de un nodo automation, ninguno de sus bordes salientes puede apuntar a ese mismo nodo automation. Está roto de una forma que no es obvia con solo mirar el diagrama.

El motor ejecuta automáticamente un nodo automation en el momento en que una transición aterriza en él, decision incluido. Un borde directo de ese decision de vuelta a la automatización, en cualquier rama, crea un ciclo síncrono y ajustado sin backoff, acotado solo por un límite de seguridad interno sobre pasos de automatización consecutivos. Si el ciclo supera ese límite, el motor simplemente deja de avanzar la instancia, varada en el nodo automation, sin que nadie sea notificado y sin ningún evento que una persona pudiera razonablemente descubrir y emitir para recuperarla.

Esta es una preocupación más acotada de lo que solía ser: dado que la falla de una automatización ya no necesita una rama de decision para ser visible (Patrón 1), la rama más tentadora para que un modelador vuelva a la automatización, “¿falló? reintentar”, ya no existe por defecto. Pero si una decisión de negocio genuina (Patrón 2) tiene una rama que conceptualmente significa “ir a intentar esa automatización de nuevo”, el mismo riesgo estructural sigue aplicando, y la solución es la misma: enrutarla a un nodo process en su lugar, uno donde una persona vuelva a disparar la automatización como efecto colateral de una acción que ya entiende, no de vuelta al nodo automation directamente.


Patrón 4: Reutiliza efectos, actions y automatizaciones entre flujos en lugar de duplicarlos

Si el mismo paso del mundo real aparece en más de un flujo, modélalo una sola vez y reutiliza ese nodo, no una copia por flujo. Esto aplica por igual a efectos, process actions y automatizaciones. Un caso común es una acción de revisión o aprobación realizada por el mismo rol en más de un proceso, por ejemplo una acción de visto bueno de “interventoría” que aparece tanto en un flujo de orden de cambio como en un flujo de no conformidad. Si es la misma acción (mismo actor, misma intención, mismo payload que necesita), declárala una vez y apunta ambos flujos a ella.

Solo duplica cuando las dos ocurrencias son genuinamente acciones distintas que comparten nombre, no la misma acción apareciendo dos veces. La prueba está en el payload y la intención, no en la etiqueta: si la “revisión de interventoría” de un flujo necesita datos de entrada distintos que la del otro (campos diferentes, una decisión diferente que está tomando), son dos acciones distintas que comparten un nombre legible, y forzarlas en un solo nodo pierde información o fuerza una unión incómoda de payloads. Si piden los mismos datos y significan lo mismo, son una sola acción alcanzada desde dos lugares.

Este es el mismo principio ya cubierto para automatizaciones alcanzadas desde más de un origen: por ejemplo, dos ramas de rechazo distintas que necesitan marcar una incidencia de ACC como reabierta comparten un solo nodo automation, no uno duplicado por origen. Eso antes requería duplicar la automatización (y, previamente, el decision justo después) una vez por origen, porque la rama de reintento ante fallo de un nodo compartido no tenía forma de enrutar de vuelta al llamador correcto. Esa preocupación desapareció ahora que la falla se maneja de forma nativa por el motor en lugar de una rama del diagrama (Patrón 1): el único borde saliente de un nodo automation compartido solo tiene que expresar “dónde continúa el camino feliz”, y eso es muy a menudo el mismo lugar sin importar qué origen la haya disparado.

decision_a (rechazado) ──┐
                          ├──→ mark_reopened (automation, compartido)  →  (siguiente nodo único)
decision_b (rechazado) ──┘

flow_orden_de_cambio ──────┐
                            ├──→ interventoria_review (process, compartido)  →  (cada flujo continúa a su propio siguiente nodo)
flow_no_conformidad ───────┘

Solo duplica el nodo si el destino del camino feliz genuinamente difiere por origen de una forma que no se puede modelar con un siguiente paso compartido, o si aplica el Patrón 2 y la bifurcación de negocio después de una automatización necesita saber qué origen la disparó (un guard solo puede ver el output propio de la automatización, no qué borde llevó a ella).


Patrón 5: Después de cambiar un flujo, revisa si quedaron actions, automatizaciones o efectos huérfanos y elimínalos

Editar un flujo (quitar un nodo, recablear un borde, reemplazar una action por otra) puede dejar una action, automatización o efecto que ya ningún nodo del diagrama referencia. Declarar uno y reutilizarlo (Patrón 4) solo vale la pena si lo inverso también ocurre: una vez que nada en ningún flujo apunta a él, elimínalo en lugar de dejarlo declarado.

Un huérfano que queda ahí no es un no-op neutral. Sigue apareciendo en listados y selectores como si todavía fuera parte activa de algún proceso, puede reutilizarse por error por alguien que asume que sigue conectado a algo, y su presencia hace más difícil distinguir, meses después, qué actions declaradas realmente importan versus cuáles son peso muerto que nadie eliminó.

Convierte esto en un paso explícito final de cualquier edición de flujo, no en algo pendiente para después: después de quitar o recablear un nodo, revisa si la action, automatización o efecto que usaba sigue siendo referenciado por algún otro nodo en algún flujo. Si ya nada lo referencia, elimina la declaración. No te saltes esto por “podría reutilizarse después”; un paso genuinamente reutilizable vale más si se vuelve a declarar cuando la necesidad realmente surja que si se mantiene sin uso por si acaso.


Patrón 6: Consolida una cadena de pasos de proceso del mismo actor en una sola action y deja que su descripción lleve el detalle

Si un flujo tiene una secuencia lineal de nodos process realizados por el mismo actor, uno justo después del otro sin bifurcación, sin otro actor de por medio, y sin espera intermedia, considera modelarlo como un solo nodo process en lugar de uno por sub-paso. Por ejemplo, “hacer los cambios en el modelo”, “subirlo al CDE” y “compartirlo” realizados uno tras otro por el mismo modelador es una sola unidad de trabajo del mundo real contada a través de tres nodos del diagrama. Consolídalo en una sola action, por ejemplo “actualizar y compartir el modelo en el CDE”, y deja que la descripción de la action detalle los sub-pasos en prosa en lugar de que el diagrama los detalle como nodos separados.

El diagrama no es el lugar para capturar cada sub-paso de lo que hace una persona para completar una action; es el lugar para capturar la estructura del proceso, es decir quién actúa, en qué orden, y dónde realmente ocurre una bifurcación o una espera. Una cadena de nodos que nunca bifurca y nunca cambia de actor agrega superficie al diagrama (más nodos para cablear, más bordes que mantener correctos, más superficie para que el Patrón 5 tenga que detectar después) sin agregar ninguna estructura que el proceso realmente tenga. El detalle es real y vale la pena conservarlo, solo que pertenece a la descripción de la action, no codificado como nodos separados.

Antes:
edit_model (process) → upload_to_cde (process) → share_model (process) → (siguiente nodo)

Después:
update_and_share_model_in_cde (process, description: "Hacer los cambios necesarios en el
modelo de autoría, subir la versión actualizada al CDE, y compartirla con el equipo del proyecto.")
  → (siguiente nodo)

No consolides a través de una bifurcación genuina, una espera por un actor distinto, o un paso que otros flujos reutilizan de forma independiente (ver Patrón 4): si “subir al CDE” es en sí misma reutilizada en otro lugar como su propia action, colapsarla dentro de una más grande la duplica en lugar de reutilizarla. La consolidación solo aplica cuando toda la cadena es verdaderamente un actor haciendo una sola pieza de trabajo ininterrumpida.


Patrón 7: Notifica desde dentro de la automation, no desde un effect en su edge

Si una transición necesita enviar una notificación sobre algo que una automation acaba de hacer (marcar una incidencia como en progreso y publicarlo en un chat, cerrar un ticket y anunciarlo), haz esa llamada desde dentro del handler de la automation misma. No lo modeles como un effect tipo discord-notify colgado del edge de salida de la automation, aunque el Patrón 1 diga que los effects son la herramienta correcta para notificaciones de disparar-y-olvidar en general.

La automation ya tiene todo lo que necesita en el momento en que corre: sabe exactamente qué pasó, y puede construir el mensaje y hacer la llamada directamente, sin indirección adicional. Un effect, en cambio, solo ve lo que el contexto acumulado de la instancia expone bajo los nombres exactos de key que declara su propio payload — lo que obliga a que el output de la automation (o el payload del evento que la dispara) lleve un campo con ese nombre específico solo para que un effect sin relación lo encuentre, únicamente para satisfacer esa búsqueda. Llamar desde dentro de la automation se salta esa indirección por completo.

Mal:
mark_in_review   (automation: actualiza el estado en Bimply)
  │ [edge lleva effect: notify_discord, payload: { message }]

review_model     (process: revisión del Coordinador BIM)

Bien:
mark_in_review   (automation: actualiza el estado en Bimply, y ella misma publica en Discord)

review_model     (process: revisión del Coordinador BIM)

Los effects siguen siendo exactamente correctos cuando la transición la dispara un humano completando un paso de proceso, no una automation. En ese punto no hay ningún handler corriendo del cual colgar una llamada directa — el effect es el único mecanismo disponible para reaccionar a eso.

review_model (process, actor: Coordinador BIM, payload: { approved, comments })
  │ [edge lleva effect: notify_discord, payload: { comments }]

(siguiente nodo)

Esto no cambia el Patrón 1: si un cambio de estado necesita ser una automation lo sigue decidiendo si el sistema externo debe mantenerse sincronizado. Este patrón solo decide, una vez que algo ya es una automation, dónde debe vivir la notificación sobre lo que hizo.