# 02 - Fluxos do Sistema

> Os fluxos ponta-a-ponta do Genesis, com objetivo, entrada, processamento, saída, exceções e riscos de cada um. O domínio central é a **logística de expedição**; os demais módulos orbitam em torno dela.
>
> Relacionados: [03 - Regras de Negócio](03-regras-de-negocio.md) (o porquê de cada regra), [04 - Módulos](04-modulos.md) (quem é dono de cada etapa), [06 - APIs](06-apis.md).

## 0. O fluxo-mestre: do pedido à entrega (e de volta)

```mermaid
flowchart TD
  P[Pedido no TMS/ERP] --> C[Central: incluir/importar pedido]
  C --> RC[Rotas de carregamento + roteirização]
  RC --> ER{Motorista quer reordenar?}
  ER -- sim --> EDR[Edicaorota: OTP + proposta + aprovação do gestor]
  ER -- não --> D[Designacoes: gestor cria missões]
  EDR --> D
  D --> SEP[Missão SEPARAÇÃO - App + bipagem]
  SEP --> CONF{requer_conferencia?}
  CONF -- sim --> CF[Conferência pelo líder]
  CONF -- não --> CAR[Missão CARREGAMENTO auto-gerada]
  CF --> CAR
  CAR --> ENT[Entrega]
  ENT --> RET[Retorno: Ocorrencias]
  RET --> LOTE[Lotes de pendência + retorno ao mapa]
  D --> WMS[Missão ALOCACAO_WMS]
  D --> INV[Inventário cíclico]
```

Quem cuida de cada etapa (tabela completa em [04 - Módulos](04-modulos.md)):

| Etapa | Sinal |
|---|---|
| Painel de despacho, rotas, romaneios | `Central` (V1, produção) / `Central2` (V2, dev-only) |
| Reordenação de rota pelo motorista | `Edicaorota` |
| Criação de missões | `Designacoes` (+ `Lotes`/`GestorLotes` como staging) |
| Execução no app do operador | `App` (Handlers por tipo) + satélites `MissoesExecucao`/`MissoesBipagem` |
| WMS interno/externo | `AlocacaoWms` + `IntegracaoWms` |
| Retorno e exceções | `Ocorrencias`, `Solicitacoes`, `SugestoesCodigoBarras` |
| Observabilidade | `Dashboard` via `/api/v1/dashboard/*` |

---

## 1. Fluxo: Missão Logística (criação → execução → conclusão)

**Objetivo.** Empacotar qualquer trabalho de armazém/rota (separação, carregamento, pesagem, alocação WMS, inventário, retorno, ajuste de dados — 13 tipos no enum `TipoMissao`) num modelo único com itens, equipe e auditoria.

**Entrada.** Gestor no painel `Designacoes` (ou um Lote, ou uma ordem WMS externa) seleciona romaneios/pedidos/produtos e um tipo de missão.

**Processamento.**
1. `Designacoes` delega a `GestaoMissoes<Tipo>` (Template Method sobre `GestaoMissoesBase::processarEnvio()`): buscar itens → aplicar regras de negócio → deduplicar → persistir via satélite `MissoesLogisticas` (tabelas `missao_logistica`, `missao_logistica_itens`, `missao_logistica_usuarios`).
2. O operador abre o app (`App`), que roteia por handler (`SeparacaoHandler`, `CarregamentoHandler`...).
3. **Fila de itens multi-operador**: `MissoesExecucao::obterProximoItem` aplica *stickiness* (se o usuário já tem item `em_andamento`, devolve ele), ordena candidatos por Strategy (roteirização/Sticky Floor em PHP) e reserva atomicamente via stored procedure `sp_reservar_item_da_lista` — `FOR UPDATE SKIP LOCKED` entre operadores + `pg_advisory_xact_lock(usuario)` contra duplo clique do mesmo operador.
4. Cada bipagem/conclusão grava no **extrato append-only** (`missao_logistica_extrato`).
5. Conclusão passa por `MissoesStatus::transicionar()` — o **hub de side-effects** (ver máquina de estados abaixo).

**Saída.** Missão `concluida` + efeitos em cascata: separação sem conferência gera missão de carregamento; carregamento concluído finaliza o pedido; conclusão sincroniza `romaneios.separado/carregado`; sugestões de código de barras pendentes viram missão `AJUSTE_DADOS`.

**Exceções.**
- "Corte logístico": `finalizarManualmente` marca itens sem bipagem como `REMOVIDO` e pode **regerar** nova missão com o saldo.
- Operador precisa sair/recomeçar → passa pela fila global `Solicitacoes` (tipos DESVINCULO, RECOMECO, ACESSO_*).
- Item com problema → status `ocorrencia`/`pendente_corte`.

**Riscos.** Concorrência é resolvida **por item**, nunca por missão — qualquer código novo que tente "travar a missão para um operador" quebra o modelo intencional de múltiplos operadores ([03 §2](03-regras-de-negocio.md)). Ids do enum `TipoMissao` não são sequenciais e são imutáveis (há linhas em produção).

### Máquina de estados da missão

```mermaid
stateDiagram-v2
  [*] --> rascunho
  rascunho --> pendente
  pendente --> em_andamento
  em_andamento --> pausada
  em_andamento --> aguardando_aprovacao
  em_andamento --> concluida
  pausada --> em_andamento
  aguardando_aprovacao --> concluida
  aguardando_aprovacao --> em_andamento
  aguardando_aprovacao --> cancelada
  pendente --> cancelada
  cancelada --> pendente : reabertura
  concluida --> [*]
```

`MissoesStatus::transicionar()` valida a transição pelo enum `StatusMissao::proximosStatusPossiveis()` e dispara os hooks pós-transição. Os hooks usam `class_exists()` + try/catch não-fatal — falha num hook não desfaz a transição.

---

## 2. Fluxo: Bipagem com fator por código de barras

**Objetivo.** Uma leitura de código pode representar N unidades (ex.: DUN de caixa com `fator=10`), mantendo **um registro de log por leitura**.

**Entrada.** Scan (ou digitação) de um código durante separação/carregamento/pesagem no App.

**Processamento** (núcleo em `Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesBipagem.php`):
1. `resolverProduto`: consulta **primeiro** a tabela `produtos_codigos_barras` (fonte da verdade do fator); fallback para os campos do produto com fator 1.
   - Validação **estrita** (item esperado selecionado): código deve pertencer ao produto — senão erro.
   - Validação **ampla** (scan livre): resolve pela Sonda ou apenas pelo `codigo_barras` principal — **não** aceita `id`/`codigo_alternativo` (poderiam colidir com o código real de outro produto).
2. `alterarQuantidade(item, fator, 'scan', ...)`: `novaQtd = max(0, atual + fator)`.
3. `validarLimites`: itens **pesáveis** (KG/G/MT/L) são isentos de teto e do fator de caixa; os demais bloqueiam se `novaQtd > limite` — nada é somado.
4. Contadores separados: `quantidade_bipada_scan` vs `quantidade_bipada_manual`; ajustes manuais ± são sempre unitários (fator não se aplica).
5. Item completo → `SeparacaoHandler` busca o próximo da fila (SKIP LOCKED) e re-renderiza o card via `reencaminheLuz`.

**Saída.** Item atualizado + linha de extrato com `fator` e `tipo_codigo_barras`. Offline-first: o mapa `codigosBarras={codigo: fator}` vai ao front para incremento offline; o backend confia no `quantidade_delta` do lote com cap defensivo.

**Exceções.** Código não pertence ao produto → erro na tela; quantidade excedida → bloqueio total do incremento. **Retorno não valida código de barras** (por design). Produto sem código → fluxo de sinalização (`SugestoesCodigoBarras`, §5).

**Riscos.** Alterar a ordem de precedência (Sonda antes dos campos do produto) muda o fator aplicado silenciosamente. Conferência e inventário estão **fora** do escopo do fator.

---

## 3. Fluxo: Edição de rota pelo motorista (Edicaorota)

**Objetivo.** Permitir que o motorista reordene as paradas da rota pelo celular, **sem login**, com aprovação do gestor — sem acoplar o mecanismo ao domínio Romaneio.

**Entrada.** Gestor (via Central ou API) cria uma edição: `POST /api/v1/edicao-rota` → `CriarEdicaoRotaLaboratorio` gera `token_publico` (64 hex), grava `rota_atual` (JSON), `callback_modulo` + `callback_referencia_id` e o timer `expira_em` (default 30 min).

**Processamento.**

```mermaid
stateDiagram-v2
  [*] --> aguardando_envio : criar (token + expira_em)
  aguardando_envio --> em_andamento : motorista valida OTP
  em_andamento --> pendente_aprovacao : submete proposta
  pendente_aprovacao --> aprovada : gestor aprova → callback.aoAprovar()
  pendente_aprovacao --> rejeitada : gestor rejeita (no-op no domínio)
  aguardando_envio --> cancelada
  em_andamento --> cancelada
  pendente_aprovacao --> cancelada
```

1. Motorista abre o link com token → pede OTP (`/otp/gerar`; canais via Mensageria/Otp — WhatsApp, ou `exibir_tela` para o gestor ditar o código) → valida (`/otp/validar`) → sessão `$_SESSION['edicao_rota_<token>']`.
2. Edita a ordem das paradas (rascunhos salvos em `/rascunho`), submete em `/proposta` com justificativa.
3. Gestor aprova/rejeita pelo painel (polling `GET /edicao-rota?ids=`). Aprovação é **idempotente** (aprovar duas vezes = sucesso sem reefeito).
4. Na aprovação, o `EdicaoRotaCallbackRegistry` resolve o callback do módulo (`'romaneio_veiculo'` → `Adapters/Romaneio/...Callback::aoAprovar()`), que: aplica a nova `sequencia_roteirizacao` em `romaneios_pedidos` (parte crítica, roda mesmo se o resto falhar), snapshota `rota_original` apenas se NULL e grava `rota_motorista`.
5. A resposta da aprovação já devolve o payload `refresh` (rota + sequência) para o front re-renderizar o mapa **sem depender de socket** (o Scaledrone REST retorna 500 — decisão consciente).

**Saída.** Rota reordenada no romaneio; edição espelhada como Solicitação global (`EspelhoSolicitacaoGlobalAdapter`) para a fila do gestor.

**Exceções.** Timer expira → tela `sessao_expirada` + espelho de solicitação; rejeição é no-op no domínio; existe atalho `reordenar_direto` que **pula a aprovação** (uso interno do gestor).

**Riscos.** O registro dos callbacks acontece no bootstrap do controller web; a aprovação via API REST não instancia o Sinal — por isso `garantirCallbackRegistrado()` re-registra os callbacks core antes de resolver. Remover esse guard faz a edição "aprovar" sem aplicar a sequência (bug histórico real).

---

## 4. Fluxo: Alocação WMS e integração com WMS externo

**Objetivo.** Registrar movimentos produto ↔ endereço de armazém (modelo **endereço-primeiro**: o operador bipa o endereço e então informa itens) e, quando a empresa opta, receber ordens de um WMS externo e reportar a execução.

### 4.1 Execução interna (AlocacaoWms)

1. Uma missão `ALOCACAO_WMS` (tipo único, id 14) carrega a operação concreta em `configuracoes.operacao_tipo` (`alocacao`, `inventario_produtos`, `inventario_endereco`, `movimentacao`...). O `OperacaoAdapterFactory` resolve o Adapter que sabe validar, montar a view e o movimento.
2. `ConfirmarAlocacaoLaboratorio`: valida (gate transversal + validação específica do adapter), cria o **item dinamicamente** no primeiro vínculo missão+produto (unique `uq_itens_missao_produto`; corrida 23505 → reusa o vencedor), registra em `wms_movimentos` (**append-only**, `integracao_erp/wms = pendente`).
3. **Sessão livre, sem auto-conclusão**: a missão só fecha na finalização explícita do operador.
4. Se `missao.requer_aprovacao`, o movimento nasce retido (`AprovacaoGestor::PENDENTE`); senão vai direto para o cron.
5. Cron `crons/wms_alocacao_integracao.php` (~2 min) empurra movimentos aprovados para ERP e WMS externo (**flags independentes** `integracao_erp`/`integracao_wms`), com estados de estorno (`estorno_pendente/estornado/estorno_falha`).

**Regra de ouro:** o WMS externo é o **dono do saldo** — o Genesis é *push-only*, nunca lê saldo de lá. O "saldo local" é a soma dos movimentos `confirmada` (exibido com aviso). Movimentos cancelados nunca são integrados.

### 4.2 Ordem vinda do WMS externo (IntegracaoWms)

```mermaid
flowchart TD
  W[WMS externo POST /api/webhooks/wms-N] --> IH[InboundHandler: HMAC]
  IH --> N[WmsPayloadNormalizer: versão do contrato]
  N --> R[ReceberOrdemLaboratorio]
  R --> T[tenant fail-closed + sessão sintética]
  T --> AE{config.ativo_entrada?}
  AE -- não --> REJ[rejeitada + motivo persistido]
  AE -- sim --> DED{external_ref já visto?}
  DED -- sim --> JP[ja_processada: devolve veredito original]
  DED -- não --> CO[cria wms_ordens = recebida]
  CO --> ID{produto/endereço conhecidos?}
  ID -- não --> REJ
  ID -- sim --> TR{tradutor para o tipo?}
  TR -- não --> REJ
  TR -- sim --> M[cria missão ALOCACAO_WMS requer_aprovacao=0 → ordem traduzida]
  M --> EX[operador executa → wms_movimentos pendentes]
  EX --> CRON[cron: WmsProvider.enviar]
  CRON --> RP[ReportarOrdemLaboratorio → ordem reportada]
```

Pontos de negócio críticos:
- **Tenant fail-closed**: a empresa vem da linha autenticada do canal (`block_empresa_id` do `webhook_endpoints`); divergência com a URL → rejeita. Sessão sintética é estabelecida **antes** de qualquer Sonda.
- **Dedup por `external_ref`** (unique por empresa): reenvio devolve o veredito original, nunca duplica missão.
- **`requer_aprovacao=0` nas ordens WMS**: se exigisse aprovação do gestor, o gate reteria o push e a ordem nunca fecharia o ciclo `recebida→traduzida→reportada`.
- Tradutores prontos: alocação, movimentação, inventário. **`separacao` está bloqueado por design** — o contrato v1 não carrega `romaneio_id`/`pedido_id` que a `GestaoMissoesSeparacao` exige (decisão de produto pendente).
- Identidade: produto por `importacao_id`, endereço por `wms_localizacoes.codigo`; qualquer identidade desconhecida rejeita a ordem inteira.

---

## 5. Fluxo: Sugestão/ausência de código de barras

**Objetivo.** Deixar o operador reportar problema de código de barras sem parar a operação, e transformar isso em trabalho de correção (missão `AJUSTE_DADOS`).

**Dois ramos** (a diferença importa):
- **`sugestao`/`codigo_errado`** (separação/carregamento, o operador digita o código correto): a missão de ajuste nasce **concluída + aguardando aprovação**; a aprovação do gestor grava o código sugerido no produto.
- **`ausencia`** (produto sem código, na alocação): não há valor a aplicar — a missão `AJUSTE_DADOS` nasce **executável** (`pendente`, com `campos_solicitados=['codigo_barras']`), idempotente por origem.

**Gatilho.** O hook em `MissoesStatus::transicionar` — na conclusão de SEPARACAO/CARREGAMENTO/ALOCACAO_WMS, as sugestões pendentes da missão são convertidas via `GerarAjusteDeSugestoesLaboratorio` (Adapter `Adapters/AjusteDados/`).

---

## 6. Fluxo: Retorno de mercadoria e ocorrências

**Objetivo.** Tratar o que volta da rua: recusas, ausências, avarias.

**Processamento.**
1. Ocorrências registradas na entrega chegam ao Sinal `Ocorrencias` (satélite `OcorrenciasRetorno`: `processar`/`consultar`/`remover_retornos`).
2. `RetornoMissaoServico` cria a missão `RETORNO_ENTREGAS` — **sempre `requer_aprovacao=true`** (`configuracoes.origem='goldie_ocorrencias'`).
3. Na execução do retorno, a bipagem **não valida** código de barras (mercadoria volta como está).
4. Conclusão dispara `processarPosFinalizacaoRetorno`: cria lotes de pendência (`Lotes`) e devolve pedidos ao mapa (`RetornoMapaServico`).

**Riscos.** O cancelamento de pedido joga `romaneios_pedidos.status` de `pendente` para `problema` — detecção de "cancelado preso em rota" precisa olhar **ambos** os status (regra da curadoria de alertas TMS).

---

## 7. Fluxo: Lotes (staging de trabalho)

**Objetivo.** Acumular entidades (pedidos/produtos) numa "área de preparação" por categoria antes de virarem missão — usado por pendências de retorno, faltas e fluxos staged.

**Regras centrais** (Facade `GestorLotes`):
- `obterOuCriarLotePorCategoria()` é idempotente por categoria+referência.
- `inserirEntidadesEmLote()`: entidade já pendente no lote = sucesso (idempotência por mensagem).
- **A lista de um lote é imutável** — só pode ser consumida/concluída, nunca editada.
- `criarMissaoDoLote()` passa por `VerificadorIdempotenciaMissao` (não duplica missão para o mesmo lote) e resolvers de agrupamento por romaneio.
- `lote_movimentacoes` é append-only (auditoria).

---

## 8. Fluxo: OTP (Mensageria)

**Objetivo.** Verificação por código de uso único para qualquer fluxo do sistema (hoje: edição de rota; desenhado como capability genérica).

**Processamento.** `POST /api/v1/otp` (ou `/otp/criar` + `/otp/{id}/reenviar`) → `CriarEEnviarOtpLaboratorio` → canal resolvido pelo `OtpCanalRegistry` (string → classe registrada no bootstrap). Canais: WhatsApp (Evolution), SMTP, `exibir_tela` (devolve o código para o gestor ditar), `log` (dev/fallback). Configuração por **contexto** (ex.: edição de rota usa `validade=300s, max_tentativas=3, codigo=6`).

**Exceções.** Código expirado/tentativas estouradas → erro de validação; canal indisponível → o bootstrap garante fallback `LogCanal` registrado por último (não sobrescreve provedores reais).

**Riscos.** `exibir_tela` usa estado estático no processo — não é seguro sob concorrência no mesmo worker ([06 §8](06-apis.md#8-gotchas-específicos-da-api)).

---

## 9. Fluxo: Inventário cíclico

**Objetivo.** Contagens recorrentes de estoque com tratamento de divergência em ciclo fechado.

**Processamento.** `CriarPlano` (universo de endereços/produtos via `UniversoProviderRegistry`) → `GerarCicloDiario` (cron `0-4-inventario_ciclico_diario.php`, 04:00) seleciona alvos por Strategy (aleatória, sequencial, menos-recente, prioridade manual) e distribui (quantidade fixa/percentual/período) → `MissaoInventarioBridge` cria missões de contagem → divergências seguem a máquina `aberta → recontagem → escalada → ajustada/resolvida/descartada` (`AvaliarDivergencia`, `GerarRecontagem`, `EscalonarDivergencia`, `AplicarAjustesAprovados`) → `FecharCiclo`.

**Ponto de extensão.** `EsperadoProviderRegistry` define a fonte do saldo esperado — é o *seam* planejado para um dia ler saldo do WMS externo sem mudar o fluxo.

---

## 10. Fluxo: Dashboard operacional (cache-first)

**Objetivo.** KPIs e séries da expedição sem custo de query em tempo real.

**Processamento.** Cron diário (`dashboard_expedicao_diario.php`, com guard de produção e janela configurável) materializa agregados em tabelas de cache; os ~45 endpoints `/api/v1/dashboard/*` leem o cache. O front usa busca em 2 fases: `?light=1` (números imediatos, sem séries) → full em background. Período padrão da UI: semana ISO anterior; persistência em `sessionStorage`; querystring vence.

**Riscos.** Cron é idempotente por janela (delete+insert); rodá-lo com host `dev-` em produção é bloqueado pelo guard. Dados "estranhos" no dashboard = primeiro verificar se o cron rodou (log em `logs/dashboard_expedicao_diario.log`).

---

## 11. Fluxo: Solicitações (fila global de aprovações)

**Objetivo.** Centralizar tudo que o operador pede e o gestor decide: desvínculo de missão, recomeço, acesso a retira/pesagem/alocação, cancelamento, edição de rota.

**Processamento.** `criarSolicitacao` → fila `pendente` → `aprovarSolicitacao`/`rejeitarSolicitacao`. A decisão dispara uma **Strategy por tipo** (`EdicaoRotaAprovadaStrategy`, `AcessoPesagemAprovadoStrategy`...) que executa o side-effect no domínio correspondente. `LiberacaoSocketNotifier` avisa o app do operador em tempo real. Histórico em `SolicitacaoHistoricoSonda`.
