Workflow Studio: fluxos de aprovação que rodam em cima dos seus formulários
O desenvolvedor desenha o fluxo num canvas, simula antes de publicar, e o sistema gerado ganha inbox de pendências, formulários com lente por passo, paralelo real, SLA com escalonamento e um mapa vivo do processo — sem escrever uma linha de código.
O que é
Um fluxo de aprovação é um grafo desenhado no builder que passa a rodar dentro do sistema gerado: cada registro enviado vira uma instância que caminha pelos passos, cria pendências para as pessoas certas, atualiza o próprio registro e deixa trilha de auditoria completa.
Um exemplo completo do mundo real — aprovação de compras. Toda requisição acima de R$ 500 passa pelo gestor da área; aprovada, colhe pareceres do financeiro e da diretoria em paralelo:
Também existem no fluxo: saída Rejeitar do gestor para um fim “Rejeitada” (com comentário obrigatório), ação automática de carimbo entre a Junção e o fim, e notificações. Omitidos aqui para leitura.
O que esse desenho entrega automaticamente no sistema gerado:
- Botão “Enviar para aprovação” no formulário da tabela-alvo (ou envio automático ao salvar).
- Fila de Aprovação — inbox com as pendências da pessoa logada (dos perfis dela + atribuídas direto a ela).
- Formulário aberto com a lente do passo: cada etapa vê/edita só o que foi configurado.
- Mapa do fluxo para o solicitante acompanhar onde o pedido está.
- Prazo com lembrete e escalonamento automáticos, delegação, trilha de auditoria completa.
O nó humano — a peça central
Todo passo humano é modelado do mesmo jeito:
Não existem “nó de aprovação” e “nó de coleta de dados” separados — são o mesmo nó, configurado diferente:
- Aprovação pura = lente toda em leitura + botões Aprovar Rejeitar
- Coleta de dados = alguns campos editáveis (o financeiro anexa o centro de custo, o jurídico o parecer) + um botão Enviar
- Correção pelo solicitante = responsável “Solicitante” + campos editáveis + botão Reenviar apontando de volta — o ciclo de devolução vira um caso comum do grafo.
Responsáveis (quem decide)
| Modo | Como resolve | Quem pode decidir |
|---|---|---|
| Perfil | mad_iam_role.code — estável a renomeações | Qualquer usuário do perfil |
| Solicitante | Quem iniciou a instância (requested_by) | Só essa pessoa |
| Campo do registro | Coluna com id de usuário — ex.: gestor_id da requisição. É o aprovador dinâmico: cada compra sobe para o gestor daquela área | Só o usuário apontado |
| Usuário (login) | Login digitado, resolvido em runtime | Só esse usuário |
Um passo aceita vários responsáveis ao mesmo tempo — cada um vira uma pendência própria (uma tarefa por responsável), e a regra de decisão define quando o passo fecha.
A lente do formulário
O passo humano pode apontar para um formulário existente da tabela-alvo e definir, campo a campo, o que aquele passo enxerga. Mesmo formulário-base, “lentes” diferentes por etapa:
Regras que fazem a lente ser segura de verdade (não só cosmética):
- Baseline tudo-leitura. Só o que está explicitamente como “editável” libera; “oculto” some da tela. Campo novo criado depois nasce em leitura.
- O registro vem da tarefa. O formulário em modo tarefa (
?wf_task=N) ignora o?idda URL — não dá para trocar a lente apontando outro registro. - Whitelist no servidor. Ao decidir, só os campos que a lente marcou como editáveis são salvos (
array_intersect_key) — manipular o POST não fura o recorte. - Obrigatório-para-decidir revalidado no engine. A fila rápida nunca é a única barreira: o motor confere os campos obrigatórios lendo o registro antes de aceitar a decisão.
- “Campo obrigatório para avançar” é condição da transição, não só do formulário — exatamente o mecanismo que resolve o “nem tudo é conhecido no início”: o registro nasce enxuto e acumula dado a cada passo.
Passo sem formulário vinculado também existe: a decisão acontece direto na fila, num modal com os botões do passo e campo de comentário.
Regras de decisão e botões de saída
Botões de saída (outcomes)
Cada passo humano declara seus próprios botões — rótulo, cor e se exigem comentário. Nada de “aprovar/rejeitar” fixos: um passo de parecer pode ter só Parecer OK; a aprovação do gestor pode ter Aprovar Rejeitar Devolver. Cada botão vira uma aresta no canvas — o destino da decisão é literalmente desenhado.
Com vários responsáveis
| Regra | O passo fecha quando… | Uso típico |
|---|---|---|
| Qualquer um | a primeira pessoa decide (as demais pendências são puladas) | Time de plantão — quem pegar primeiro |
| Todos | todos decidem no botão primário · um veto fecha na hora | “Financeiro e diretoria precisam aprovar” |
| Quórum N | N decisões no primário · veto idem | Comitê — 2 de 3 bastam |
Comentário obrigatório é por botão (“Rejeitar exige justificar”) e é barrado pelo motor, não só pela tela.
Todos os nós da paleta
Início
Um por fluxo. De onde a instância parte quando o registro é enviado.
Passo humano
Responsáveis + lente + botões de saída + prazo. A peça de trabalho do fluxo (seção 02).
Condição
Gateway sim/não sobre os dados do registro — mesmo construtor de filtros das listagens, com cadeias de FK. Avaliada na entrada do nó, sobre o dado mais recente.
Ação automática
Atualiza campos do registro sem humano: valores fixos, {now} ou {requester_id}. É como se faz auto-aprovação e carimbos.
Paralelo
Dispara todos os ramos ligados a ele ao mesmo tempo. Cada ligação é um ramo nomeado.
Junção
Espera os ramos: todos, o primeiro (cancela o resto) ou N chegadas.
Notificação
Avisa solicitante ou um perfil e segue. Canal: no app, e-mail ou ambos.
Fim
Encerra com status (aprovado/rejeitado/devolvido) e grava o valor configurado na coluna de status do registro. Um fim em qualquer ramo encerra a instância inteira.
O nó Aprovação da primeira versão continua existindo e rodando (fluxos antigos não quebram) — o passo humano é o seu sucessor generalizado.
Paralelo de verdade: a execução por tokens
Paralelismo existe em dois níveis, e eles se combinam:
- Dentro do passo — vários responsáveis, cada um com sua pendência, join pela regra de decisão. Cobre “financeiro + diretor aprovam em paralelo” com um único nó.
- No grafo — Paralelo/Junção rodam trechos inteiros do fluxo ao mesmo tempo (análise financeira caminhando enquanto a diretoria delibera).
Por baixo, cada ramo vivo é um token — a posição de execução daquele ramo:
Como se lê: o gestor aprova, o Paralelo encerra o token pai e cria um token por ramo (financeiro e diretoria); a Junção “todos” só dispara quando nenhum token vivo ainda consegue alcançá-la.
Três garantias do motor que valem decorar:
- Junção “todos” é dinâmica. Ela não conta arestas: espera os tokens vivos que ainda alcançam a junção. Um ramo desviado por condição não a trava.
- Fim encerra tudo. Qualquer ramo chegando num Fim cancela os tokens e pendências restantes — é o veto em escala de grafo. (O Studio avisa quando você desenha um Fim dentro de um trecho paralelo.)
- Concorrência resolvida por trava de instância. Dois aprovadores decidindo no mesmo segundo, em ramos irmãos, não corrompem o estado: toda mutação trava a linha da instância antes (ordem fixa instância → tarefa → irmãs), e as notificações só saem depois do commit.
Guardas duras: máx. 100 transições por token por avanço e 64 tokens vivos por instância.
Devolver e reenviar — ciclos controlados
“Rejeição” configurável por passo é só apontar as arestas: encerrar (botão → Fim rejeitado), ou devolver para corrigir (botão → passo do solicitante → botão “Reenviar” → de volta ao aprovador). O ciclo é um caso comum do grafo, não uma feature especial.
- O validador permite ciclo somente se ele contém pelo menos um passo humano — ciclo 100% automático (condição ↔ notificação) rodaria em loop de máquina e é barrado no publish.
- Cada reativação do passo ganha uma chave de ativação nova: as decisões da rodada anterior não contaminam a contagem de “todos/quórum” da rodada atual.
- Na prática: o gestor devolve com comentário obrigatório (“detalhe a justificativa”) → a pendência cai para o solicitante com os campos liberados → ele corrige pela lente e reenvia → a aprovação do gestor volta como pendência nova.
Prazo, lembrete e escalonamento
Cada passo humano pode ter SLA. O prazo é gravado na pendência ao nascer, e um agendador (mad:wf-sla-tick, a cada 15 min no sistema gerado) cuida do resto:
“Não aprovou em 48h → sobe para o superior”: o alvo do escalonamento usa os mesmos modos de responsável (perfil, campo do registro, login…). O tick é idempotente — rodar duas vezes não duplica nada (rodar duas vezes não duplica nada).
Delegação é a versão manual: na fila, “Delegar” repassa a pendência para um usuário específico — que passa a ser o único que pode decidi-la. Tudo vai para a trilha (delegated, escalated, reminded).
O Studio — desenhar sem medo
Cada fluxo abre numa aba própria em tela cheia (painel Fluxos de Aprovação → “Abrir no Studio”). O canvas é o de menos — o valor está no resto:
- Paleta arrastável à esquerda (ou clique para soltar no centro), zoom/pan/minimapa, botão Organizar (auto-layout).
- Painel de propriedades à direita — onde moram 80% da configuração: responsáveis, regra de decisão, formulário + editor de lente (tabela campo × editável/leitura/oculto/obrigatório com ações em massa), botões de saída ordenáveis, SLA, condição, canal de notificação.
- Saídas viram alças no próprio nó: cada botão declarado ganha um conector colorido pela sua cor — a regra é desenhada, não digitada. Renomear um botão re-liga as arestas sozinho.
- No nó Paralelo, cada ligação nova é um ramo com nome automático (
ramo_1,ramo_2…) — selecione a aresta para renomear.
Validação em tempo real
As mesmas regras do publish rodam enquanto você desenha — badge vermelho no nó problemático + painel “Problemas” clicável (clicou, seleciona o nó). O que é conferido:
| Categoria | Exemplos do que é barrado |
|---|---|
| Estrutura | saída declarada sem ligação · ligação sem botão correspondente · junção com 1 entrada · paralelo com 1 ramo · gateway sem as duas saídas |
| Pessoas | passo sem responsável · perfil que não existe · quórum maior que o nº de responsáveis |
| Dados | campo da lente que não existe na tabela · obrigatório que não está editável · coluna da ação automática inexistente · placeholder desconhecido |
| Grafo | nó desconectado do Início · caminho que não chega a nenhum Fim · ciclo 100% automático · ligação cruzando a fronteira de um trecho paralelo |
| Avisos | Fim dentro de trecho paralelo (“vai cancelar os irmãos”) |
Gerar com IA
O botão Gerar com IA (✦, na barra do canvas) desenha o fluxo por você: descreva o processo em linguagem natural — “compras até R$ 500 aprovam sozinhas; acima, o gestor aprova com justificativa obrigatória; acima de R$ 10 mil, pareceres em paralelo do financeiro e da diretoria” — e a IA monta os nós, as ligações, as lentes e os SLAs usando as suas tabelas, perfis e formulários reais (ela não inventa nomes: recebe o catálogo do projeto e é validada pelas mesmas regras do publish, com correção automática).
- Nada é salvo sozinho: o resultado entra no canvas como rascunho — revise, ajuste e clique em Salvar.
- Com um fluxo já desenhado, a IA parte dele: “adicione um prazo de 48h com escalada para a diretoria no passo do gestor” funciona (o Studio pede confirmação antes de substituir o desenho).
- Dica: cite perfis e campos pelos nomes que existem no projeto — a IA recebe a lista exata.
- O modelo usado vem de Propriedades do projeto → Modelos de IA → Gerador de fluxos (dá pra trocar pontualmente na própria janela).
Simulação — rodar antes de publicar
- Condições avaliam de verdade contra os valores digitados (mesma semântica do motor): digite
valor = 12.000e veja o fluxo desviar para o gestor;valor = 300cai na auto-aprovação. - Condição que depende de subconsulta/sessão — impossível de avaliar no navegador — vira uma pergunta honesta: “o que aconteceria?” Sim/Não. Nunca um chute.
- Nos passos humanos aparecem os botões reais do passo; com vários responsáveis, você clica as decisões uma a uma e vê a regra fechar (inclusive o veto).
- Paralelo abre os ramos; a junção espera ou cancela; ações automáticas alteram o registro simulado — condições seguintes enxergam a mudança.
“E se o gestor rejeitar?” deixa de ser aposta: reinicie e clique diferente.
Publicar & versionamento
Salvar no Studio grava rascunho; o badge no topo mostra publicado v2 ou rascunho sobre a v2. Publicar valida tudo, compila e congela a versão.
definitions/{slug}.v{N}.php no sistema gerado, e a tabela wf_release guarda todas as versões publicadas: regenerar o projeto re-emite todas — nenhuma instância em voo fica órfã.- Condições são compiladas para closures Eloquent — zero interpretador em runtime, cadeias de FK de graça.
- O formulário do passo é resolvido no publish e o controller fica congelado na definição — apagar a página depois não derruba instâncias (a fila degrada com aviso).
- A definição também carrega o bloco
ui(posições + arestas rotuladas) — é dele que o mapa do fluxo desenha o processo no sistema gerado.
No sistema gerado — o dia a dia
Enviar
O formulário da tabela-alvo ganha “Enviar para aprovação” (gatilho manual). Com gatilho ao criar/ao editar, o envio é automático após salvar — registro já em aprovação é ignorado com elegância.
A fila (inbox)
Página “Fila de Aprovação”: pendências dos seus perfis + as atribuídas direto a você. Colunas: registro legível (rótulo denormalizado — “Notebooks Dell — 10 un.”, não “#42”), etapa, fluxo, prazo, criada em. Ações:
- Abrir — passo com formulário → navega para o form em modo tarefa (lente aplicada, botões do passo no rodapé, comentário quando exigido). Passo sem formulário → modal de decisão ali mesmo.
- Delegar — repassa a pendência para um usuário específico.
O formulário em modo tarefa
No exemplo da compra: descricao e valor travados, centro_custo invisível para o gestor, justificativa editável e obrigatória; o botão Salvar padrão some e entram Aprovar/Rejeitar/Devolver. Decidir salva os campos liberados e registra a decisão numa tacada.
O mapa do fluxo
Botão “Acompanhar aprovação” no formulário abre o processo desenhado — SVG renderizado no servidor a partir da definição congelada: passos visitados marcados, ativos pulsando, status e trilha resumida. É a resposta ao “cadê meu pedido?” sem abrir chamado. Sobrevive até a definição sumir do disco (cai no retrato congelado da instância).
Trilha de auditoria
Tudo é evento imutável em mad_wf_history: started, task_created, decided (com botão e comentário), task_skipped, forked, join_fired, auto_applied, delegated, escalated, reminded, cancelled_by_end, completed — quem, quando, o quê.
Arquitetura — três planos
O motor (WorkflowEngine, no framework) expõe uma API pequena:
start(slug, registro, solicitante) // “Enviar para aprovação” — cria instância + token raiz
decide(tarefa, botão, comentário, ator) // valida botão/autorização/lente e avança o token
reassign(tarefa, usuário) // delegação e escalonamento
slaTick() // lembretes + escaladas (agendado, idempotente)
taskContext(tarefa) // lente + botões + registro — o que a fila e o form consomem
instanceMap(instância) // bloco ui + nós ativos + trilha — o que o mapa desenha
Shape essencial de um passo humano na definição compilada:
'aprovacao_gestor' => [
'kind' => 'human',
'condition' => fn ($q) => $q->where('valor', '>', 500),
'assignees' => [['mode' => 'field', 'field' => 'gestor_id']], // o gestor daquela compra
'decision' => ['rule' => 'any'],
'form' => ['page_id' => 41, 'controller' => 'CompraForm', // congelado no publish
'lens' => ['fields' => ['descricao' => 'read', 'valor' => 'read', 'justificativa' => 'edit', 'centro_custo' => 'hidden'],
'required' => ['justificativa']]],
'outcomes' => ['aprovar' => 'pareceres', 'rejeitar' => 'fim_rej', 'devolver' => 'corrigir'],
'sla' => ['hours' => 48, 'escalate_to' => ['mode' => 'role', 'role' => 'diretoria'], 'remind_hours_before' => 8],
],
Bom saber — comportamento e limites
- Condições avaliam o dado mais recente. A condição roda quando o fluxo entra no nó — se um passo anterior editou o registro pela lente, o gateway seguinte já enxerga a mudança.
- Perfis por código. A alçada referencia o código do perfil de acesso — renomear o perfil não quebra fluxos publicados. Perfil sem usuários não trava: a pendência fica aguardando e aparece assim que alguém ganhar o perfil.
- Um registro, uma aprovação por vez. Enviar um registro que já está em aprovação no mesmo fluxo é bloqueado com aviso.
- Registro excluído no meio do fluxo: ações automáticas e status viram no-op registrado na trilha; decisões que exigem campos obrigatórios avisam que o registro não existe mais.
- Fim dentro de trecho paralelo cancela os irmãos — é o comportamento de veto. O Studio avisa ao desenhar.
- E-mail é melhor esforço. Notificação por e-mail depende da configuração de correio do sistema gerado; falha de envio nunca trava o fluxo (o aviso no app sempre acontece).
- Guardas de segurança do motor: no máximo 100 transições por avanço e 64 ramos vivos por solicitação — um desenho degenerado para com erro claro, nunca roda para sempre.
Limitações conhecidas & próximos passos
- Anexos por decisão —
mad-attach-btnjá existe no framework; falta plugar na tela de decisão. - WhatsApp — exige provedor externo (Evolution/Twilio). Hoje: no app + e-mail.
- “Gestor do solicitante” — o IAM não tem hierarquia (
manager_id); hoje o dinâmico é por campo do registro. - Lente sobre tabelas de detalhe — no modo tarefa elas ficam ocultas; expor exigirá
lens.sections. - Wizard em modo tarefa — o passo aponta para formulários comuns; wizards ficam de fora por ora.
- Cancelamento administrativo de instância órfã/travada.