como funciona el flowable, en cuanto a flujos, puedo parametrizar los estado y quien ejecuta ese estado?
¡Hola! Qué excelente pregunta. Sí, absolutamente puedes hacer ambas cosas en , y de hecho es una de sus mayores fortalezas.
Antes de adentrarnos en cómo estructurar estos flujos y diseñar las asignaciones dinámicas, me encantaría saber un poco más de tu contexto actual para darte la explicación perfecta.
¿Estás trabajando en una implementación concreta con Java/Spring Boot o usas las herramientas de modelado de Flowable UI, y cuál es tu nivel de experiencia previo con BPMN?
quiero para usar con sprint boot y uso de flowable ui, yoy principiante
¡Qué alegría saber que estás dando tus primeros pasos con y Flowable UI! Como estás empezando, vamos a desmitificar cómo funciona este motor por detrás sin complicarnos con demasiada teoría.
La autopista del proceso: Transiciones y Wait States
A five-step BPMN sequential process flow moving left to right from start event to end event.
¿Cómo parametrizar quién ejecuta un estado?
- Assignee: Un único usuario responsable. Si usas
\${ejecutor}, Flowable buscará en las variables de tu proceso el valor de esa variable al llegar a la tarea. - Candidate Users: Un grupo de usuarios específicos que pueden reclamar la tarea (ej.
\${candidatos}). - Candidate Groups: Grupos o roles enteros habilitados para resolverla (ej.
GROUP_\${rolGrupo}).
\${status} que puedes actualizar mediante tareas de servicio automáticas a medida que el token avanza.quiero que me generes un ejemplo de flujo con la siguiente información: SOLICITANTE
│
Crear solicitud
│
BORRADOR
│
Enviar
▼
SOLICITADO
│
▼
COORDINADOR
Revisar solicitud
/
Observar Aprobar
│ │
▼ ▼
OBSERVADO ASIGNADO
│ │
Corregir ▼
│ RESPONSABLE
└──► enviar Iniciar mantenimiento
│
▼
EN_MANTENIMIENTO
│
Finalizar
▼
EN_REVISION
│
▼
SUPERVISOR
Revisar
/
Observar Validar
│ │
▼ ▼
OBSERVADO_MANT. VALIDADO
│ │
Corregir ▼
│ RESPONSABLE
└────────► Registrar trabajo
│
▼
TRABAJO_REALIZADO
│
▼
SOLICITANTE
Recibir/verificar
│
▼
FINALIZADO
¡Este es un flujo espectacular para modelar! Contiene ciclos de revisión, múltiples roles dinámicos y cambios continuos de estado de negocio. Vamos a plasmar exactamente esta lógica utilizando el estándar BPMN 2.0 en Flowable.
He comenzado a generar una propuesta visual del diagrama BPMN. Mientras se procesa la imagen, analicemos los tres pilares clave para construir este flujo de forma completamente parametrizada en y Spring Boot sin dejar nada fijo en el XML.
1. Mapeo de Roles Dinámicos con JUEL
Para que tu flujo sea reutilizable, no debes escribir nombres de usuarios reales en las propiedades de las tareas en Flowable UI. En su lugar, utilizaremos las expresiones dinámicas que aprendimos. Configura las tareas de tu diagrama con estos valores en el campo Assignee:
- Crear solicitud / Corregir:
\${solicitante} - Revisar solicitud:
\${coordinador} - Iniciar mantenimiento / Registrar trabajo:
\${responsable} - Revisar (Supervisor):
\${supervisor}
Cuando arranques la instancia desde tu backend de Spring Boot, simplemente inyectarás un mapa con los IDs reales correspondientes a estas variables.
2. Compuertas Exclusivas (Exclusive Gateways)
En tu mapa de procesos tienes dos decisiones críticas:
- El Coordinador decide si aprueba o pone observaciones. Usaremos un . Al salir de la compuerta, una flecha irá hacia 'Iniciar mantenimiento' con la condición formal de flujo
\${aprobado == true}. La otra flecha irá hacia 'Corregir solicitud' (cambiando al estado OBSERVADO) con la condición\${aprobado == false}. - El Supervisor decide si valida o pone observaciones al finalizar el mantenimiento. Configuramos otra compuerta exclusiva con condiciones similares, por ejemplo
\${validado == true}y\${validado == false}.
3. Parametrización del Business Status
Para que los usuarios sepan en qué estado de negocio se encuentra su flujo (como BORRADOR, EN_MANTENIMIENTO o TRABAJO_REALIZADO), utilizaremos la variable de proceso \${status} o invocaremos el servicio del motor para actualizar el Business Status oficial de la instancia.
Esto se puede lograr de dos maneras en Flowable UI:
- Service Tasks: Creando una tarea automática antes de cada paso que ejecute un método en Spring Boot para cambiar el estado.
- Execution Listeners: Configurando un listener en las transiciones o al entrar a las User Tasks para que de forma silenciosa actualice el estado en la base de datos.
¿Cómo se vería el código en Spring Boot para iniciar este proceso enviando los roles?
pom.xml. Esto levantará automáticamente todos los servicios del motor como y configurará la base de datos en memoria para que no tengas que preocuparte por la infraestructura al arrancar.<!-- En tu pom.xml -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.8.0</version> <!-- Usa la versión estable más reciente -->
</dependency>
.bpmn20.xml dentro del directorio src/main/resources/processes/. Al iniciar la aplicación, Flowable escaneará esa carpeta y desplegará el flujo en la base de datos de manera automática.Controlador para Iniciar el Proceso y Consultar Tareas
RuntimeService para arrancar la instancia enviando los roles dinámicos como variables, y TaskService para buscar las tareas pendientes de un usuario específico y completarlas tomando en las compuertas.package com.example.flowable.controller;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/mantenimiento")
public class MantenimientoController {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
// 1. Iniciar el proceso enviando los roles asignados a las variables JUEL
@PostMapping("/iniciar")
public String iniciarProceso(@RequestParam String solicitante,
@RequestParam String coordinador,
@RequestParam String responsable,
@RequestParam String supervisor) {
Map<String, Object> variables = new HashMap<>();
variables.put("solicitante", solicitante);
variables.put("coordinador", coordinador);
variables.put("responsable", responsable);
variables.put("supervisor", supervisor);
// Se inicia usando la clave (Key) del proceso definida en Flowable UI
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey("procesoMantenimiento", variables);
return "Proceso iniciado con ID: " + processInstance.getId()
+ " | Estado de negocio inicial: BORRADOR";
}
// 2. Consultar las tareas pendientes asignadas a un usuario específico
@GetMapping("/tareas")
public List<Map<String, String>> obtenerTareasPendientes(@RequestParam String usuario) {
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee(usuario)
.list();
return tasks.stream().map(task -> {
Map<String, String> map = new HashMap<>();
map.put("id", task.getId());
map.put("nombre", task.getName());
map.put("instanciaId", task.getProcessInstanceId());
return map;
}).collect(Collectors.toList());
}
// 3. Completar la tarea del Coordinador tomando la decisión (Aprobar o Observar)
@PostMapping("/completar-coordinador")
public String completarCoordinador(@RequestParam String taskId, @RequestParam boolean aprobado) {
Map<String, Object> variables = new HashMap<>();
variables.put("aprobado", aprobado);
taskService.complete(taskId, variables);
return aprobado ? "Solicitud aprobada. Enviada a Responsable (ASIGNADO)."
: "Solicitud observada. Retornada a Solicitante (OBSERVADO).";
}
}
/iniciar, el motor evalúa las variables pasadas en el mapa y las asocia a la instancia. Cuando el token llega a la primera tarea del Coordinador, Flowable consulta quién está asignado resolviendo la expresión \${coordinador}.¿Cómo actualizo automáticamente el Business Status en la base de datos al cambiar de tarea?
Actualizar de forma automática el Business Status es una de las mejores prácticas para mantener la transparencia en tu sistema de flujos. En lugar de ejecutar código manual en tus controladores REST cada vez que avanza el token, puedes delegar esta responsabilidad directamente en el motor mediante .
La Solución Elegante: TaskListener e Inyección en Spring Boot
La forma más robusta y limpia de lograr esto en Spring Boot es implementar un interceptor de tareas que escuche el evento de creación. Cada vez que una se instancie, nuestro código se ejecutará automáticamente para leer una propiedad personalizada o deducir el nuevo estado y actualizar el Business Status de la instancia de proceso activa en la base de datos.
package com.example.flowable.listener;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("statusUpdateListener")
public class StatusUpdateListener implements TaskListener {
@Autowired
private RuntimeService runtimeService;
@Override
public void notify(DelegateTask delegateTask) {
// 1. Obtener el ID de la instancia de proceso en ejecución
String processInstanceId = delegateTask.getProcessInstanceId();
// 2. Obtener el nombre o una variable asignada a la tarea actual
String taskDefinitionKey = delegateTask.getTaskDefinitionKey();
String nuevoEstado = "TRABAJO_REALIZADO";
// Mapeamos el ID de la tarea de BPMN al Business Status deseado
switch (taskDefinitionKey) {
case "crearSolicitud":
nuevoEstado = "BORRADOR";
break;
case "revisarSolicitud":
nuevoEstado = "SOLICITADO";
break;
case "corregirSolicitud":
nuevoEstado = "OBSERVADO";
break;
case "iniciarMantenimiento":
nuevoEstado = "EN_MANTENIMIENTO";
break;
case "revisarSupervisor":
nuevoEstado = "EN_REVISION";
break;
case "recibirVerificar":
nuevoEstado = "VALIDADO";
break;
default:
nuevoEstado = "PROCESANDO";
break;
}
// 3. Actualizar directamente el Business Status oficial en la base de datos
runtimeService.updateBusinessStatus(processInstanceId, nuevoEstado);
System.out.println("¡Listener Ejecutado! Instancia: " + processInstanceId
+ " actualizada al estado: " + nuevoEstado);
}
}
Configuración en Flowable UI
Para asociar este componente de Spring a tus tareas en el modelador visual de Flowable UI, solo debes seguir estos pasos sencillos en la barra de propiedades:
- Selecciona la User Task correspondiente (por ejemplo, 'Revisar solicitud').
- Busca la propiedad Task listeners en el panel lateral y haz clic para editar.
- Añade un nuevo listener con el evento de tipo
create. - Configura el tipo de listener como
Delegate expressiony escribe\${statusUpdateListener}en la expresión.
Con esto configurado, el motor buscará tu clase de Spring por su nombre de componente y actualizará el estado del negocio de forma invisible.
Genérame un ejemplo de archivo XML BPMN completo con esta configuración
Para que puedas visualizar exactamente cómo se estructuran estos elementos en segundo plano, he preparado la definición BPMN 2.0 en XML completa. Este archivo contiene la definición del proceso secuencial, las compuertas para evaluar las decisiones y la declaración de nuestro listener de tareas que se dispara en el evento de creación.
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flowable="http://flowable.org/bpmn"
targetNamespace="http://www.flowable.org/processdef">
<process id="procesoMantenimiento" name="Flujo de Mantenimiento Parametrizado" isExecutable="true">
<!-- 1. Evento de Inicio -->
<startEvent id="inicioProceso" />
<!-- 2. Tarea: Crear solicitud (Asignada al Solicitante) -->
<userTask id="crearSolicitud" name="Crear solicitud" flowable:assignee="${solicitante}">
<extensionElements>
<flowable:taskListener event="create" delegateExpression="${statusUpdateListener}" />
</extensionElements>
</userTask>
<!-- 3. Tarea: Revisar solicitud (Asignada al Coordinador) -->
<userTask id="revisarSolicitud" name="Revisar solicitud" flowable:assignee="${coordinador}">
<extensionElements>
<flowable:taskListener event="create" delegateExpression="${statusUpdateListener}" />
</extensionElements>
</userTask>
<!-- 4. Primera Compuerta Exclusiva (Decisión del Coordinador) -->
<exclusiveGateway id="compuertaCoordinador" name="¿Aprobado por Coordinador?" />
<!-- 5. Tarea: Corregir solicitud (Asignada al Solicitante si se observa) -->
<userTask id="corregirSolicitud" name="Corregir solicitud" flowable:assignee="${solicitante}">
<extensionElements>
<flowable:taskListener event="create" delegateExpression="${statusUpdateListener}" />
</extensionElements>
</userTask>
<!-- 6. Tarea: Iniciar mantenimiento (Asignada al Responsable) -->
<userTask id="iniciarMantenimiento" name="Iniciar mantenimiento" flowable:assignee="${responsable}">
<extensionElements>
<flowable:taskListener event="create" delegateExpression="${statusUpdateListener}" />
</extensionElements>
</userTask>
<!-- 7. Tarea: Revisar (Asignada al Supervisor) -->
<userTask id="revisarSupervisor" name="Revisar" flowable:assignee="${supervisor}">
<extensionElements>
<flowable:taskListener event="create" delegateExpression="${statusUpdateListener}" />
</extensionElements>
</userTask>
<!-- 8. Segunda Compuerta Exclusiva (Decisión del Supervisor) -->
<exclusiveGateway id="compuertaSupervisor" name="¿Validado por Supervisor?" />
<!-- 9. Tarea: Corregir trabajo (Asignada al Responsable si se observa) -->
<userTask id="corregirTrabajo" name="Corregir trabajo" flowable:assignee="${responsable}">
<extensionElements>
<flowable:taskListener event="create" delegateExpression="${statusUpdateListener}" />
</extensionElements>
</userTask>
<!-- 10. Tarea: Recibir/verificar (Asignada al Solicitante para finalizar) -->
<userTask id="recibirVerificar" name="Recibir/verificar" flowable:assignee="${solicitante}">
<extensionElements>
<flowable:taskListener event="create" delegateExpression="${statusUpdateListener}" />
</extensionElements>
</userTask>
<!-- 11. Evento de Fin -->
<finProceso id="finProceso" />
<!-- ========================================== -->
<!-- CONECTORES DE SECUENCIA Y CONDICIONES (JUEL) -->
<!-- ========================================== -->
<sequenceFlow id="flow1" sourceRef="inicioProceso" targetRef="crearSolicitud" />
<sequenceFlow id="flow2" sourceRef="crearSolicitud" targetRef="revisarSolicitud" />
<sequenceFlow id="flow3" sourceRef="revisarSolicitud" targetRef="compuertaCoordinador" />
<!-- Transiciones de la compuerta del Coordinador -->
<sequenceFlow id="flowAprobadoCoordinador" name="Aprobar" sourceRef="compuertaCoordinador" targetRef="iniciarMantenimiento">
<conditionExpression xsi:type="tFormalExpression">${aprobado == true}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flowObservadoCoordinador" name="Observar" sourceRef="compuertaCoordinador" targetRef="corregirSolicitud">
<conditionExpression xsi:type="tFormalExpression">${aprobado == false}</conditionExpression>
</sequenceFlow>
<!-- Retorno desde corrección a revisión -->
<sequenceFlow id="flowReenviar" name="Enviar" sourceRef="corregirSolicitud" targetRef="revisarSolicitud" />
<sequenceFlow id="flow4" sourceRef="iniciarMantenimiento" targetRef="revisarSupervisor" />
<sequenceFlow id="flow5" sourceRef="revisarSupervisor" targetRef="compuertaSupervisor" />
<!-- Transiciones de la compuerta del Supervisor -->
<sequenceFlow id="flowValidadoSupervisor" name="Validar" sourceRef="compuertaSupervisor" targetRef="recibirVerificar">
<conditionExpression xsi:type="tFormalExpression">${validado == true}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flowObservadoSupervisor" name="Observar" sourceRef="compuertaSupervisor" targetRef="corregirTrabajo">
<conditionExpression xsi:type="tFormalExpression">${validado == false}</conditionExpression>
</sequenceFlow>
<!-- Retorno desde corrección de trabajo a revisión de supervisor -->
<sequenceFlow id="flowCorregido" name="Registrar trabajo" sourceRef="corregirTrabajo" targetRef="revisarSupervisor" />
<sequenceFlow id="flow6" sourceRef="recibirVerificar" targetRef="finProceso" />
</process>
</definitions>
- La etiqueta
<userTask>: Define cada estado de espera humano. Nota el atributoflowable:assignee="\${variable}", que inyecta dinámicamente el valor del usuario en tiempo de ejecución. - El bloque
<extensionElements>: Dentro de cada tarea declaramos nuestroflowable:taskListenerapuntando adelegateExpression="\${statusUpdateListener}". El eventocreategarantiza que se dispare exactamente cuando el token pisa el estado, permitiendo que el se actualice sin demora. - Las compuertas
<exclusiveGateway>: El rombo de decisión no almacena lógica; esta se define en las etiquetas<sequenceFlow>de salida mediante expresiones de condición<conditionExpression>que evalúan las variables booleanas que inyectamos desde el código de Spring Boot.
