# Implementação backend — Fase 1: Saída plugável + config por empresa

## Arquivos criados

- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Migrations/001_create_integracao_wms_config.sql` — DDL da tabela `integracao_wms_config` com UNIQUE em `block_empresa_id`; block_* sem DEFAULT
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Config/IntegracaoWmsEnvConfig.php` — Defaults de URL e token via env (espelha EvolutionConfig); elimina os últimos hardcodings de URL/token
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Config/IntegracaoWmsConfigSonda.php` — Sonda por empresa; `customerControl=false`; `obterConfig()` retorna `configDefault()` se empresa sem linha ou tabela ausente; `resolverEmpresaIdAtual()` espelha ScanCotaConfigSonda
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Config/Laboratorios/ConsultarConfigLaboratorio.php` — Thin lab: delega a `obterConfig()`; ponto de entrada da futura tela de config
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Config/Laboratorios/SalvarConfigLaboratorio.php` — Thin lab: valida provedor registrado (guard coerência) e delega a `salvarConfig()`; upsert idempotente
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Provedores/WmsProviderInterface.php` — Contrato: `nome(): string; enviar(WmsEnvioContexto): WmsEnvioResultado`
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Provedores/WmsProviderRegistry.php` — Registry estático; espelha OtpCanalRegistry (registrar/temRegistro/resolver/listar/reset)
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Provedores/WmsProviderFactory.php` — Factory com `setOverride()/clearOverride()` para testes sem HTTP; espelha OtpCanalFactory
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Provedores/WmsEnvioContexto.php` — VO: destino, opc, payload, url, bearerToken, externalRef (seam Fase 2)
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Provedores/WmsEnvioResultado.php` — VO: ok, erro, raw; mapeamento ok→EstadoIntegracao é da strategy, não do provedor
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Provedores/Galpao/GalpaoProvider.php` — Strangler-fig: extração mecânica de `HookSincronizadorBase::enviarRequisicaoJson()` byte-a-byte; URL/token vêm do contexto; log idêntico ao original
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Provedores/Http/HttpWmsProvider.php` — Stub Fase 2: retorna `ok=false` com erro explícito; seam já presente no contexto (externalRef, url, bearerToken)
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/IntegracaoWms.php` — Controller/bootstrap: `bootstrap()` registra galpao/http_wms com guard; `__call()` delega a `start()` (requisito SPA)

## Arquivos alterados

- `componentes/Constelacoes/Logistica/MissaoLogistica/Strategies/HookSincronizadorBase.php` — Removida inicialização hardcoded de `$urlWebhook` e `= 'gnesistk'` de `$bearerToken`; ambos agora lidos via `IntegracaoWmsEnvConfig::urlErpLegado()` e `bearerToken()`; campos mantidos para as strategies legadas (Falta, Devolucao, etc.)
- `componentes/Constelacoes/Logistica/MissaoLogistica/Strategies/ErpAlocacaoWmsHookStrategy.php` — Constructor não define mais URL; `sincronizar()`/`estornar()` resolvem config por tenant (`IntegracaoWmsConfigSonda::resolverEmpresaIdAtual()`), constroem `WmsEnvioContexto` e delegam a `WmsProviderFactory::build($config->provedorSaida)`; mapeamento resultado→EstadoIntegracao idêntico ao anterior
- `componentes/Missoes/Logistica/sinais/IntegracaoWms/Config/Laboratorios/WmsAlocacaoWmsHookStrategy.php` → na path correta: `componentes/Constelacoes/Logistica/MissaoLogistica/Strategies/WmsAlocacaoWmsHookStrategy.php` — Idem ErpAlocacaoWmsHookStrategy; usa `endpoint_wms ?? IntegracaoWmsEnvConfig::urlWmsSync()`
- `componentes/Missoes/Plataforma/sinais/Api/Bootstrap.php` — Adicionado `use IntegracaoWms` e chamada `IntegracaoWms::bootstrap()` com guard `class_exists`; mesmo padrão dos canais OTP

## Desvios do techplan

**Nenhum desvio de spec.** Observações de implementação:

1. **`HookSincronizadorBase` não perdeu os campos `$urlWebhook`/`$bearerToken`**: o techplan diz "remover ... chumbados", interpretado como "remover os valores hardcoded literais", não os campos. Remover os campos quebraria as 6 strategies legadas (Falta, Devolucao, Recusa, PesoSeparacao, AjusteDados, SeparacaoIniciada) que estendem a base e chamam `$this->enviarRequisicaoJson()`. Os campos foram mantidos; apenas a inicialização passou a vir de `IntegracaoWmsEnvConfig`.

2. **`ErpAlocacaoWmsHookStrategy` e `WmsAlocacaoWmsHookStrategy` não chamam `parent::__construct()`**: já era assim antes desta Fase. O construtor das duas strategies não invocava o pai; mantivemos a mesma decisão para não introduzir a inicialização de URL base (que seria sobrescrita de qualquer forma pela lógica de provedor por tenant).

3. **Gate `ativo_saida` adicionado em `sincronizar()`/`estornar()`**: o techplan menciona o flag mas não detalha onde gatear; adicionado nas strategies para completude. `configDefault()` tem `ativo_saida=1` → empresas sem config continuam enviando normalmente.

4. **`crons/wms_alocacao_integracao.php` não alterado**: o techplan menciona "(a) carregar a config do tenant" no cron. Porém, após a refatoração, as strategies resolvem a config internamente via `resolverEmpresaIdAtual()` (que lê a sessão sintética já estabelecida pelo loop do cron). Não há necessidade de mudança no cron para Fase 1. A alteração do cron para ponto (b) — `ReportarOrdemLaboratorio` — é Fase 2.

## Verificações de sintaxe executadas

Todos os arquivos abaixo passaram em `php -l`:

- `IntegracaoWmsEnvConfig.php` → OK
- `IntegracaoWmsConfigSonda.php` → OK
- `ConsultarConfigLaboratorio.php` → OK
- `SalvarConfigLaboratorio.php` → OK
- `WmsProviderInterface.php` → OK
- `WmsProviderRegistry.php` → OK
- `WmsProviderFactory.php` → OK
- `WmsEnvioContexto.php` → OK
- `WmsEnvioResultado.php` → OK
- `GalpaoProvider.php` → OK
- `HttpWmsProvider.php` → OK
- `IntegracaoWms.php` → OK
- `HookSincronizadorBase.php` → OK
- `ErpAlocacaoWmsHookStrategy.php` → OK
- `WmsAlocacaoWmsHookStrategy.php` → OK
- `Bootstrap.php` (Api) → OK
- `wms_alocacao_integracao.php` (cron) → OK
- Strategies legadas (Falta, Devolucao, Recusa, PesoSeparacao, AjusteDados, SeparacaoIniciada) → todas OK

## O que ficou para Fase 2

- `Migrations/002_create_wms_ordens.sql` e `003_seed_webhook_endpoint_dev.sql`
- `Sondas/WmsOrdemSonda.php` (dedup por external_ref, transições de status)
- `Webhooks/WmsPayloadNormalizer.php` (implements Api\Webhooks\PayloadNormalizer)
- `Validators/OrdemEnvelopeValidator.php` e `OrdemIdentidadeValidator.php`
- `Laboratorios/ReceberOrdemLaboratorio.php` (tenant sintético → dedup → traduzir → criar missão → gravar ordem)
- `Laboratorios/ReportarOrdemLaboratorio.php` (avança ciclo wms_ordens no report de volta)
- `HttpWmsProvider` real (HMAC out + external_ref)
- Alteração do cron para chamar `ReportarOrdemLaboratorio` após push com `enviado`
- Seed `webhook_endpoints` por empresa para o receiver

## O que ficou para Fase 3

- `Tradutores/OrdemTipo.php` + `TraducaoOrdemInterface.php` + `TraducaoOrdemFactory.php`
- `AlocacaoOrdemTradutor.php`, `SeparacaoOrdemTradutor.php`, `MovimentacaoOrdemTradutor.php`, `InventarioOrdemTradutor.php`

## O que ficou para Dev Frontend (Fase 1 UI)

- `html/integracaowms.php` + `html/integracaowms.js` — tela de config por empresa
- Conectar a tela a `ConsultarConfigLaboratorio` e `SalvarConfigLaboratorio` via controller `IntegracaoWms::start()`

---

## Correções pós-review (06-review.md) — aplicadas em 2026-06-30

### B1 — `provedorSaida` camelCase → `provedor_saida` snake_case (4 call sites)

**Problema**: `$config->provedorSaida` num `stdClass` de `PDO::FETCH_CLASS` retorna `null` (Undefined property); `WmsProviderFactory::build(null)` lança `TypeError` abortando o lote inteiro.

**Correção**: substituído `$config->provedorSaida` por `$config->provedor_saida` nos 4 call sites:
- `ErpAlocacaoWmsHookStrategy.php` — `sincronizar()` e `estornar()`
- `WmsAlocacaoWmsHookStrategy.php` — `sincronizar()` e `estornar()`

Docblock em `WmsProviderFactory.php` corrigido para `$config->provedor_saida` com nota explícita sobre snake_case.

### B2 — Coluna `usuario_cadastro` ausente da migration

**Problema**: `ModelInteligencia::create()` injeta `usuario_cadastro` incondicionalmente; coluna ausente → PDOException → insert sempre falha.

**Correção em `Migrations/001_create_integracao_wms_config.sql`**:
- Adicionada `usuario_cadastro VARCHAR(255)` ao DDL (ao lado de `usuario_atualizacao`)
- Adicionado `ALTER TABLE integracao_wms_config ADD COLUMN IF NOT EXISTS usuario_cadastro VARCHAR(255);` no final, para corrigir ambientes onde a tabela já foi criada sem essa coluna
- Também corrigidas as vírgulas soltas (nit n1): commas agora ficam na mesma linha da definição de coluna, antes do comentário `--`

### M2 (backend) — Guard contra provedor stub em SalvarConfigLaboratorio

**Problema**: `http_wms` está registrado mas sempre retorna `ok=false`; salvar esse provedor travaria todos os pushes do tenant silenciosamente.

**Correção em `Config/Laboratorios/SalvarConfigLaboratorio.php`**: adicionada constante `PROVEDORES_STUB = ['http_wms']` e guard que rejeita imediatamente com mensagem clara: `"Provedor 'http_wms' não está disponível nesta fase (implementação pendente). Use 'galpao'."`. O guard de "provedor registrado" permanece como segunda camada.

### Nits aplicados

**n1 (vírgulas na migration)** — resolvido junto com B2 acima.

**m2 (dead code `timeoutSegundos()`)** — método removido de `Config/IntegracaoWmsEnvConfig.php`. A constante `TIMEOUT_SEGUNDOS = 10` permanece (usada no teste unitário e como referência documental). O hardcode `CURLOPT_TIMEOUT, 10` em `GalpaoProvider.php` é mantido (byte-parity com o legado; é o valor de `TIMEOUT_SEGUNDOS`).

**m1 (log GalpaoProvider perdia Missão/Operação/Ref)** — restaurado:
- `WmsEnvioContexto.php`: adicionados campos opcionais `?int $missaoId` e `?string $referencia` ao VO (parâmetros opcionais no final do constructor — backwards-compatible).
- `ErpAlocacaoWmsHookStrategy.php`: passa `$missaoId` e `(string) $importacaoId` ao construir `WmsEnvioContexto` (em `sincronizar()` e `estornar()`).
- `WmsAlocacaoWmsHookStrategy.php`: passa `$missaoId` e `(string) $alocacaoId` ao construir `WmsEnvioContexto` (em `sincronizar()` e `estornar()`).
- `GalpaoProvider.php`: log agora emite `GalpaoProvider | Missão #N | Operação: opc | Ref: ref | HTTP: ... | Resposta: ...` quando missaoId/referencia estão presentes.

### Verificações pós-correção
- `php -l ErpAlocacaoWmsHookStrategy.php` → OK
- `php -l WmsAlocacaoWmsHookStrategy.php` → OK
- `php -l WmsProviderFactory.php` → OK
- `php -l SalvarConfigLaboratorio.php` → OK
- `php -l IntegracaoWmsEnvConfig.php` → OK
- `php -l WmsEnvioContexto.php` → OK
- `php -l GalpaoProvider.php` → OK

---

## Garantia de não-regressão

**Empresa sem `integracao_wms_config`**: `obterConfig()` retorna `configDefault()` → `provedor_saida='galpao'`, `ativo_saida=1`, `endpoint_erp=null`. As strategies resolvem `WmsProviderFactory::build('galpao')` → `GalpaoProvider`. A URL cai em `IntegracaoWmsEnvConfig::urlErpAlocacao()` / `urlWmsSync()` que geram as mesmas URLs de hoje. Token = `IntegracaoWmsEnvConfig::bearerToken()` = `'gnesistk'`. `GalpaoProvider::enviar()` executa o mesmo curl que `enviarRequisicaoJson()` fazia — idêntico em URL, headers e body.

**Guard de deploy (tabela ausente)**: `obterConfig()` captura `\Throwable` e retorna `configDefault()` quando a migration ainda não foi aplicada — o worker não abortará.

**Strategies legadas intactas**: FaltaHookStrategy, RecusaHookStrategy e demais que estendem `HookSincronizadorBase` continuam usando `$this->urlWebhook` e `$this->enviarRequisicaoJson()` sem alteração de comportamento. O construtor da base agora usa `IntegracaoWmsEnvConfig::urlErpLegado()` que produz a mesma URL de antes.
