# Review & Plano — Desacoplar o módulo Central (V1 satélite → Sinal V2 isolado)

> **Natureza deste documento:** review arquitetural + plano + **PRD vivo do módulo** (mora dentro de `sinais/Central2/`). A **execução já começou** — ver «Progresso» na §11.
> **Status:** Estratégia B escolhida · **B0 (esqueleto + gate) concluído** · próximo: B1 (socket isolado).
> **Alvo escolhido:** migrar o satélite legado para um Sinal V2 desacoplado, nos moldes `Edicaorota`/`Otp`, **com prós e contras**.
> **Atenção a referências de linha:** há ~247 linhas não-commitadas no próprio `Central.php` V1 (P0 em andamento). Revalidar cada linha antes de mexer.

---

## 1. Contexto — por que avaliar isso

O Central é o painel TMS de despacho: cria rotas (romaneios), aloca pedidos a veículos, vincula motoristas, libera separação/carregamento/faturamento e aprova portaria. O núcleo dele é um **satélite V1 de ~3.960 linhas** em `componentes/Constelacoes/Logistica/Central/Satelites/Central.php`, que é ao mesmo tempo **dispatcher** (`switch($sinal)` sobre ~30 ações no construtor), **regra de negócio** e **broadcaster de socket inline**.

Isso viola o padrão V2 do repo em vários eixos (registrados em memória e no `Edicaorota/COMPARACAO_FRAMEWORK.md`):
- **Constelacoes deveria ser só model** — aqui há Satélites + Traits + lógica de negócio dentro de Constelacoes.
- **Controller deveria ser dispatcher fino** — aqui a lógica está toda no satélite.
- **Sondas V2 only** — Central ainda usa `Inteligencias` V1 (`MapaIcone`, `NivelCubagem`, `PeriodoEntregaCliente`...).
- **Socket isolado** — aqui o `enviarSocket` está inline em 18+ métodos de negócio via `LogisticaSocketTrait`.

O objetivo do desacoplamento é tornar o Central testável, manutenível e extensível como `Edicaorota`/`Otp`, **sem mudar o comportamento para o operador**.

---

## 2. Estado atual — o achado que muda tudo

**Não existe "um" Central.** Existem 3+ pontos de entrada sobrepostos, e o trabalho central da migração é colapsá-los em UM Sinal:

1. **Satélite V1 (monólito)** — `Constelacoes/Logistica/Central/Satelites/Central.php` (~3.960 ln, ~30 ações).
2. **Controller V2 (casca meio-migrada)** — `Missoes/Logistica/sinais/Central/Central.php` (~1.394 ln). Já tem Sondas/Laboratórios/Migrations/`GalaxiaAutoLoad`, **mas delega as ações pesadas de volta ao satélite V1** via `new SatelitesCentral($this->galaxiaRoute, 'pedidosDisponiveis', ...)` e cadeias `setConstelacao('Logistica')->setEstrela('Central')->setSatelite('Central')->setSinal(...)`.
3. **Roteamento por string (GalaxiaRoute)** — forms (`salvar_rotas.php`, `fechar_rota.php`) postam direto pro satélite V1 sem passar pelo controller V2.
4. **URLs hardcoded** que codificam o namespace do satélite: cron `crons/crompedidos_logistica.php:20` (`/rotas/apis/Logistica/Central/Central/filtrarNovosPedidosDisponiveis`), `maps.js` (`removerPedidoConfirmado`, `CuradoriaAlertasTmsLab`), etc. **Mover a classe sem shim → cada uma dessas dá 404** (a do cron é silenciosa: pedidos param de chegar sem erro visível).

**Consequência de design:** o alvo **não** é criar um Sinal green-field. É **tornar `Missoes/Logistica/sinais/Central/` o dono único**, puxar as ~30 ações do satélite V1 para Laboratórios sob ele, e reduzir o satélite V1 a um shim de compatibilidade (depois deletado). Um Central paralelo orfanaria o controller de 1.394 ln + Sondas + Migrations + sub-sinais que já existem.

### Fronteiras que são "externas fixas" (fora de escopo da migração)
- **`controladores/_Src/maps.js` (~14.099 ln)** — contrato implícito e não-versionado de socket + dados. `CentralSocketHandler` lê ~20 campos de `LogisticaSocketTrait::normalizarDadosRota()` por nome e reconcilia contra o textarea `.rotasGeradas` (fonte de verdade da alocação). Migra-se *contra* ele, não se mexe nele.
- **TMS DB (cross-DB)** — `Laboratorios/CuradoriaAlertasTmsLab.php` abre PDO MySQL cru numa segunda base (`romaneios_tms_log`). Não vira Sonda, não roda em `dev_gx_goldie`. Fica cercado num `Adapters/Tms/` (ou como lab integration-only).

---

## 3. Arquitetura-alvo (V2 Sinal)

Consolidar tudo sob o Sinal **existente** `componentes/Missoes/Logistica/sinais/Central/`:

```
Missoes/Logistica/sinais/Central/
├── Central.php                 # dispatcher fino: start + GalaxiaAutoLoad + Observatorio + bootstrap
├── Laboratorios/               # as ~30 ações viram labs, agrupadas por concern
│   ├── Pedidos/                #   pedidosDisponiveis, dadosDoPedido, lerNiveisCubagem, filtrarNovos
│   ├── Rotas/                  #   salvarRotas, novaRota, vincular/desvincularMotorista, salvarRoteirizacao, atualizarDescricao
│   ├── Romaneio/               #   finalizar, confirmar, remover*, libera/cancela Separacao|Faturamento|Carregamento  (state-machine, o mais acoplado)
│   ├── Portaria/               #   aprovarPortaria, listarPortaria
│   └── Board/                  #   criarMissaoCarregamentoBoard
├── Roteirizacao/               # (se sobreviver à triagem) compute puro, sem $galaxia/socket/DB
├── Eventos/                    # camada de socket extraída do negócio
│   ├── CentralBroadcastInterface.php   # contrato = o "schema" do maps.js, num lugar só
│   ├── CentralSocketNotifier.php       # impl real (cópia de Edicaorota/Eventos/EdicaoRotaSocketNotifier)
│   └── CentralBroadcastRegistry.php    # troca notifier real ↔ spy/no-op (testes / WEBSOCKET_ENABLED=false)
├── Adapters/                   # só os domínios que os labs MUTAM via protocolo multi-passo
│   ├── Romaneio/               #   Romaneios/RomaneioPedido/RomaneioVeiculo (maior write-coupling; precedente em Edicaorota/Adapters/Romaneio)
│   ├── MissaoLogistica/        #   MissoesLogisticas/MissoesPersistencia + StatusMissao/TipoMissao
│   └── Processo/               #   Processos + ProcessosStatusMovimentacoes (transições de status)
├── Sondas/  Validators/  Enums/  Migrations/  forms/  html/  sinais/  Testes/
```

**O que fica onde (Constelacoes → Sinal):**

| Hoje (Constelacoes) | Alvo |
|---|---|
| `Satelites/Central.php` | quebrado em `Laboratorios/*` + `Eventos/*` + `Adapters/*`; satélite vira shim → deletado |
| `Traits/LogisticaSocketTrait.php` | substituído por `Eventos/CentralSocketNotifier` (vira shim → deletado) |
| `Inteligencias/*` (MapaIcone, NivelCubagem...) | **ficam** em Constelacoes como model V1 (Constelacoes = model-only); consumidas via Sonda/read |
| `Satelites/Rotas*, ColoniaDeFormigas, KMeans, SavingsAlgorithm` | **triagem primeiro** (ver §5/P-5) — fortíssima evidência de código morto → **deletar**, não migrar |
| `Satelites/Admin, Mapas, RegioesAdministrativas*` | ficam por ora (CRUD-ish, fora do caminho crítico) |
| `Testes/*SocketIntegrationTest`, `LogisticaSocketBroadcastTddTest` | movem/reescrevem em `sinais/Central/Testes/Eventos/` |

**Mecanismos de desacoplamento:**
- **Socket (a peça-chave):** labs passam a chamar `$broadcaster->rotaAtualizada(...)` em vez de `$this->galaxia->enviarSocket(...)`. Copiar `Edicaorota/Eventos/EdicaoRotaSocketNotifier.php` (estático, best-effort, gateia `WEBSOCKET_ENABLED`, **não precisa de `$galaxia`** — é o que torna os 30 labs testáveis). **maps.js não muda** (mesmas rooms, mesmo envelope).
- **Adapters: 3 domínios, não 12.** Só os que os labs mutam em protocolo (Romaneio, MissaoLogistica, Processo). Pedidos/Veiculos seguem como **read direto** — adapter ali seria cerimônia. (Alinhado ao aviso do próprio `COMPARACAO_FRAMEWORK.md` contra over-abstração.)
- **Registry+Interface** (idiom de `EdicaoRotaCallbackRegistry`/`OtpCanalRegistry`): aplicar ao broadcast registry e, se necessário, a um `RoteirizadorFactory`. Callback registry "dono da rota" tem valor baixo aqui (Central já é o dono) → adiar.

---

## 4. Plano faseado (strangler-fig — verde a cada passo)

Invariante: **o satélite V1 segue chamável o tempo todo**; ele é esvaziado por dentro.

- **Fase 0 — Rede de proteção e limpeza.** Subir `sinais/Central/Testes/`; capturar o **contrato de socket atual** como teste de caracterização (semente: `LogisticaSocketBroadcastTddTest.php`, que já espia `enviarSocket` e afirma o payload `route_update`/`route_delete`). Snapshot dos JSONs de `pedidosDisponiveis`/`veiculosParaLogistica`. Deletar lixo que confunde ownership (`copy Central.php`, `romaneio copy.js`, `.oldMapasTecnosoft/`).
- **Fase 1 — Extrair socket (maior ROI).** Criar `Eventos/CentralSocketNotifier` + interface + registry. **Shim:** reescrever os 3 métodos de broadcast do `LogisticaSocketTrait` para delegar ao notifier (mesma assinatura). Satélite continua compilando; comportamento idêntico; socket centralizado.
- **Fase 2 — Triagem + (talvez) extrair compute.** Confirmar morte dos algoritmos (§5/P-5). Se vivos → mover pra `Roteirizacao/` com testes golden-output **antes** de mover. Se mortos → deletar.
- **Fase 3 — Extrair ações, grupo a grupo (menos acoplado → mais).** Ordem: `Pedidos/` (reads) → `Portaria/` → `Rotas/` → **`Romaneio/` por último** (state-machine transacional + socket-entangled). Por ação: corpo vai pro `*Laboratorio`, writes externos via Adapter, broadcasts via notifier, escreve TddTest. **Shim:** o `case` do switch vira `$this->response = (new XxxLaboratorio($data))->response;` — ambos os pontos de entrada (string GalaxiaRoute e controller V2) atingem o mesmo lab.
- **Fase 4 — Repontar entradas & colapsar.** Controller V2 chama labs direto (não mais `new SatelitesCentral`); repontar forms e URLs hardcoded **com shim/alias** pra cron e maps.js não darem 404. Unificar credencial/room Scaledrone num lar só. Reduzir satélite a deprecation shim → deletar (+ `LogisticaSocketTrait`).
- **Fase 5 — Migrations & multi-tenant.** Consolidar `_SQL/grupos_regioes.sql` em `Migrations/NNN_*.sql`. Tabela nova ganha `block_*` (sem `DEFAULT 1` — framework injeta). Não re-tenantizar tabelas de romaneio/pedido que não são do Central.

---

## 5. Prós e Contras (o coração do pedido)

### Prós (a favor da migração V2)
- **Testabilidade real.** Hoje o negócio não roda sem `$galaxia`/socket; só há 3 testes (criados neste sprint) p/ ~30 ações. Extrair socket + labs destrava unit tests por ação — como `Edicaorota` (10 arquivos de teste).
- **Manutenção e onboarding.** Um arquivo de 3.960 ln com 65 métodos vira labs coesos de ~1 responsabilidade; acaba a duplicação de schema de socket em 3 lugares.
- **Fim do meio-termo.** O controller V2 já existe como casca que delega de volta ao V1 — a migração resolve essa dívida em vez de deixá-la apodrecendo.
- **Extensibilidade.** Registry+Interface + Adapters permitem outros domínios plugarem sem editar o core (padrão já provado em Otp/Edicaorota).
- **Segurança de dados a médio prazo.** Isolar o socket atrás de um contrato versionado/testado ataca exatamente a classe de bug (dessincronização → dupla alocação) que hoje é combatida ad-hoc.

### Contras (contra fazer a migração full agora)
- **C1 — Cirurgia em paciente na mesa.** `PRD/central-pontos-abertos-ux/PRD.md` documenta P0 de **dupla alocação** sendo corrigidos *agora*, nas linhas exatas do alvo (`Central.php:1288-1434`, `859-949`), com +247 ln não-commitadas. Refatorar e corrigir bug ao mesmo tempo, com estoque/dinheiro em jogo, é o anti-padrão clássico.
- **C2 — Contrato de socket também está em migração.** Swap de canal Scaledrone não-commitado em 3 lugares (`GalaxiaRoute.php` `enviarSocket`/`lerSocket` + `EdicaoRotaSocketNotifier`). Não dá pra congelar um contrato que está sendo consertado.
- **C3 — maps.js é contrato implícito de ~20 campos, sem schema/versão, em 14k linhas.** Qualquer campo renomeado/dropado no move dessincroniza dois operadores em silêncio → a mesma dupla alocação.
- **C4 — Pontos de entrada duplos + URLs hardcoded.** Mover o namespace quebra cron (`crompedidos_logistica.php:20`, falha silenciosa) e fetches do maps.js, salvo shim.
- **C5 — Os "algoritmos" de roteirização são, com forte evidência, código morto** (sem callers; a otimização real é no front via Google `DirectionsService`). Migrar/“modernizar” código morto é ROI negativo — o certo é **confirmar e deletar**.
- **C6 — Cobertura de teste torna regressão invisível.** 3 testes parciais (untracked, deste sprint) p/ ~30 ações; integration precisa de DB real → não roda hermético em CI.
- **C7 — Escala não-provada.** `Edicaorota` valida o padrão em ~1.500 ln / 1 fluxo / 1 adapter. Central = ~30 ações, ~24 imports, 3 sub-sinais, frontend de 14k. É a maior aposta possível num padrão ainda não provado nessa escala.
- **C8 — Acoplamento cross-sinal profundo.** Central alcança internals de *outros sinais* (`Armazem\Gnesis`, `Designacoes\GestaoMissoes*`, `Regioesagrupamentos`) e Inteligencias V1. Desacoplar "de verdade" implica também V1→V2 Sondas — é um programa multi-módulo, não uma realocação.
- **C9 — Workflow sem merge pra main.** Trabalho sempre em `dev_team`, sem release gate (memória `project_git_workflow`). Refator longo vive no mesmo branch que faz deploy diário dos P0 → estados meio-migrados podem ir a produção.
- **C10 — Custo de oportunidade.** Cada hora na migração (zero valor pro usuário final) é hora fora dos P0 que corrompem alocação hoje.

---

## 6. Pré-condições (gates antes de iniciar a parte estrutural)

- **P-1.** Fechar, commitar e verificar os P0/P1 do `central-pontos-abertos-ux` (cenário 2-navegadores de dupla alocação) **antes** de qualquer refator no arquivo-alvo.
- **P-2.** Commitar/estabilizar o swap de canal Scaledrone (contrato de socket vira ponto fixo).
- **P-3.** **Congelar o contrato de socket:** promover os ~20 campos de `normalizarDadosRota` a schema documentado/versionado com TddTest fixando as chaves (semente: `LogisticaSocketBroadcastTddTest.php`).
- **P-4.** **Testes de caracterização das ~30 ações** em `dev_gx_goldie` (input→output/efeito-DB atuais). Maior gate: sem isso o move é cego.
- **P-5.** **Inventariar e deletar morto (não migrar):** confirmar `pegarPedidosTecnosoft` (sem caller), os 5 algos + `Rotas::gerarRotas` (sem caller), `copy`/`old` files. **Manter** `filtrarNovosPedidosDisponiveis` (vivo via cron).
- **P-6.** **Shims de compatibilidade** pras URLs hardcoded (`Logistica/Central/Central/<acao>` resolvível durante/depois do move).
- **P-7.** Disciplina explícita de branch ("não faz deploy até pronto"), já que não há merge-to-main.
- **P-8.** Decidir a estratégia V1→V2 Sonda e o tratamento do acoplamento cross-sinal **antes** de mover.

---

## 7. Recomendação

> **Atualização — duas estratégias de execução.** Esta §7 descreve a **Estratégia A (strangler-fig)**: esvaziar a V1 por dentro, com shims. A **§11** descreve a **Estratégia B (V2 paralela e coexistente, dev-gated)**: construir a V2 greenfield ao lado, com a V1 100% intacta no ar. **B é a recomendada para começar agora** — tira o refactor de cima do firefight P0 (resolve C1/C9/C10). A disciplina interna da V2 (§3/§4) é a mesma nas duas.

A meta de longo prazo (Central como Sinal V2 isolado) é **correta e vale a pena** — a §3 é o destino. Mas a forma responsável é **incremental e gateada por pré-condições**, não um big-bang, dada a criticidade do módulo, os P0 abertos e o workflow sem release gate. Sequência recomendada (cada etapa é shippable e reversível, e **reforça** o trabalho de P0 em vez de competir):

1. **MVP-0 — Purga de código morto (P-5).** Dias, risco ~zero. Encolhe a superfície e remove o risco de "editar o arquivo errado".
2. **MVP-1 — Isolar o socket atrás de serviço + schema congelado/testado (P-3, Fase 1).** É onde o risco de dupla alocação realmente mora; entrega a maior parte do valor de "desacoplar" sem mover as 3.960 ln.
3. **MVP-2 — Adapter piloto p/ UM domínio (Romaneio).** Valida o padrão na escala do Central, contido. Se não couber, perdeu-se um adapter, não um rewrite.
4. **MVP-3 — Realocar Satélite→Sinal SEM re-arquitetar (só após P-1/P-4/P-6).** Move puro com shims + testes de caracterização. A re-arquitetura (labs/Adapters/Sondas V2) segue depois, ação a ação.

**MVP-0 + MVP-1 entregam ~80% do benefício prático com ~20% do risco.** O move estrutural completo (MVP-3) e a re-arquitetura ficam explicitamente adiados até P-1…P-8 valerem.

---

## 8. Verificação (end-to-end, quando executar cada etapa)

- **Sync / dupla alocação:** abrir o Central em 2 navegadores (mesmo dia/block, `dev_gx_goldie` / `dev-galpaotms.goldieapp.com.br`); ação em A reflete em B sem F5; salvar em B não re-adiciona pedido.
- **Cron:** confirmar que `crompedidos_logistica.php` segue alimentando `filtrarNovosPedidosDisponiveis` após qualquer shim/move.
- **Contrato de socket:** TddTest de schema (P-3) verde; `LogisticaSocketBroadcastTddTest` + os 2 `*SocketIntegrationTest` passam.
- **Labs extraídos:** cada `*Laboratorio` com `*TddTest.php` (padrão `Edicaorota/Testes/`); afirmar **ordem** dos side-effects (persistir → broadcast best-effort; falha de socket nunca falha a operação).
- **Roteirização (se migrar):** testes golden-output (input fixo → sequência fixa) antes e depois do move.
- **Regressão de rota dispatch:** varrer `setSatelite('Central')` e `new SatelitesCentral` (grep é a única ferramenta — rotas stringly-typed) e confirmar que nenhuma 404 após repontar.

---

## 9. Fora de escopo (explícito)
- Reescrita do `maps.js` — projeto separado; é a fronteira fixa contra a qual se migra.
- "Sonda-ficar" o `CuradoriaAlertasTmsLab` / TMS DB — fica cercado como integration-only.
- Migração V1→V2 das Inteligencias compartilhadas (MapaIcone etc.) — decisão própria (P-8), não acoplar a este move.
- Satélites `Admin/Mapas/RegioesAdministrativas` — onda posterior.

## 10. Arquivos críticos
- `componentes/Constelacoes/Logistica/Central/Satelites/Central.php` — o monólito (~3.960 ln, +247 não-commit; P0 em `1288-1434`, `859-949`).
- `componentes/Missoes/Logistica/sinais/Central/Central.php` — controller V2 a virar dono único (hoje delega via `new SatelitesCentral`).
- `componentes/Missoes/Logistica/sinais/Edicaorota/Eventos/EdicaoRotaSocketNotifier.php` — fonte-cópia da camada de socket desacoplada.
- `componentes/Constelacoes/Logistica/Traits/LogisticaSocketTrait.php` — payload de ~20 campos (`normalizarDadosRota`); seam do MVP-1.
- `controladores/_Src/maps.js` — contrato implícito socket+dados (`CentralSocketHandler`); fronteira fixa.
- `controladores/_Controladores/GalaxiaRoute.php` — dispatch namespace-from-URL (`DadosG`) + swap Scaledrone não-commit.
- `crons/crompedidos_logistica.php` — URL hardcoded (`:20`); falha silenciosa se o namespace mover sem shim.
- `componentes/Missoes/Logistica/sinais/Edicaorota/COMPARACAO_FRAMEWORK.md` — padrões V2 vs legado (referência).

---

## 11. Estratégia B — V2 paralela e coexistente (dev-gated), V1 intacta

**Conceito.** Em vez de mexer na V1 (Estratégia A / strangler-fig), construímos um **Sinal V2 novo e separado** — proposta: `Missoes/Logistica/sinais/Central2/` (namespace próprio) — greenfield, seguindo a arquitetura da §3 (dispatcher fino + `Eventos/` socket + `Adapters/` Romaneio/MissaoLogistica/Processo + Sondas V2 + Migrations + Testes). A **V1 fica 100% intacta e no ar** (satélite `Constelacoes/.../Central`, sinal `sinais/Central`, `maps.js`, cron, forms, URLs hardcoded — nada disso é tocado). As duas rodam ao mesmo tempo; a V2 é **acessível só para devs**. Assim dá pra desenvolver a V2 sem prejudicar quem usa a V1.

### Como elas NÃO interferem uma na outra (as garantias de isolamento)

1. **Rota / entry separados.** V2 é um Sinal novo → URL própria `/missao/Logistica/Central2`. A V1 segue em `/missao/Logistica/Central`, e todos os forms/cron/fetches do `maps.js` continuam batendo na V1. Namespaces distintos → o autoload não colide; nenhuma classe da V2 sobrescreve a V1.
2. **Gate de acesso (dev-only).** Copiar o padrão real de `Admin::scanCotaConfig` (`componentes/Missoes/Logistica/sinais/Admin/Admin.php:470-490`): no `start()`/`GalaxiaAutoLoad` do `Central2`, resolver `userId` via `getSession('galaxiaUserInfo')->userData->id` e gatear com `ApiConfig::isDev($userId)` (allowlist `Config::DEV_USER_IDS`); não-dev → `redirect(goldie_url())`. O item de menu da V2 só renderiza pra dev. **Em produção, usuário comum nunca alcança a V2.**
3. **Socket isolado (sem cross-talk).** A V2 publica/lê em **rooms próprias** (`galaxia_logistica_central_v2` / `..._board_v2`) pelo seu próprio `CentralSocketNotifier`. Logo, evento gerado por um dev na V2 **não** chega no `maps.js` da V1 (e vice-versa). As rooms da V1 hoje são `galaxia_logistica_central`/`galaxia_logistica_board` (`Traits/LogisticaSocketTrait.php:42,45`) — a V2 não toca nelas.
4. **Dados — a decisão crítica (precisa escolher).** V1 e V2 compartilham o **mesmo banco** (romaneios, pedidos, veículos). Se a V2 **escrever** nessas tabelas durante o dev, um dev testando mexe em romaneio **real** → risco de dupla alocação de verdade. Opções:
   - **(a) Tenant/empresa sandbox** — V2 só opera num `block`/empresa de teste (o filtro multi-tenant já existe); dev loga nesse tenant; zero impacto no tenant real. ← recomendado.
   - **(b) Read-only no início** — telas de escrita da V2 ficam sem ação até cada lab ter teste de caracterização verde; escrita liga por flag depois.
   - **(c) Shadow tables** — V2 escreve em tabelas espelho (sufixo `_v2`); isola total, mas diverge do schema real e dá mais trabalho.
   - **Recomendado:** começar com **(a) sandbox + (b) read-only** nas escritas; promover lab a lab conforme valida.
5. **Migrations aditivas.** Migrations da V2 só **CRIAM** tabelas novas (nome próprio) e colunas **aditivas**; nunca `ALTER`/`DROP` destrutivo em tabela da V1. Tabelas compartilhadas (romaneios/pedidos) ficam como estão. (`block_*` nas tabelas novas, sem `DEFAULT 1`.)
6. **Registries namespeados.** Os registries estáticos da V2 (broadcast, callbacks) usam chaves/contexto próprios (`central_v2`) — não sobrescrevem nada que a V1 registre em runtime.

### Como isso muda o plano faseado

Em vez das Fases 0-5 esvaziando a V1, vira **construção greenfield do `Central2`**, sempre dev-gated e em rooms/tenant isolados:

- **B0 — Esqueleto + gate.** ✅ **CONCLUÍDO (jun/2026)** — Sinal `Central2` criado (controller dispatcher fino + gate `ApiConfig::isDev` no `start()`), árvore de pastas V2 + view/JS placeholder, rota viva `/missao/Logistica/Central2`. V1 inalterada. *Item de menu dev-only adiado de propósito* (registrar no tema global = tocar produção); acesso por URL direta por ora. Detalhe no «Progresso» abaixo.
- **B1 — Socket V2 isolado.** `Eventos/CentralSocketNotifier` em rooms `_v2`. Front (ver decisão abaixo) conecta nessas rooms.
- **B2 — Leitura.** Labs/Sondas V2 de leitura (`pedidosDisponiveis`, `dadosDoPedido`, listagem de romaneios) — telas read-only sobre dados reais do tenant sandbox.
- **B3 — Escrita, lab a lab, com Adapter.** `Rotas/` → `Romaneio/` (state-machine por último), cada um via `Adapters/Romaneio|MissaoLogistica|Processo`, com `*TddTest`. Liga a escrita por flag conforme valida.
- **B4 — Paridade + cutover gradual (fase separada).** Quando a V2 cobre o fluxo, ligar por **tenant/usuário** (flag), repontar cron/forms/URLs hardcoded **com shim**, e só então aposentar a V1.

> ⚠️ Os contras **C3 (contrato implícito do maps.js)** e **C4 (URLs hardcoded + cron)** **não desaparecem** nesta estratégia — eles são **adiados para o cutover (B4)**. As pré-condições **P-3 (congelar contrato de socket)** e **P-4 (testes de caracterização)** continuam obrigatórias, mas só na virada, não pra começar.

### Progresso (log de execução)

**B0 — esqueleto + gate · ✅ concluído (jun/2026)**

Estrutura criada em `componentes/Missoes/Logistica/sinais/Central2/`:
- **`Central2.php`** — controller dispatcher fino. `start()` aplica o **gate dev-only** (`ApiConfig::isDev`, allowlist `Config::DEV_USER_IDS`, mesma resolução de sessão de `Admin::scanCotaConfig`); não-dev → `redirect(goldie_url())`. `bootstrap()` / `GalaxiaAutoLoad` / `Observatorio` como placeholders (TODOs apontando B1/B3).
- **`html/central2.php`** — view index com banner «🚧 Módulo em desenvolvimento — NÃO é produção» + ponteiros pro PRD/README/rooms `_v2`.
- **`html/central2.js`** — JS pareado (auto-carregado), com o aviso da decisão de front (derivar do `maps.js` sem editá-lo).
- **Árvore de pastas V2** com `.gitkeep`: `Laboratorios/{Pedidos,Rotas,Romaneio,Portaria,Board}`, `Eventos`, `Adapters/{Romaneio,MissaoLogistica,Processo}`, `Sondas`, `Validators`, `Enums`, `Migrations`, `Roteirizacao`, `Testes/{Laboratorios,Eventos,Adapters}`.
- **`README.md`** (aviso + 6 regras de isolamento) e este **`review-central.md`** movidos pra **dentro** do módulo.

Verificações: `php -l` limpo nos PHPs; rota resolve via **PSR-4** (`GalaxiaMissoes\` → `componentes/Missoes`), sem `dump-autoload`.

Decisões fechadas no caminho: **#2 front** (derivar do `maps.js`, não 100%) · **#3 nome** (`Central2`). Ainda aberta: **#1 isolamento de dados no dev**.

**Não tocado (garantia de V1 intacta):** nenhum arquivo da V1, nem tema/menu global, cron, forms ou o `maps.js` compartilhado.

**Pendente / próximo:** (a) verificar no browser logado como dev (`Config::DEV_USER_IDS`) que `/missao/Logistica/Central2` abre e que não-dev redireciona; (b) **B1** — socket isolado (`Eventos/`: `CentralBroadcastInterface` + `CentralSocketNotifier` + `CentralBroadcastRegistry`) em rooms `_v2`; (c) bater o martelo na **decisão #1** antes do B3.

> 🗂️ **Timeline completa de desenvolvimento** (tracking vivo, derivada deste PRD): **`tasks/README.md`** — todas as tarefas B0→B4 + front (F) + decisões/gates (D), com ID, status, dependências e esforço. Um arquivo por fase em `tasks/`.

### Prós (vs Estratégia A / strangler-fig)
- **V1 intacta → zero risco a produção durante o dev.** Ataca de frente C1 (cirurgia em paciente na mesa), C9 (sem release gate) e C10 (custo de oportunidade): a V2 e o firefight P0 vivem em arquivos diferentes, sem conflito.
- **Timelines independentes** — a V2 evolui sem esperar os P0 fecharem.
- **Clean-room** — adota a arquitetura V2 plena desde o dia 1, sem shims dentro da V1.
- **Reversível trivialmente** — desligar a V2 é só esconder o menu / fechar o gate; **nada na V1 muda**.

### Contras / partes difíceis (honesto)
- **Drift / divergência.** Enquanto coexistem, todo fix de comportamento na V1 (inclusive os P0 em curso) precisa ser **re-portado** pra V2, ou ela fica desatualizada. Quanto mais longa a coexistência, maior o drift — é o risco clássico do "rewrite que nunca alcança a produção". Mitigação: escopo enxuto + cutover por tenant o quanto antes (não esperar 100% de paridade).
- **`maps.js` é o elefante — DECISÃO TOMADA: front novo, derivado do `maps.js` como base (não 100%).** A maior parte da complexidade do Central é o front (~14k ln). Opções consideradas: (i) reusar o `maps.js` compartilhado apontado pros endpoints/rooms da V2 (rápido, mas re-acopla na fronteira fixa) · (ii) front 100% do zero (desacoplamento total, meses). **Escolha: o caminho do meio** — criar um **front próprio do `Central2`** usando o `maps.js` atual como **base/referência** (portar o núcleo de mapa / markers / desenho de rota, que é em boa parte provider-agnóstico), mas **reescrevendo a camada de socket+estado coesa com a proposta do módulo**: contrato de socket explícito/versionado (espelho do `CentralBroadcastInterface`); store de estado no lugar do hack do textarea `.rotasGeradas` (fonte de verdade); sem dispatch implícito por `typeof fn === 'function'`; i18n via `show()` (não as ~22 strings PT hardcoded). **Não editar** o `controladores/_Src/maps.js` compartilhado — é a fronteira fixa da V1; o front da V2 é um **asset separado** sob `sinais/Central2/`. Trade-off: mais esforço que o reuso puro e sujeito ao mesmo drift do backend durante a coexistência; em troca, o front nasce desacoplado e o **contrato C3 da V2 passa a ser explícito** (C3 segue valendo só pra V1/cutover).
- **O cutover ainda é difícil.** O risco de integração (URLs hardcoded, cron, contrato de socket) é **adiado, não eliminado**; exige o mesmo cuidado de grep + shims na virada (B4).
- **Custo de manter dois sistemas** até o cutover.
- **Dados compartilhados** exigem a decisão de isolamento (item 4) pra dev não corromper romaneio real.

### Decisões
1. **Isolamento de dados no dev** _(em aberto)_: (a) tenant sandbox + (b) read-only nas escritas [recomendado] · ou (c) shadow tables `_v2`?
2. ~~**Front**~~ ✅ **DECIDIDO:** front próprio do `Central2`, **derivado do `maps.js` como base (não 100%)** — núcleo de mapa/markers/rota portado; camada socket+estado reescrita coesa/desacoplada; o `maps.js` compartilhado **não** é editado.
3. ~~**Nome do Sinal**~~ ✅ **DECIDIDO:** `Central2`.

### Veredito
A **Estratégia B é a melhor para começar agora**: isola o desenvolvimento da V2 do risco de produção e do firefight P0, mantendo a V1 servindo os operadores sem qualquer interferência. Ela **não** resolve sozinha o `maps.js` nem o cutover — só os adia para uma fase B4 dedicada, onde as pré-condições P-3/P-4/P-6 voltam a valer. A disciplina arquitetural interna da V2 segue §3/§4 (Adapters em 3 domínios, socket isolado, Sondas V2, Testes espelhando estrutura).
