> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zapmeow.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Fluxo por estágio

> Motor de fluxo ligado aos estágios do GHL: desenho, condições em linguagem natural, ações ao entrar e ao sair, sombra obrigatória antes do ao vivo.

Cada pipeline do GHL pode ter um fluxo: um grafo em que **cada nó é um estágio**, cada aresta tem uma **condição em linguagem natural** e cada nó tem **ações ao entrar e ao sair**. O estágio da oportunidade no GHL manda: o nó da conversa é sempre o do estágio em que o card está. Tudo abaixo usa a chave admin (`X-Admin-Key`), em `/admin/tenants/{id}`.

## Quem decide

O ZapMeow não escreve a resposta nem avalia a condição: ele pergunta ao **decisor** e executa. O decisor é configurado na seção `flow` do tenant:

```json theme={null}
PUT /admin/tenants/{id}/config/flow
{ "decider": { "kind": "http", "url": "https://sua-ia.exemplo/decide" },
  "minShadowDays": 7, "minShadowRuns": 3, "minShadowEvents": 10, "historyMessages": 12 }
```

* `kind: "http"`: o ZapMeow faz `POST` na `url` com o turno e espera a decisão. Se existir o segredo `flow_decider_token` no cofre do tenant, vai em `Authorization: Bearer`.
* `kind: "anthropic"`: chama a API da Anthropic com o segredo `anthropic_api_key` do tenant (modelo em `decider.model`, padrão `claude-sonnet-5-5`).

Entrada do decisor (`DecideIn`): `mode` (`shadow`, `simulate` ou `live`), `nodeKey`, `nodeTitle`, `instructions`, `options[]` (`to`, `title`, `when`), `collect[]` (campos a extrair), `messages[]` (`from`: `lead` ou `ia`), `contactId`, `opportunityId`.
Saída (`DecideOut`): `reply` (texto ao lead ou vazio), `next` (chave de um nó de `options`, ou vazio para ficar), `handoff` (passar para uma pessoa), `fields` (campo → valor, só dos pedidos em `collect`), `reason`. Passagem para um nó que não é saída do atual e campo não pedido são descartados, com nota no evento.

## Desenhar

| Rota | O que faz |
| - | - |
| `GET /flows` · `POST /flows` | lista · cria (`400` se o desenho é inválido) |
| `GET/PUT/DELETE /flows/{fid}` | lê · substitui o desenho · apaga |
| `POST /flows/{fid}/status` `{"status": "draft"\|"shadow"\|"live"}` | muda o estado (veja abaixo) |

```json theme={null}
{ "name": "Vendas", "ghlPipelineId": "P1",
  "nodes": [
    { "key": "novo", "title": "Novo", "ghlStageId": "S1", "instructions": "Descubra o interesse",
      "onEnter": [{ "kind": "collect_field", "arg": "Interesse" }],
      "onExit":  [{ "kind": "create_followup", "arg": "Ligar para o lead", "value": "2d" }] },
    { "key": "qual", "title": "Qualificado", "ghlStageId": "S2",
      "onEnter": [{ "kind": "send_message", "arg": "Perfeito! Vou te passar as condições." }] },
    { "key": "fim", "title": "Fechado", "kind": "end", "ghlStageId": "S3" } ],
  "edges": [ { "from": "novo", "to": "qual", "when": "o lead demonstrou interesse real" },
             { "from": "qual", "to": "fim",  "when": "o lead aceitou a proposta" } ] }
```

Nós e arestas guardam `tenantId` e `ghlPipelineId`. Regras: o pipeline e os estágios precisam existir no espelho do GHL (sincronize antes: `409` se não); um nó por estágio; `kind` é `stage` (precisa de `ghlStageId`) ou `end` (fim; não sai); até 100 nós, 300 arestas, 10 ações por lista. O pipeline de um fluxo não muda depois de criado. Um fluxo em sombra ou ao vivo por pipeline.

### Catálogo de ações

| `kind` | `arg` | `value` | O que faz ao vivo |
| - | - | - | - |
| `send_message` | texto | | enfileira na fila humanizada (digitando, pausa, áudio responde áudio) |
| `move_stage` | id ou nome do estágio | | tool `mover_oportunidade` |
| `set_field` | campo (id, chave ou nome) | valor | tool `gravar_campo` |
| `collect_field` | campo | | o decisor extrai da conversa; ao informar, grava com `gravar_campo` |
| `create_followup` | título | prazo (`30m`, `4h`, `2d`, `AAAA-MM-DD` ou RFC 3339) | tool `criar_followup` |
| `handoff` | aviso opcional ao lead | | passa para uma pessoa e **pausa a IA nesta conversa**; emite o evento `flow.handoff` no webhook da sessão |

As tools respeitam o modo do tenant (`config/tools`): em `pede_ok` (padrão) o fluxo propõe e uma pessoa aprova.

## Sombra antes do ao vivo

* `draft`: só desenho; não processa conversa.
* `shadow`: observa as conversas reais do pipeline e grava em `zm_flow_events` **o que faria** (ações ao sair, passagem, ações ao entrar). **Não envia, não mexe no GHL e não chama nenhuma tool.** O mundo segue como está; quando o card muda de estágio, o evento `compare` diz se bateu com a previsão (`match`).
* `live`: executa. **Só se chega aqui pelo `shadow`**: `draft → live` dá `409`, e `shadow → live` confere o relatório (`GET /flows/{fid}/shadow`): dias em sombra, conversas reais e eventos mínimos (padrão 7 dias, 3 conversas, 10 eventos; configuráveis na seção `flow`, com piso de 1 conversa e 1 evento). Conversa de teste (simulação), eventos de erro e `skipped` não contam.
* Editar o desenho de um fluxo ao vivo o devolve para a sombra, na versão seguinte (a contagem recomeça).
* `live → shadow` é o botão de reversão.

## Conversa de teste (simulação)

`POST /flows/{fid}/simulate` com `{"chat": "teste-1", "message": "tenho interesse", "startNode": "novo"}` roda um turno de uma conversa que não existe no WhatsApp: o histórico fica guardado, o decisor responde e o nó segue as passagens escolhidas. Nada é enviado e o GHL não é tocado. Funciona em qualquer estado, inclusive rascunho. Responde `{event, run}`.

## Acompanhar

| Rota | O que devolve |
| - | - |
| `GET /flows/{fid}/events?kind=&runId=&before=&limit=` | eventos: `turn`, `transition`, `handoff`, `reposition`, `compare`, `confirmed`, `rejected`, `skipped`, `error`, cada um com `wouldDo`, `executed`, `match`, `note` |
| `GET /flows/{fid}/shadow` | relatório da sombra e se já pode ir ao vivo |
| `GET /flows/{fid}/runs` | conversas acompanhadas: nó atual, `paused`, `awaiting` |
| `POST /flows/{fid}/runs/{rid}/pause` `{"reason"}` · `/resume` | pausa ou devolve a conversa para a IA |

## Como a conversa encontra o fluxo

A mensagem do lead (chat individual) procura o contato do GHL pelo telefone (`/contacts/search/duplicate`, vínculo guardado em `zm_flow_links`), pega a oportunidade **aberta** mais recente num pipeline com fluxo em sombra ou ao vivo e começa no nó do estágio dela. Lead sem contato no GHL ou em estágio fora do desenho não é acompanhado (o evento `skipped` explica). Várias mensagens seguidas geram uma resposta só, sobre a conversa inteira.

## O humano manda no card

* Pessoa move o card (webhook do GHL, ou o espelho ao começar o turno): o nó **reposiciona** sem executar ações (evento `reposition`; na sombra, `compare`). Oportunidade ganha, perdida ou abandonada encerra o acompanhamento.
* Passagem em `pede_ok`: o nó só muda depois do OK do pedido; aprovado, o evento `confirmed` roda as ações ao entrar; recusado, a conversa fica onde estava (`rejected`).
* Handoff pausa só este motor. Respostas que outro sistema (ex.: o Convex do tenant Glow) manda pela fila `outbox` não são bloqueadas pela pausa.

## Limites conhecidos

* O motor guarda estado no Postgres, mas as travas por conversa são em memória: mantenha 1 réplica.
* Condições são avaliadas pelo decisor, não pelo ZapMeow: a qualidade vem do decisor.
* Mensagem de áudio do lead entra no histórico como `[audio]` (sem transcrição).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.