# Implementação backend

## Arquivos criados

- `componentes/Missoes/Dashboards/sinais/Expedicao/Migrations/001_dashboard_expedicao_setup.sql` — cria tabela `dashboard_expedicao_setup` com UNIQUE(block) e seed de blocks existentes (via grupos_regioes) como 'pronto'
- `componentes/Missoes/Dashboards/sinais/Expedicao/Enums/DashboardExpedicaoSetupStatus.php` — enum backed string com cases AGUARDANDO / EM_PROGRESSO / PRONTO / FALHOU
- `componentes/Missoes/Dashboards/sinais/Expedicao/Sondas/DashboardExpedicaoSetupSonda.php` — model V2 de `dashboard_expedicao_setup` (customerControl=true, companyControl=true, userControl=false)
- `componentes/Missoes/Dashboards/sinais/Expedicao/Laboratorios/AutoSetupExpedicaoLaboratorio.php` — orquestra estado de provisionamento; métodos: verificar / iniciar / executarSync / garantirGrupoPadrao / marcarPronto / marcarFalhou
- `componentes/Missoes/Dashboards/sinais/Expedicao/Satelites/StatusSetupSatelite.php` — despacha verificar/iniciar; block sempre lido da sessão (tenant safety)

## Arquivos alterados

- `componentes/Missoes/Dashboards/sinais/Expedicao/Expedicao.php` — `start()` ganha setup guard no topo: chama verificar(), se não 'pronto' chama iniciar() e renderiza html/configurando; 2 imports adicionados
- `crons/dashboard_expedicao_diario.php` — parser ganha `--modo-setup`; top-level try/catch + register_shutdown_function; branch que invoca executarSync + garantirGrupoPadrao + AtualizarCache + marcarPronto antes de sair; comportamento legacy (sem --modo-setup) inalterado

## Migration executada

A migration `001_dashboard_expedicao_setup.sql` foi executada em `dev_gx_goldie`:
- Tabela criada com sucesso.
- Seed inseriu 1 linha (block existente com grupos_regioes) com status='pronto'.

## Confirmação de schema externo (risco #3 do techplan)

`grupos_regioes.regioes_ids` é **jsonb** (confirmado via `information_schema.columns` em `dev_gx_goldie`). A implementação de `garantirGrupoPadrao` usa `json_encode(array_of_ints)` no lado PHP e cast `::jsonb` no INSERT — alinhado com o esperado.

## Decisões de implementação que se desviaram do techplan

1. **`marcarPronto` e `marcarFalhou` como métodos públicos separados** — o techplan descreve o fluxo do cron como "ao final transiciona pronto" sem nomear o método explicitamente. Extrair em métodos próprios foi necessário para o cron chamar diretamente e para o handler de shutdown chamar marcarFalhou sem acoplar ao fluxo principal. Sem impacto funcional.

2. **`upsertSetup` privado no Laboratório** — helper interno que centraliza o INSERT ON CONFLICT + UPDATE para evitar duplicação entre `iniciar` e eventuais calls defensivas. Não previsto explicitamente no techplan mas consistente com o padrão "idempotente".

3. **`Expedicao::start` lê block via `SessionGalaxia::get` dentro de try/catch** — o controller não tem acesso direto a `$galaxiaRoute->getSessionInfo` (esse campo não existe no objeto de forma padronizada). A leitura direta via `SessionGalaxia::get('galaxiaUserInfo')` é o mesmo padrão que o Satélite usa — sem desvio real de padrão V2, apenas adequação ao que o framework provê.

4. **`html/configurando` renderizado via `$this->galaxiaRoute->visual()`** — o techplan menciona "Render html/configurando.php" sem especificar o método. Usei `visual()` que é exatamente o que `Expedicao::start()` já usa para `html/estrutura`. A view em si está fora de escopo (frontend a implementa).

## Comandos de verificação executados

- `php -l Enums/DashboardExpedicaoSetupStatus.php` → OK
- `php -l Sondas/DashboardExpedicaoSetupSonda.php` → OK
- `php -l Laboratorios/AutoSetupExpedicaoLaboratorio.php` → OK
- `php -l Satelites/StatusSetupSatelite.php` → OK
- `php -l Expedicao.php` → OK
- `php -l crons/dashboard_expedicao_diario.php` → OK
- `php crons/dashboard_expedicao_diario.php --inicio=2026-05-01 --fim=2026-05-01` → OK (erros=0, comportamento legacy preservado)
- `php crons/dashboard_expedicao_diario.php --modo-setup` (sem --block) → exit 2 com mensagem de erro (validação OK)
- Migration executada em `dev_gx_goldie`: tabela criada, 1 seed row inserida

## Pontos abertos para o frontend

### View obrigatória para o flow funcionar
`html/configurando.php` (e `html/configurando.js`) precisam ser criados antes de qualquer teste manual de primeiro acesso. Enquanto não existirem, o `visual()` em `Expedicao::start()` vai lançar erro ao tentar renderizar um bloco inexistente.

### Endpoints de polling (StatusSetupSatelite)

```
POST /labs/apis/Dashboards/Expedicao/StatusSetupSatelite/verificar
POST /labs/apis/Dashboards/Expedicao/StatusSetupSatelite/iniciar
```

Payload: vazio (sem parâmetros — block vem da sessão).

Resposta de ambas as rotas:
```json
{
  "status": "aguardando" | "em_progresso" | "pronto" | "falhou",
  "iniciado_em": "2026-05-04T14:32:01Z" | null,
  "finalizado_em": "2026-05-04T14:32:45Z" | null,
  "erro": "mensagem de erro" | null,
  "mensagem": "Processando dados das últimas 7 entregas..."
}
```

### Comportamento esperado do polling
- Polling a cada 4s em `verificar`.
- Em `pronto` → `window.location.reload()`.
- Em `falhou` → mostrar `erro` + botão "Tentar novamente" que chama `iniciar` e reinicia polling.
- Após 5min sem mudança → mensagem de "demorando mais que o esperado" mas continua polling.

### Para o tester

Cenários de integração relevantes:
1. **Primeiro acesso**: limpar linha de `dashboard_expedicao_setup` para block 22, acessar `/missao/dashboards/Expedicao` → deve ver html/configurando (após view existir).
2. **Cron --modo-setup**: `php crons/dashboard_expedicao_diario.php --block=22 --dias=7 --modo-setup` → deve criar grupo "Padrão" em `grupos_regioes` se não existir e marcar `status='pronto'`.
3. **Idempotência**: rodar o cron --modo-setup 3× → 1 grupo "Padrão" único, sem duplicata.
4. **Watchdog**: UPDATE manual `SET status='em_progresso', iniciado_em=NOW()-INTERVAL'11 minutes'` → chamar `/verificar` → deve retornar `falhou` com `erro='Timeout de execução'`.
5. **Multi-tenant**: setup de block A não afeta block B (verificar via SELECT com WHERE block = B).
6. **Regressão legacy**: cron sem flags (`--inicio=X --fim=Y`) deve continuar funcionando com `erros=0`.

---

## Rodada 2 — Correções do reviewer

### Arquivos alterados

- `Sondas/DashboardExpedicaoSetupSonda.php` — **B1+B4**: shell vazio substituído por Sonda funcional com `make()`, `findByBlock()`, `save()`, `atualizarStatus()`, `tentarIniciar()` (algoritmo anti-race com `create()` + `update()` condicional).
- `Laboratorios/AutoSetupExpedicaoLaboratorio.php` — **B1+M2+M4**: todos os writes migrados para usar a Sonda; adicionado `obterBlockDaSessao()` estático; `iniciar()` usa `tentarIniciar()` em vez de `upsertSetup` com ON CONFLICT DO UPDATE; checagem de `disable_functions` antes do `exec()`; log path movido para `LOCAL_NOVO_SISTEMA/logs/`; `marcarPronto()`/`marcarFalhou()` usam `atualizarStatus()` da Sonda; `garantirGrupoPadrao` mantém raw SQL apenas para SELECTs em tabelas externas sem Sonda.
- `Satelites/StatusSetupSatelite.php` — **B3**: substituídos os 2 reads inline por delegação ao `StatusSetupValidator`; `match` agora é exhaustive (sem `default` que cobria opções inválidas já tratadas pelo Validator).
- `Expedicao.php` — **B3**: inline de `getSession()->customerData->id` substituído por `AutoSetupExpedicaoLaboratorio::obterBlockDaSessao()` em try/catch.
- `Migrations/001_dashboard_expedicao_setup.sql` — **B2**: removido o bloco `INSERT ... SELECT DISTINCT block, 'pronto' FROM grupos_regioes` que chumbava `block_empresa_id = block`. Adicionado comentário explicativo sobre o comportamento esperado no primeiro acesso de clientes legacy.
- `linguagens/es.json` — **M1**: `836a00c1` (`"Tentar novamente"`) corrigido de `"Intentar de nuevo"` para `"Intentar nuevamente"`; `9cc77b9b` (demorando) e `34ae55ce` (erro) atualizados com traduções canônicas conforme tabela do reviewer.
- `linguagens/en.json` — **M1**: `9cc77b9b` e `34ae55ce` atualizados com traduções em inglês conforme tabela do reviewer (os demais 5 já estavam corretos).
- `crons/dashboard_expedicao_diario.php` — **M5**: comentário do `register_shutdown_function` atualizado para explicitar que cobre apenas fatal errors PHP, não SIGKILL/OOM; watchdog de 10min é a defesa real para travamento total. Emoji `❌` removido do stderr (m6).
- `Testes/Laboratorios/AutoSetupExpedicaoTddTest.php` — `montarSessaoSetup($BLOCK_C)` adicionado antes do T10, garantindo que o `customerControl` da Sonda bata com o `block_empresa_id` da linha ao chamar `marcarPronto($BLOCK_C)`. Justificativa: a Sonda com `customerControl=true` filtra `AND block_empresa_id = <session_customer_id>` nos UPDATEs; o teste anterior funcionava só porque o raw SQL do Laboratório não filtrava por tenant.

### Arquivos criados

- `Validators/StatusSetupValidator.php` — **B3+M3**: valida `$opcao in ['verificar','iniciar']` e `block > 0` extraído da sessão via `obterBlockDaSessao()`; ignora explicitamente qualquer chave `block` em `$data` (tenant safety).

### Desvios do techplan nesta rodada

1. **`tentarIniciar` usa `create()` + `fail` reset + `update()` em vez de raw SQL ON CONFLICT**: ModelInteligencia não expõe `ON CONFLICT DO NOTHING` nativamente. A lógica equivalente é implementada como método da própria Sonda (`DashboardExpedicaoSetupSonda::tentarIniciar`), que reseta `$this->fail` após UNIQUE violation esperada antes de tentar o `update()` condicional. O comportamento observável é idêntico ao algoritmo do reviewer — só reserva execução se conseguiu modificar a linha.

2. **`executarSync` incrementa `tentativas` via read-modify-write PHP**: ModelInteligencia não suporta `tentativas = tentativas + 1` em expressão SQL. Como `executarSync` é chamado single-threaded pelo cron, o read-modify-write no PHP é safe. Se no futuro houver paralelismo no cron, seria necessário migrar para raw SQL incremental neste ponto específico.

3. **`garantirGrupoPadrao` mantém raw SQL para SELECTs e o INSERT em `grupos_regioes`**: sem Sonda de `grupos_regioes` no escopo desta feature. O reviewer confirmou que raw SQL é aceitável para estas tabelas externas.

4. **M1 parcial**: As strings `"demorando"` e `"erro"` em `configurando.php` diferem levemente das strings da tabela do reviewer (fonte diferente → MD5 diferente). As traduções dos keys existentes foram atualizadas com os valores canônicos da tabela do reviewer. Para sincronizar 100%, o frontend precisará atualizar as strings-fonte em `html/configurando.php` quando for fazer as correções de minor scope.

### Comandos de verificação executados

- `php -l Sondas/DashboardExpedicaoSetupSonda.php` → OK
- `php -l Laboratorios/AutoSetupExpedicaoLaboratorio.php` → OK
- `php -l Validators/StatusSetupValidator.php` → OK
- `php -l Satelites/StatusSetupSatelite.php` → OK
- `php -l Expedicao.php` → OK
- `php -l crons/dashboard_expedicao_diario.php` → OK
- `php -l Testes/Laboratorios/AutoSetupExpedicaoTddTest.php` → OK
- `php -l Testes/Laboratorios/AutoSetupExpedicaoIntegrationTest.php` → OK
- `json_decode(en.json)` → OK
- `json_decode(es.json)` → OK
