# Central2 — Backend Escrita (B3) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Implementar os Laboratórios de **escrita** da Central V2, com cada ação isolada por Adapter, gateada pelo sandbox (block 22) e emitindo broadcast nas rooms `_v2` via `CentralBroadcastRegistry`.

**Architecture:** Cada Lab de escrita segue o padrão: `SandboxGuard::verificar` → Adapter faz a escrita no banco → `CentralBroadcastRegistry::resolver()->...` emite o evento. Labs nunca tocam Scaledrone diretamente. Adapters isolam os models V1 (Romaneios, MissaoLogistica, Processos) atrás de interfaces — Labs só conhecem o Adapter.

**Tech Stack:** PHP 8.1, padrão Sonda V2 (`ModelInteligencia`), `CentralBroadcastRegistry` + `CentralSocketNotifier` (já em `Eventos/`), scripts TDD standalone (não PHPUnit).

## Global Constraints

- **Namespace raiz:** `GalaxiaMissoes\Logistica\sinais\Central2`
- **Sandbox:** qualquer Lab de escrita deve chamar `SandboxGuard::verificar($blockId)` antes de qualquer escrita; lança `\RuntimeException` fora do block 22.
- **Broadcast:** sempre via `CentralBroadcastRegistry::resolver()`, nunca direto ao Scaledrone. Falha de socket não pode falhar a operação — o broadcast é best-effort após persistência.
- **Rooms:** `_v2` only (nunca `galaxia_logistica_central` sem sufixo).
- **Sem raw SQL:** escritas via Sonda (`$sonda->create()` / `$sonda->update()` / `$sonda->delete()`), nunca `Connect::getInstance`.
- **Referência V1 para queries:** `componentes/Constelacoes/Logistica/Central/Satelites/Central.php` (ler os métodos privados para entender o schema antes de implementar cada adapter/lab).
- **Testes:** scripts PHP standalone em `Testes/` com classe `Assert*` local; sem banco (stubs injetados via construtor); exit(1) se FAIL.
- **Nenhum arquivo fora de `Central2/` deve ser alterado.**

---

## Task 1: SandboxGuard (D-1)

**Files:**
- Create: `componentes/Missoes/Logistica/sinais/Central2/Adapters/Sandbox/SandboxGuard.php`
- Create: `componentes/Missoes/Logistica/sinais/Central2/Testes/Adapters/SandboxGuardTddTest.php`

**Interfaces:**
- Produces: `SandboxGuard::verificar(int $blockId): void` — lança `\RuntimeException` se `$blockId !== 22`.

- [ ] **Step 1: Escrever o teste**

```php
<?php
// Testes/Adapters/SandboxGuardTddTest.php
require_once __DIR__ . '/../../Adapters/Sandbox/SandboxGuard.php';

use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Sandbox\SandboxGuard;

class AssertSandbox {
    public static int $passou = 0;
    public static int $falhas = 0;
    public static function true(bool $cond, string $msg): void {
        if ($cond) { self::$passou++; echo "[PASS] {$msg}\n"; }
        else        { self::$falhas++; echo "[FAIL] {$msg}\n"; }
    }
}

echo "====================================================\n";
echo "TDD — SandboxGuard\n";
echo "====================================================\n\n";

// SANDBOX_BLOCK_ID é 22
AssertSandbox::true(SandboxGuard::SANDBOX_BLOCK_ID === 22, 'SANDBOX_BLOCK_ID === 22');

// block correto: não lança
$ok = true;
try { SandboxGuard::verificar(22); } catch (\Throwable $e) { $ok = false; }
AssertSandbox::true($ok, 'verificar(22) não lança');

// block errado: lança RuntimeException
$threw = false;
try { SandboxGuard::verificar(99); } catch (\RuntimeException $e) { $threw = true; }
AssertSandbox::true($threw, 'verificar(99) lança RuntimeException');

// mensagem de erro contém o block_id
$msg = '';
try { SandboxGuard::verificar(5); } catch (\RuntimeException $e) { $msg = $e->getMessage(); }
AssertSandbox::true(str_contains($msg, '5'), 'mensagem de erro contém o block_id recusado');

echo "\nRESULTADO: PASS=" . AssertSandbox::$passou . "  FAIL=" . AssertSandbox::$falhas . "\n";
if (AssertSandbox::$falhas > 0) { exit(1); }
echo "TODOS OS TESTES PASSARAM\n";
```

- [ ] **Step 2: Rodar o teste — confirmar FAIL**

```bash
cd /home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/Central2
php Testes/Adapters/SandboxGuardTddTest.php
# Esperado: erro de require_once (arquivo não existe ainda)
```

- [ ] **Step 3: Implementar**

```php
<?php
// Adapters/Sandbox/SandboxGuard.php
namespace GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Sandbox;

class SandboxGuard
{
    public const SANDBOX_BLOCK_ID = 22;

    public static function verificar(int $blockId): void
    {
        if ($blockId !== self::SANDBOX_BLOCK_ID) {
            throw new \RuntimeException(
                "Central2 V2: escrita fora do sandbox proibida (block={$blockId}, sandbox=" . self::SANDBOX_BLOCK_ID . ")."
            );
        }
    }
}
```

- [ ] **Step 4: Rodar o teste — confirmar PASS**

```bash
php Testes/Adapters/SandboxGuardTddTest.php
# Esperado: 4/4 PASS
```

- [ ] **Step 5: Commit**

```bash
git add Adapters/Sandbox/SandboxGuard.php Testes/Adapters/SandboxGuardTddTest.php
git commit -m "feat(central2): D-1 — SandboxGuard block 22 + TddTest"
```

---

## Task 2: RomaneioAdapter (B3-1)

O Adapter encapsula toda escrita nas tabelas `romaneios`, `romaneios_pedidos`, `romaneios_veiculos`. Labs nunca importam models de Romaneio direto.

**Antes de implementar:** ler os métodos V1 `novaRota` (ln 1148), `salvarRotas` (ln 467), `removerRomaneioPedido` (ln 1288), `removerRomaneioVeiculo` (ln 1363), `criarRota` (buscar na V1 — é chamado por `salvarRotas`) em `componentes/Constelacoes/Logistica/Central/Satelites/Central.php` para entender o schema exato das tabelas.

**Files:**
- Create: `Adapters/Romaneio/RomaneioAdapterInterface.php`
- Create: `Adapters/Romaneio/RomaneioAdapter.php`
- Create: `Testes/Adapters/RomaneioAdapterTddTest.php`

**Interfaces:**
- Consumes: `SandboxGuard` (Task 1), Sondas Romaneio (criar via `ModelInteligencia` ou reuso de Sondas existentes em `Constelacoes/Logistica/`)
- Produces:
  ```php
  interface RomaneioAdapterInterface {
      public function criarRomaneio(int $blockId, string $data): object;             // retorna {id, codigo_md5, ...}
      public function vincularPedido(int $romaneioId, int $pedidoId, array $dados): object; // retorna {id}
      public function desvincularPedido(int $romaneioPedidoId): bool;
      public function vincularVeiculo(int $romaneioId, int $veiculoId, float $cubagem): object; // retorna {id}
      public function desvincularVeiculo(int $romaneioVeiculoId): bool;
      public function atualizarSequencia(int $romaneioVeiculoId, array $sequencia, array $metricas): bool;
  }
  ```

- [ ] **Step 1: Escrever o teste (com stub)**

```php
<?php
// Testes/Adapters/RomaneioAdapterTddTest.php
require_once __DIR__ . '/../../Adapters/Romaneio/RomaneioAdapterInterface.php';
require_once __DIR__ . '/../../Adapters/Romaneio/RomaneioAdapter.php';

use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio\RomaneioAdapterInterface;
use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio\RomaneioAdapter;

class AssertRomaneioAdapter {
    public static int $passou = 0;
    public static int $falhas = 0;
    public static function true(bool $cond, string $msg): void {
        if ($cond) { self::$passou++; echo "[PASS] {$msg}\n"; }
        else        { self::$falhas++; echo "[FAIL] {$msg}\n"; }
    }
}

echo "TDD — RomaneioAdapter\n\n";

// RomaneioAdapter implementa a interface
AssertRomaneioAdapter::true(
    is_subclass_of(RomaneioAdapter::class, RomaneioAdapterInterface::class),
    'RomaneioAdapter implementa RomaneioAdapterInterface'
);

// A interface define os 6 métodos esperados
$iface = new \ReflectionClass(RomaneioAdapterInterface::class);
$methods = array_map(fn($m) => $m->getName(), $iface->getMethods());
foreach (['criarRomaneio','vincularPedido','desvincularPedido','vincularVeiculo','desvincularVeiculo','atualizarSequencia'] as $m) {
    AssertRomaneioAdapter::true(in_array($m, $methods), "Interface define método: {$m}");
}

echo "\nRESULTADO: PASS=" . AssertRomaneioAdapter::$passou . "  FAIL=" . AssertRomaneioAdapter::$falhas . "\n";
if (AssertRomaneioAdapter::$falhas > 0) { exit(1); }
echo "TODOS OS TESTES PASSARAM\n";
```

- [ ] **Step 2: Implementar `RomaneioAdapterInterface.php`**

```php
<?php
namespace GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio;

interface RomaneioAdapterInterface
{
    public function criarRomaneio(int $blockId, string $data): object;
    public function vincularPedido(int $romaneioId, int $pedidoId, array $dados): object;
    public function desvincularPedido(int $romaneioPedidoId): bool;
    public function vincularVeiculo(int $romaneioId, int $veiculoId, float $cubagem): object;
    public function desvincularVeiculo(int $romaneioVeiculoId): bool;
    public function atualizarSequencia(int $romaneioVeiculoId, array $sequencia, array $metricas): bool;
}
```

- [ ] **Step 3: Implementar `RomaneioAdapter.php`**

Leia V1 (ln 1148–1287) para entender o schema de `romaneios`, `romaneios_pedidos`, `romaneios_veiculos`. Use Sondas V2 (já criadas em `Sondas/RomaneioLogisticaSonda.php`) para leitura; para escrita, use `$sonda->create([...])` / `$sonda->update(['id'=>$id, ...])` / `$sonda->delete($id)`.

```php
<?php
namespace GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio;

use GalaxiaMissoes\Logistica\sinais\Central2\Sondas\RomaneioLogisticaSonda;
use ModelInteligencia;

class RomaneioAdapter implements RomaneioAdapterInterface
{
    // Sondas para as 3 tabelas (criar Sondas inline ou separado se necessário)
    private function sondaRomaneios(): ModelInteligencia
    {
        // Retorna sonda com table='romaneios'; pode ser RomaneioLogisticaSonda
        return new RomaneioLogisticaSonda();
    }

    // Ler V1 ln 1211 (novoRomaneio) para campos obrigatórios de romaneios
    public function criarRomaneio(int $blockId, string $data): object
    {
        // ler campos de 'romaneios' na V1, depois:
        $sonda = $this->sondaRomaneios();
        $id = $sonda->create([
            'block'         => $blockId,
            'dia_efetivo'   => $data,
            'status'        => 'aberto',
            // demais campos — preencher após ler schema V1
        ]);
        return (object) ['id' => $id];
    }

    // Ler V1 ln 1224–1270 (novoVeiculo, novoPedido) para campos obrigatórios
    public function vincularPedido(int $romaneioId, int $pedidoId, array $dados): object { /* ... */ return (object)[]; }
    public function desvincularPedido(int $romaneioPedidoId): bool { /* ... */ return true; }
    public function vincularVeiculo(int $romaneioId, int $veiculoId, float $cubagem): object { /* ... */ return (object)[]; }
    public function desvincularVeiculo(int $romaneioVeiculoId): bool { /* ... */ return true; }
    public function atualizarSequencia(int $romaneioVeiculoId, array $sequencia, array $metricas): bool { /* ... */ return true; }
}
```

> **Nota:** os `/* ... */` acima são os únicos stubs permitidos nesta tarefa — o implementador deve completá-los com base na leitura do V1. Todas as chaves de arrays de `create()`/`update()` devem corresponder às colunas reais das tabelas.

- [ ] **Step 4: Rodar o teste — confirmar 7/7 PASS**

```bash
php Testes/Adapters/RomaneioAdapterTddTest.php
```

- [ ] **Step 5: Commit**

```bash
git add Adapters/Romaneio/ Testes/Adapters/RomaneioAdapterTddTest.php
git commit -m "feat(central2): B3-1 — RomaneioAdapter interface + impl"
```

---

## Task 3: MissaoLogisticaAdapter + ProcessoAdapter (B3-2 + B3-3)

**Antes de implementar:** ler `liberaSeparacao` (ln 120), `liberaCarregamento`, `liberarFaturamento`, `cancelaSeparacao`, `cancelaCarregamento`, `cancelarFaturamento` na V1 para entender quais transições de status existem.

**Files:**
- Create: `Adapters/MissaoLogistica/MissaoLogisticaAdapterInterface.php`
- Create: `Adapters/MissaoLogistica/MissaoLogisticaAdapter.php`
- Create: `Adapters/Processo/ProcessoAdapterInterface.php`
- Create: `Adapters/Processo/ProcessoAdapter.php`
- Create: `Testes/Adapters/AdaptersB3TddTest.php`

**Interfaces:**
- Produces:
  ```php
  interface MissaoLogisticaAdapterInterface {
      public function criarMissao(int $blockId, string $tipo, array $dados): object;
      public function transicionarStatus(int $missaoId, string $novoStatus): bool;
  }

  interface ProcessoAdapterInterface {
      public function atualizarStatusMovimentacao(int $processoId, int $pedidoId, string $status): bool;
  }
  ```

- [ ] **Step 1: Escrever teste de conformidade**

```php
<?php
// Testes/Adapters/AdaptersB3TddTest.php
// require_once de cada interface + adapter (4 arquivos)
// Afirmar: cada Adapter implementa sua Interface
// Afirmar: interfaces definem os métodos listados acima
// Padrão idêntico ao Task 2 — 2+2+2+2 asserts mínimos
```

- [ ] **Step 2: Implementar interfaces** (padrão idêntico a Task 2 — substituindo os nomes)

- [ ] **Step 3: Implementar adapters** (ler V1 — `liberaSeparacao` ln ~120, `ProcessosStatusMovimentacoes` para o Processo)

- [ ] **Step 4: Rodar teste — confirmar PASS**

```bash
php Testes/Adapters/AdaptersB3TddTest.php
```

- [ ] **Step 5: Commit**

```bash
git add Adapters/MissaoLogistica/ Adapters/Processo/ Testes/Adapters/AdaptersB3TddTest.php
git commit -m "feat(central2): B3-2/B3-3 — MissaoLogistica + Processo adapters"
```

---

## Task 4: Labs de Rotas — NovaRota + RemoverPedido + RemoverVeiculo (B3-4 parte 1)

As ações de escrita mais críticas (P0-1, P0-2 do PRD). Cada Lab: guard → adapter → broadcast.

**Files:**
- Create: `Laboratorios/Rotas/NovaRotaLaboratorio.php`
- Create: `Laboratorios/Rotas/RemoverPedidoRotaLaboratorio.php`
- Create: `Laboratorios/Rotas/RemoverVeiculoRotaLaboratorio.php`
- Create: `Testes/Laboratorios/NovaRotaLaboratorioTddTest.php`
- Create: `Testes/Laboratorios/RemoverPedidoRotaLaboratorioTddTest.php`
- Create: `Testes/Laboratorios/RemoverVeiculoRotaLaboratorioTddTest.php`

**Interfaces:**
- Consumes: `SandboxGuard` (Task 1), `RomaneioAdapterInterface` (Task 2), `CentralBroadcastRegistry` (B1 ✅)
- Produces:
  ```php
  // NovaRotaLaboratorio::executar(['block_id'=>int, 'pedidos'=>int[], 'veiculo_id'=>int, 'data'=>string])
  // => ['romaneio_id' => int, 'rota_id' => string]

  // RemoverPedidoRotaLaboratorio::executar(['block_id'=>int, 'romaneio_pedido_id'=>int, 'rota_id'=>int])
  // => ['ok' => true, 'rota_ficou_vazia' => bool]

  // RemoverVeiculoRotaLaboratorio::executar(['block_id'=>int, 'romaneio_veiculo_id'=>int, 'rota_id'=>int])
  // => ['ok' => true]
  ```

- [ ] **Step 1: Escrever os 3 testes (com spy de broadcast)**

```php
<?php
// Testes/Laboratorios/NovaRotaLaboratorioTddTest.php
require_once __DIR__ . '/../../Adapters/Sandbox/SandboxGuard.php';
require_once __DIR__ . '/../../Adapters/Romaneio/RomaneioAdapterInterface.php';
require_once __DIR__ . '/../../Eventos/CentralBroadcastInterface.php';
require_once __DIR__ . '/../../Eventos/CentralBroadcastRegistry.php';
require_once __DIR__ . '/../../Laboratorios/Rotas/NovaRotaLaboratorio.php';

use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Sandbox\SandboxGuard;
use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio\RomaneioAdapterInterface;
use GalaxiaMissoes\Logistica\sinais\Central2\Eventos\CentralBroadcastInterface;
use GalaxiaMissoes\Logistica\sinais\Central2\Eventos\CentralBroadcastRegistry;
use GalaxiaMissoes\Logistica\sinais\Central2\Laboratorios\Rotas\NovaRotaLaboratorio;

// Stub do adapter
$stubAdapter = new class implements RomaneioAdapterInterface {
    public array $criados = [];
    public function criarRomaneio(int $blockId, string $data): object {
        $this->criados[] = ['blockId' => $blockId, 'data' => $data];
        return (object) ['id' => 999];
    }
    public function vincularPedido(int $rid, int $pid, array $d): object { return (object)['id'=>1]; }
    public function desvincularPedido(int $id): bool { return true; }
    public function vincularVeiculo(int $rid, int $vid, float $c): object { return (object)['id'=>1]; }
    public function desvincularVeiculo(int $id): bool { return true; }
    public function atualizarSequencia(int $rvId, array $seq, array $m): bool { return true; }
};

// Spy de broadcast
CentralBroadcastRegistry::reset();
$spy = new class implements CentralBroadcastInterface {
    public array $calls = [];
    public function rotaAtualizada(int $rotaId, array $campos): void { $this->calls[] = ['m'=>'rotaAtualizada','rotaId'=>$rotaId]; }
    public function rotaRemovida(int $rotaId): void { $this->calls[] = ['m'=>'rotaRemovida','rotaId'=>$rotaId]; }
    public function markersAtualizados(array $markers): void { $this->calls[] = ['m'=>'markersAtualizados']; }
};
CentralBroadcastRegistry::registrar($spy);

class AssertNovaRota {
    public static int $passou = 0; public static int $falhas = 0;
    public static function true(bool $cond, string $msg): void {
        if ($cond) { self::$passou++; echo "[PASS] {$msg}\n"; }
        else        { self::$falhas++; echo "[FAIL] {$msg}\n"; }
    }
}

echo "TDD — NovaRotaLaboratorio\n\n";

$lab = new NovaRotaLaboratorio($stubAdapter);

// Block fora do sandbox lança
$threw = false;
try { $lab->executar(['block_id'=>99,'pedidos'=>[1],'veiculo_id'=>5,'data'=>'2026-06-19']); }
catch (\RuntimeException $e) { $threw = true; }
AssertNovaRota::true($threw, 'block fora do sandbox lança RuntimeException');

// Block correto: romaneio criado
$result = $lab->executar(['block_id'=>22,'pedidos'=>[10,11],'veiculo_id'=>5,'data'=>'2026-06-19']);
AssertNovaRota::true(!empty($result['romaneio_id']), 'resultado tem romaneio_id');
AssertNovaRota::true($stubAdapter->criados[0]['blockId'] === 22, 'adapter recebeu blockId=22');

// Broadcast emitido após escrita
AssertNovaRota::true(!empty($spy->calls), 'broadcast emitido');
AssertNovaRota::true($spy->calls[0]['m'] === 'rotaAtualizada', 'broadcast é rotaAtualizada');

echo "\nRESULTADO: PASS=" . AssertNovaRota::$passou . "  FAIL=" . AssertNovaRota::$falhas . "\n";
if (AssertNovaRota::$falhas > 0) { exit(1); }
echo "TODOS OS TESTES PASSARAM\n";
```

*(Criar testes similares para `RemoverPedidoRota` e `RemoverVeiculo` com o spy correspondente.)*

- [ ] **Step 2: Implementar `NovaRotaLaboratorio.php`**

```php
<?php
namespace GalaxiaMissoes\Logistica\sinais\Central2\Laboratorios\Rotas;

use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Sandbox\SandboxGuard;
use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio\RomaneioAdapterInterface;
use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio\RomaneioAdapter;
use GalaxiaMissoes\Logistica\sinais\Central2\Eventos\CentralBroadcastRegistry;

class NovaRotaLaboratorio
{
    private RomaneioAdapterInterface $adapter;

    public function __construct(?RomaneioAdapterInterface $adapter = null)
    {
        $this->adapter = $adapter ?? new RomaneioAdapter();
    }

    public function executar(array $input): array
    {
        SandboxGuard::verificar((int) ($input['block_id'] ?? 0));

        $blockId   = (int) $input['block_id'];
        $pedidos   = (array) ($input['pedidos'] ?? []);
        $veiculoId = (int) ($input['veiculo_id'] ?? 0);
        $data      = $input['data'] ?? date('Y-m-d');

        // 1. Criar romaneio
        $romaneio = $this->adapter->criarRomaneio($blockId, $data);

        // 2. Vincular veículo
        $rv = $this->adapter->vincularVeiculo($romaneio->id, $veiculoId, 0.0);

        // 3. Vincular pedidos
        foreach ($pedidos as $pedidoId) {
            $this->adapter->vincularPedido($romaneio->id, (int) $pedidoId, []);
        }

        // 4. Broadcast — best-effort após persistência
        if (CentralBroadcastRegistry::temRegistro()) {
            try {
                CentralBroadcastRegistry::resolver()->rotaAtualizada($romaneio->id, [
                    'pedidos'    => $pedidos,
                    'veiculo_id' => $veiculoId,
                ]);
            } catch (\Throwable $e) {
                error_log('[NovaRotaLaboratorio] broadcast falhou: ' . $e->getMessage());
            }
        }

        return ['romaneio_id' => $romaneio->id, 'rota_id' => (string) $romaneio->id];
    }
}
```

- [ ] **Step 3: Implementar `RemoverPedidoRotaLaboratorio.php`**

```php
<?php
namespace GalaxiaMissoes\Logistica\sinais\Central2\Laboratorios\Rotas;

use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Sandbox\SandboxGuard;
use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio\RomaneioAdapterInterface;
use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio\RomaneioAdapter;
use GalaxiaMissoes\Logistica\sinais\Central2\Eventos\CentralBroadcastRegistry;

class RemoverPedidoRotaLaboratorio
{
    private RomaneioAdapterInterface $adapter;

    public function __construct(?RomaneioAdapterInterface $adapter = null)
    {
        $this->adapter = $adapter ?? new RomaneioAdapter();
    }

    public function executar(array $input): array
    {
        SandboxGuard::verificar((int) ($input['block_id'] ?? 0));

        $romaneioPedidoId = (int) $input['romaneio_pedido_id'];
        $rotaId           = (int) ($input['rota_id'] ?? 0);

        $ok = $this->adapter->desvincularPedido($romaneioPedidoId);

        // Verificar se rota ficou vazia para emitir o evento correto
        // (a lógica de "ficou vazia" fica na Sonda/Adapter — Lab só lê o resultado)
        $rotaFicouVazia = (bool) ($input['ultima_pedido'] ?? false);

        if (CentralBroadcastRegistry::temRegistro()) {
            try {
                if ($rotaFicouVazia) {
                    CentralBroadcastRegistry::resolver()->rotaRemovida($rotaId);
                } else {
                    CentralBroadcastRegistry::resolver()->rotaAtualizada($rotaId, [
                        'pedido_removido' => (int) ($input['pedido_id'] ?? 0),
                    ]);
                }
            } catch (\Throwable $e) {
                error_log('[RemoverPedidoRotaLaboratorio] broadcast falhou: ' . $e->getMessage());
            }
        }

        return ['ok' => $ok, 'rota_ficou_vazia' => $rotaFicouVazia];
    }
}
```

- [ ] **Step 4: Implementar `RemoverVeiculoRotaLaboratorio.php`**

```php
<?php
// Mesmo padrão — desvincularVeiculo() + broadcast rotaRemovida($rotaId)
namespace GalaxiaMissoes\Logistica\sinais\Central2\Laboratorios\Rotas;

use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Sandbox\SandboxGuard;
use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio\RomaneioAdapterInterface;
use GalaxiaMissoes\Logistica\sinais\Central2\Adapters\Romaneio\RomaneioAdapter;
use GalaxiaMissoes\Logistica\sinais\Central2\Eventos\CentralBroadcastRegistry;

class RemoverVeiculoRotaLaboratorio
{
    private RomaneioAdapterInterface $adapter;

    public function __construct(?RomaneioAdapterInterface $adapter = null)
    {
        $this->adapter = $adapter ?? new RomaneioAdapter();
    }

    public function executar(array $input): array
    {
        SandboxGuard::verificar((int) ($input['block_id'] ?? 0));

        $romaneioVeiculoId = (int) $input['romaneio_veiculo_id'];
        $rotaId            = (int) ($input['rota_id'] ?? 0);

        $ok = $this->adapter->desvincularVeiculo($romaneioVeiculoId);

        if (CentralBroadcastRegistry::temRegistro()) {
            try {
                CentralBroadcastRegistry::resolver()->rotaRemovida($rotaId);
            } catch (\Throwable $e) {
                error_log('[RemoverVeiculoRotaLaboratorio] broadcast falhou: ' . $e->getMessage());
            }
        }

        return ['ok' => $ok];
    }
}
```

- [ ] **Step 5: Rodar os 3 testes**

```bash
cd /home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/Central2
php Testes/Laboratorios/NovaRotaLaboratorioTddTest.php
php Testes/Laboratorios/RemoverPedidoRotaLaboratorioTddTest.php
php Testes/Laboratorios/RemoverVeiculoRotaLaboratorioTddTest.php
```

- [ ] **Step 6: Commit**

```bash
git add Laboratorios/Rotas/{NovaRotaLaboratorio,RemoverPedidoRotaLaboratorio,RemoverVeiculoRotaLaboratorio}.php \
        Testes/Laboratorios/{NovaRota,RemoverPedidoRota,RemoverVeiculo}RotaLaboratorioTddTest.php
git commit -m "feat(central2): B3-4a — Labs Rotas (NovaRota + RemoverPedido + RemoverVeiculo) + TddTests"
```

---

## Task 5: Labs de Rotas — VincularMotorista + SalvarRoteirizacao (B3-4 parte 2)

**Files:**
- Create: `Laboratorios/Rotas/VincularMotoristaLaboratorio.php`
- Create: `Laboratorios/Rotas/DesvincularMotoristaLaboratorio.php`
- Create: `Laboratorios/Rotas/SalvarRoteirizacaoLaboratorio.php`
- Create: testes correspondentes em `Testes/Laboratorios/`

**Interfaces:**
- Consumes: mesmos de Task 4
- Produces:
  ```php
  // VincularMotoristaLaboratorio::executar(['block_id','romaneio_veiculo_id','motorista_nome','motorista_id?'])
  // => ['ok' => true]

  // SalvarRoteirizacaoLaboratorio::executar(['block_id','romaneio_veiculo_id','rota_id','sequencia'=>int[],'km_total','custo_total','minutos_total'])
  // => ['ok' => true]
  // Broadcast: rotaAtualizada($rotaId, ['sequencia'=>..., 'km_total'=>..., 'custo_total'=>..., 'minutos_total'=>...])
  ```

- [ ] **Step 1: Ler V1 `vincularMotoristaRota` (ln 191) e `salvarRoteirizacao` (ln 859) para entender campos**

- [ ] **Step 2: Escrever testes** (padrão idêntico Task 4 — guard + stub adapter + spy broadcast)

- [ ] **Step 3: Implementar Labs** (padrão idêntico Task 4 — guard → adapter → broadcast)

- [ ] **Step 4: Rodar testes — confirmar PASS**

- [ ] **Step 5: Commit**

```bash
git commit -m "feat(central2): B3-4b — Labs Rotas (VincularMotorista + SalvarRoteirizacao) + TddTests"
```

---

## Task 6: Labs de Portaria (B3-5)

**Files:**
- Create: `Laboratorios/Portaria/AprovarPortariaLaboratorio.php`
- Create: `Testes/Laboratorios/AprovarPortariaLaboratorioTddTest.php`

**Antes de implementar:** ler `aprovarPortaria` (ln 3857) na V1 para entender campos de input e a transição de status do processo.

**Interfaces:**
- Consumes: `SandboxGuard`, `ProcessoAdapterInterface` (Task 3), `CentralBroadcastRegistry`
- Produces:
  ```php
  // AprovarPortariaLaboratorio::executar(['block_id','processo_id','pedido_id','operador_id?'])
  // => ['ok' => true]
  // Broadcast: rotaAtualizada($rotaId, ['portaria_aprovada' => true, 'pedido_id' => $pedidoId])
  ```

- [ ] **Step 1: Escrever teste** (guard + stub ProcessoAdapter + spy broadcast — padrão Task 4)

- [ ] **Step 2: Implementar Lab** (padrão Task 4: guard → ProcessoAdapter::atualizarStatusMovimentacao → broadcast)

- [ ] **Step 3: Rodar teste — confirmar PASS**

- [ ] **Step 4: Commit**

```bash
git commit -m "feat(central2): B3-5 — AprovarPortariaLaboratorio + TddTest"
```

---

## Task 7: Labs Romaneio state machine — ConfirmarRomaneio (B3-6, por último)

**Atenção:** este é o Lab mais transacional. Implementar POR ÚLTIMO, após todos os outros Labs estarem testados.

**Files:**
- Create: `Laboratorios/Romaneio/ConfirmarRomaneioLaboratorio.php`
- Create: `Testes/Laboratorios/ConfirmarRomaneioLaboratorioTddTest.php`

**Antes de implementar:** ler `confirmarRomaneio` (ln 3661) e seus helpers na V1 para entender o fluxo de transição (status + missão + evento).

**Interfaces:**
- Consumes: `SandboxGuard`, `RomaneioAdapterInterface`, `MissaoLogisticaAdapterInterface`, `CentralBroadcastRegistry`
- Produces:
  ```php
  // ConfirmarRomaneioLaboratorio::executar(['block_id','romaneio_id','rota_id'])
  // => ['ok' => true, 'missao_criada' => bool]
  // Broadcast: rotaAtualizada($rotaId, ['confirmado' => true])
  // Ordem afirmada em teste: persistir → broadcast (nunca inverso)
  ```

- [ ] **Step 1: Ler V1 `confirmarRomaneio` (ln 3661) inteiro antes de escrever qualquer código**

- [ ] **Step 2: Escrever teste** (inclui afirmação de ordem: `persistido` antes de `broadcasted`)

```php
// No spy de broadcast: registrar timestamp da chamada
// No stub adapter: registrar timestamp da chamada
// Afirmar: adapter_timestamp < broadcast_timestamp
$ordem = [];
$stubAdapter = new class implements RomaneioAdapterInterface { /* ... registra microtime em $ordem */ };
$spy = new class implements CentralBroadcastInterface { /* ... registra microtime em $ordem */ };
// Após executar, afirmar que 'adapter' aparece antes de 'broadcast' em $ordem
```

- [ ] **Step 3: Implementar Lab**

- [ ] **Step 4: Rodar teste — confirmar PASS + ordem correta**

- [ ] **Step 5: Commit**

```bash
git commit -m "feat(central2): B3-6 — ConfirmarRomaneioLaboratorio + TddTest (ordem persistir→broadcast)"
```

---

## Task 8: Flag de escrita por Lab (B3-7)

Garante que nenhum Lab de escrita é chamado acidentalmente fora do sandbox antes de estar pronto.

**Files:**
- Modify: `Central2.php` — adicionar método `eEscritaAtiva(string $lab): bool`

- [ ] **Step 1: Adicionar flag ao controller**

```php
// Em Central2.php, após o bootstrap():
private const LABS_ESCRITA_ATIVOS = [
    // Adicionar conforme cada Lab é validado em dev:
    // 'NovaRota'        => true,
    // 'RemoverPedido'   => true,
];

public function eEscritaAtiva(string $lab): bool
{
    return self::LABS_ESCRITA_ATIVOS[$lab] ?? false;
}
```

- [ ] **Step 2: Rodar `php -l Central2.php`**

- [ ] **Step 3: Commit**

```bash
git add Central2.php
git commit -m "feat(central2): B3-7 — flag de escrita por Lab (default desligado)"
```

---

## Checklist final (self-review)

- [ ] Todos os Labs chamam `SandboxGuard::verificar` como primeira instrução
- [ ] Nenhum Lab emite broadcast antes de persistir
- [ ] Nenhum Lab importa modelo Galaxia direto — só via Adapter
- [ ] Todos os TddTests rodam sem banco (stubs injetados)
- [ ] `php -l` passa em todos os arquivos novos
- [ ] Nenhum arquivo fora de `Central2/` foi alterado
