# 08 - Integrações

> Tudo que entra e sai do Genesis: ERP legado, WhatsApp, TMS, WMS externo, Google (Maps/OAuth), Docuseal, realtime (Scaledrone) e os crons que movem essas integrações. Para cada uma: objetivo, direção, fluxo, autenticação, tratamento de erro e limitações.
>
> Relacionados: [06 - APIs](06-apis.md#6-webhook-inbound-genérico) (infra de webhook inbound), [02 - Fluxos](02-fluxos-do-sistema.md), [13 - Dívidas Técnicas](13-dividas-tecnicas.md) (riscos de segurança consolidados).

## 0. Panorama

| # | Integração | Direção | Transporte / Auth |
|---|---|---|---|
| 1 | ERP legado ("Goldie"/galpão) | in + out | HTTP JSON (Bearer) + **MySQL direto** (`mysql_goldie_atual`) |
| 2 | WhatsApp (Evolution API) | in + out | HTTP, header `apikey` — **3 implementações paralelas** |
| 3 | TMS (`galpaotms`) | in + out | HTTP Bearer + leitura MySQL remoto (+ orquestrador **N8N** externo) |
| 4 | WMS externo | in + out | Webhook HMAC (`X-Signature`) + providers plugáveis |
| 5 | Docuseal / Dropbox / Gmail | out / stub / in-out | JWT HS256 / — / OAuth2 Google |
| 6 | Google Maps | out (browser) | API key (`KEYMAPSGOOGLE`) |
| 7 | Realtime (Scaledrone) | server → browser | REST Basic auth |
| 8 | Crons | out | HTTP Bearer / CLI |
| 9 | Boletos/bancos | **não existe** | CRUD interno apenas |

**O "jeito Genesis" de plugar integração** é o padrão Registry + Interface + Factory (OTP: `OtpCanal*`; WMS: `WmsProvider*`; Edição de rota: `EdicaoRotaCallback*`; Mensageria: `ConectorInterface` + `FabricaDeConectores`) com configuração por `define()` global e, para inbound, a tabela `webhook_endpoints` + `InboundHandler` genérico. **Integração inbound nova não deve criar `index.php` solto** — deve passar pela infra genérica.

## 1. ERP legado ("Goldie" / galpão)

**Objetivo.** Coexistência com o ERP anterior (MySQL, pasta `antigo/`) durante a migração. O Genesis é o TMS/WMS novo; o ERP continua dono de cadastros e faturamento.

**A regra-mestra de identidade:**
- `importacao_id` (Genesis) **=** `codigoMD5` (ERP). É a única chave confiável de cruzamento (produtos, pedidos, veículos).
- `numero_pedido` **não é único** (reuso entre empresas/anos) — serve só para exibição.
- Timestamps do ERP são gravados em **UTC** — converter para `America/Sao_Paulo` com `DateTime` na exibição.

**Direção OUT (Genesis → ERP):**
- **MySQL direto:** Sondas apontadas para a conexão `mysql_goldie_atual` com tenant desligado — `PedidoErpSonda` (`entity="pedidos"`, métodos `buscarPorCodigoMD5`/`atualizarPorCodigoMD5`, chave `(codigoMD5, block)`), `ProdutoErp` (`1goldie_produtos`).
- **HTTP hook:** `ErpAlocacaoWmsHookStrategy` empurra alocações WMS confirmadas; cada item leva `importacao_id` — itens sem ele são pulados com log.

**Direção IN (ERP → Genesis):** webhooks roteados por `sinalGxData` no Sinal `Central`:
- `sincronizarVeiculo` — upsert idempotente de veículos; suporta `acao=remover` (soft delete `block=0`).
- `sincronizarSeparador` — sincroniza usuários/separadores.
- Sincronização de produtos/códigos de barras em `Produtos/sinais/Sincronizacoes`.
- Auth: Bearer/JWT. Logs em arquivo local do componente (`Central/Logs/*.log`, sem rotação).

**Curadoria de alertas TMS** (`CuradoriaAlertasTmsLab`): conecta num **MySQL remoto com credenciais hardcoded no código** para detectar pedidos-fantasma e bloqueios do ERP (cache TTL 300s). Regra de negócio: cancelamento joga `romaneios_pedidos.status` de `pendente` para `problema` — a detecção de "cancelado preso em rota" olha os dois status.

**Riscos.** Escritas cross-database sem transação distribuída (Genesis e ERP podem divergir se um lado falhar); credenciais hardcoded; filtros no ERP precisam de `COALESCE` (ex.: `pedidos.comodato` é NULL em quase tudo).

## 2. WhatsApp (Evolution API)

Três implementações **paralelas** — duplicação conhecida:

1. **Webhook inbound cru** (`webhooks/whatsapp/evolution/`): recebe eventos (`messages.upsert`) e hoje **apenas loga** em `webhook_log.txt` e responde 200. Sem validação de assinatura. Processamento comentado/pendente.
2. **Canal OTP** (`Mensageria/Otp/Canais/Whatsapp/Evolution.php`): envia códigos OTP via `POST /message/sendText/{instance}` (header `apikey`). Normaliza para E.164 (assume DDI 55). **Não retenta** — falha vira evento `falha_envio`. Config por `define()`: `GENESIS_WHATSAPP_EVOLUTION_BASE_URL/_API_KEY/_INSTANCE/_TEMPLATE/_TIMEOUT`; se incompleta, o bootstrap mantém o stub `LogCanal` no lugar (dev não quebra).
3. **Central de Mensageria** (`Mensageria/Central/`): motor multi-canal genérico com Guzzle (`ConectorWhatsappEvolution` — envio, status de conexão, QR code de pareamento), fábrica de conectores e webhook inbound próprio (também só loga).

O registry de canais OTP (`OtpCanalInterface`/`Registry`/`Factory`) é a referência do padrão: `registrar(nome, classe)` no bootstrap, resolução por string, `setOverride()` para testes. Canais: `log`, `whatsapp`, `email`, `sms`, `exibir_tela`.

## 3. TMS (`galpaotms`)

O "TMS" é o sistema legado exposto como TMS. Fluxos:

| Fluxo | Direção | Mecanismo |
|---|---|---|
| Veículos / separadores | IN | webhooks `sincronizarVeiculo`/`sincronizarSeparador` (§1) |
| Produtos | OUT | POST form-urlencoded `opc=sincronizar_tms` para `gerenciar.php` do legado (Bearer) |
| Todos os pedidos | OUT | `opc=sincronizar_todos_tms` |
| Novos pedidos disponíveis | IN (poll) | cron `crompedidos_logistica.php` — **a fila é populada por um orquestrador N8N externo**; o cron promove `pendente` → `aguardando` e publica markers via socket |
| Alertas (fantasmas/bloqueios) | IN | `CuradoriaAlertasTmsLab` (MySQL direto, §1) |
| Ocorrências de retorno | trigger | cron `ocorrencias_retorno.php` |

Auth por Bearer token que **define o tenant** (ex.: token da alper → customer 2). Erro: HTTP ≥ 400 → `exit(1)` + STDERR; o "retry" é a periodicidade do cron.

## 4. WMS externo (`Logistica/sinais/IntegracaoWms`)

A integração mais bem arquitetada do sistema — leia `IntegracaoWms/README.md` como fonte primária. **Opt-in por empresa** (config `IntegracaoWmsConfigSonda`: `provedor_saida`, `endpoint_erp/wms`, `ativo_entrada/saida`); quem não plugar segue no push legado ("galpão"). Fluxo de negócio completo em [02 §4](02-fluxos-do-sistema.md#4-fluxo-alocação-wms-e-integração-com-wms-externo).

**IN (ordem → missão):** `POST /api/webhooks/wms-<empresaId>` → HMAC (`X-Signature: sha256=<hmac_sha256(body, secret)>`, timing-safe) → `WmsPayloadNormalizer` (versão de contrato; major desconhecida rejeita) → `ReceberOrdemLaboratorio` (tenant fail-closed pela linha do canal, dedup por `external_ref`, validação de identidade, `TraducaoOrdemFactory`). Ciclo `wms_ordens`: `recebida → traduzida → reportada` | `rejeitada` (sempre com motivo). Respostas: 200 aceita / 422 rejeitada ou `ja_processada` / 401 HMAC / 404 canal.

**OUT (report):** providers plugáveis — `GalpaoProvider` (legado, paridade byte-a-byte com o push antigo) e `HttpWmsProvider` (genérico: Bearer + `X-Signature` HMAC + `external_ref`; segredo em `GENESIS_WMS_HTTP_HMAC_SECRET`). Providers **nunca lançam** — retornam resultado com erro. Registrados em `IntegracaoWms::bootstrap()` (idempotente).

**Worker:** `crons/wms_alocacao_integracao.php` (~2 min): dois passes sobre `wms_movimentos` (push de `pendente/falha`; estorno de `estorno_pendente/estorno_falha`), limite 50/ciclo, sessão sintética por tenant, e fecha o ciclo chamando `ReportarOrdemLaboratorio`. **Precisa chamar `IntegracaoWms::bootstrap()`** — em CLI ninguém mais registra os providers.

**Pendências antes do 1º WMS real:** popular `GENESIS_WMS_HTTP_HMAC_SECRET` (sem ele a "assinatura" de saída cai no bearer), tradutor `separacao` bloqueado (decisão de produto), atualização/cancelamento de ordem deferidos, read API de saldo inexistente **por design** (push-only).

## 5. Docuseal, Dropbox, Gmail (`Missoes/Integracoes`)

- **Docuseal** (assinatura de documentos, OUT): gera JWT HS256 para embutir o builder/form da DocuSeal via CDN. **Chave hardcoded no construtor** e payload com e-mails fixos — precisa parametrização. Sem webhook de status de assinatura (só embed). Persistência em `DocusealSubmissionsSubmitters`/`Templatesdocs`.
- **Dropbox**: stub — só renderiza a tela de setup, sem API real.
- **Gmail** (envio de e-mail via conta Google, OAuth2): `league/oauth2-google`; callback em `/missao/integracoes/gmail/callback` troca `code` por `access_token`+`refresh_token` (exige refresh — senão força re-consent) e salva em `Emails.client_auth`. O `client_secret` (JSON do Google Cloud) vem do banco.
- **OAuth standalone** (`system/o2auth.php` + `oauth2callback.php`): fluxo Google separado usado pelo ConnectionAccounting (escopo Gmail completo, `access_type=offline`). ⚠️ Aponta para `/home/goldie/www/galaxia/...` — path histórico, ver §8.

## 6. Google Maps

Roteirização e desenho de rotas no painel logístico (browser → Google). Key na constante `KEYMAPSGOOGLE`, injetada em `window.googleMapsKey` e no `<script>` dos temas (libraries places/drawing/geometry; carregado só nas rotas `/logistica/central|admin`). `DirectionsService`/`DirectionsRenderer` em `controladores/_Src/maps.js`; Edicaorota carrega o Maps **async** sob demanda — usar `google.maps.importLibrary()` para não estourar a primeira chamada ([Armadilha 17](12-armadilhas-conhecidas.md)). Restringir a key por HTTP referrer no Google Cloud (ela é pública por natureza).

## 7. Realtime — Scaledrone (não é socket.io)

- Transporte SaaS **Scaledrone**; na prática unidirecional: o servidor publica via REST (`POST api2.scaledrone.com/{channel}/{room}/publish`, Basic auth channel_id:secret) e o browser escuta.
- **O payload é o mesmo protocolo de diretivas Galaxia** ([07 §4](07-front-end.md#4-o-protocolo-de-resposta-requestgalaxiaact)) — aplicado por `RequestGalaxiaAct`. Conexão automática para cada elemento `.rooms-socket`.
- **Rooms:** `galaxia_logistica_board`, `galaxia_logistica_central`, `galaxia_logistica_missao_{id}`, `galaxia_logistica_separacao_usuario_{id}`, `galaxia_logistica_liberacoes_user_{id}`; rooms de dev por username. Handler dedicado `CentralSocketHandler` (em `maps.js`) trata `route_update`/`route_sequence_updated` fora do despacho declarativo.
- **Publicar é best-effort**: try/catch + `error_log`, nunca lança; o negócio persiste **antes** de publicar (write-before-fire). Incidente histórico: troca de canal retornou HTTP 500 e o realtime morreu em silêncio — por isso a aprovação de edição de rota devolve o payload `refresh` na própria resposta HTTP, sem depender do socket.
- ⚠️ Canal e secret hardcoded em **três lugares que precisam ficar em sincronia**: `GalaxiaRoute::enviarSocket()`, `GalaxiaRoute::lerSocket()` e `EdicaoRotaSocketNotifier` (este faz POST direto porque roda em contexto REST sem `$galaxia`).
- Docs internos: `docs/websocket/01-usabilidade.md`, `02-arquitetura.md`.

## 8. Crons

Agendamento por convenção de nome (`MM-HH-*.php`) instalado por `crons/auto.sh` — detalhes em [09 - Configurações §6](09-configuracoes.md#6-agendamento-crons).

| Script (`crons/`) | O que faz | Frequência | Observações |
|---|---|---|---|
| `wms_alocacao_integracao.php` | Worker WMS: push + estorno de `wms_movimentos`, report de `wms_ordens` | ~2 min | Idempotente; retry no próximo ciclo; exige bootstrap |
| `crompedidos_logistica.php` | Poll de pedidos novos (fila do N8N) → socket + status `aguardando` | 1 min | Tenant pelo token; comentário sugere `flock` (não implementado) |
| `ocorrencias_retorno.php` | Dispara `OcorrenciasRetorno/processar` (missões de retorno) | sem prefixo → **a cada minuto** | Verificar se a frequência é intencional |
| `ocorrencias_retry.php` | Retry de ocorrências com backoff exponencial (1/5/15 min, máx. tentativas, lote 50) | ~5 min | |
| `notificar_pendencias_connection.php` | Notifica pendências do ConnectionAccounting | sem prefixo | Token/URL hardcoded |
| `0-4-inventario_ciclico_diario.php` | Gera ciclos de inventário por tenant → missões | 04:00 | Idempotente (hash plano+dia) |
| `25-4-cache_dashboard.php` | Materializa cache de dashboard (sistema antigo) | 04:25 | Bearer hardcoded |
| `dashboard_expedicao_diario.php` | Cache do dashboard de expedição | diário | Padrão canônico: sessão sintética multi-tenant + guard de produção |

`crons_auto/`: 13 clientes cURL que aquecem o cache de dashboard do **sistema antigo** (`alper.goldie.com.br/rotas/apis/...`), um por mês/horário escalonado.

**Riscos transversais:** `auto.sh` aponta `CRON_DIR=/home/goldie/www/galaxia/crons` (não `genesis/crons`) — o crontab pode estar rodando **outra cópia** do projeto; mesma divergência em `o2auth.php`. Verificar `crontab -l` antes de confiar. Nenhum cron usa `flock` (risco de sobreposição). Tokens Bearer hardcoded em todos.

## 9. Boletos / bancos

**Não há integração bancária** (CNAB, remessa/retorno, APIs de banco). `Cobrancas/Boletos` é CRUD interno; `Boletos/BoletosPainel` é só painel. A emissão/liquidação bancária, se existe, acontece no ERP legado. O elo financeiro com o ERP é somente sincronização de dados via `importacao_id` (satélites `Contasareceber*`, `FormasRecebimento`).

## 10. Como criar uma integração nova (resumo)

1. **Inbound:** inserir linha em `webhook_endpoints` (channel, secret, `signature_algo`, `target_lab`, `payload_mapper` opcional) e criar o Laboratório alvo que recebe `($payload, $endpoint)` — o tenant vem do `$endpoint`. Nada de endpoint solto.
2. **Outbound:** implementar a Interface do domínio (canal OTP, provider WMS, conector de mensageria...) e registrá-la no bootstrap idempotente do Sinal. Nunca lançar do provider — retornar resultado com erro.
3. **Config:** `define()` globais com prefixo `GENESIS_<DOMINIO>_*`, defaults seguros (stub/log) quando incompleta.
4. **Worker/retry:** cron idempotente com estado por linha (`pendente/enviado/falha`), sessão sintética por tenant e limite por ciclo.
Passo a passo detalhado em [10 - Guia de Desenvolvimento](10-guia-de-desenvolvimento.md).
