# Central2 — Frontend Core (F-1 + F-2 + F-3) 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:** Criar `html/central2.js` — asset próprio do Central2 com o núcleo portado do `maps.js` (mapa, markers, rotas), store explícito substituindo o textarea V1, e camada de socket desacoplada nas rooms `_v2`.

**Architecture:** IIFE única em `central2.js`. Estado em `window.C2_STATE` (substitui `window.APP_STATE` + textarea `.rotasGeradas`). Socket desacoplado em `C2Socket` — subscreve rooms `_v2` e despacha eventos para handlers registrados explicitamente (sem `typeof fn === 'function'`). `central2.php` provê o HTML container e carrega o asset.

**Tech Stack:** JavaScript ES2020 (IIFE, const/let, arrow functions, Map/Set, optional chaining), Google Maps JavaScript API v3 (AdvancedMarkerElement, Polygon, DrawingManager), Scaledrone JS SDK (já carregado no layout do tema), jQuery (já carregado), `show()` do Galaxia i18n para todas as strings.

## Global Constraints

- **Arquivo alvo:** `componentes/Missoes/Logistica/sinais/Central2/html/central2.js`
- **Não editar** `controladores/_Src/maps.js` — é a fronteira fixa da V1.
- **Rooms V2 only:** `galaxia_logistica_central_v2` / `galaxia_logistica_board_v2`. Nunca as rooms sem `_v2`.
- **Sem textarea de estado:** nenhuma referência a `.rotasGeradas`. Estado em `window.C2_STATE`.
- **Sem `typeof fn === 'function'` dispatch:** handlers registrados explicitamente.
- **i18n:** toda string de UI passa por `show("texto PT literal")`. Nunca string hardcoded PT na UI.
- **Sem chamadas de form Galaxia:** sem `$(".salvarFormulario*").submit()`. Escrita futura vai via Labs (B3).
- **Sem portaria flow:** nenhuma função de aprovação de portaria neste asset — será Labs B3-5.
- **Google Maps API:** carregar com `loading=async` + `importLibrary` (não `google.maps.DirectionsService` inline — ver memory `pattern_google_maps_loading_async`).
- **Fonte de consulta:** `controladores/_Src/maps.js` — NÃO editar, só ler para portar o núcleo.

---

## Mapa de seções do maps.js × o que portar

| Seção original (linhas) | Portar? | Nota |
|------------------------|---------|------|
| 3–337 CentralSocketHandler (V1) | ❌ | Substituído por C2Socket (Task 6) |
| 440–449 APP_STATE | ✅ → C2_STATE | Renomear + remover campos textarea |
| 450–590 constantes + iconBeltMap | ✅ | Cópia direta |
| 592–655 filtro de isolamento | ✅ | Cópia direta |
| 658–778 naturalSort + getOffsetCoordinates | ✅ | Cópia direta |
| 780–927 createIconBelt | ✅ | Cópia direta |
| 931–1041 updateViewModeIndicator + createAnnotationBadge | ✅ | Cópia direta |
| 1043–1072 undo/redo | ✅ simplificado | Usar C2_STATE (sem textarea) |
| 1074–1173 drawRodizioWatermarks | ✅ | Cópia direta |
| 1175–1189 getRotasState | ✅ | Ler C2_STATE.rotas |
| 1248–1325 drawRoutePolygons | ✅ | Cópia direta |
| 1331–1348 calcularValorTotalRota | ✅ | Cópia direta |
| 1356–1370 addLongPressListener | ✅ | Cópia direta |
| 1375–1410 saveRotasState (textarea) | ❌ → updateC2State | Sem textarea |
| 1439–1540 auto-save/forceSave (form) | ❌ | B3 Labs farão a escrita |
| 1545–1574 calculateRouteDiameter | ✅ | Cópia direta |
| 1585–1701 assignOrdersToRoute | ✅ | Cópia direta |
| 1702–2071 createRouteInfoPanel | ✅ | Cópia direta (ações de escrita → disabled por ora) |
| 2072–2116 updateVehicleSectionUI | ✅ | Cópia direta |
| 2392–2440 convexHull + crossProduct | ✅ | Cópia direta |
| 2520–2552 updateAllMarkerVisuals | ✅ | Cópia direta |
| 3011–3044 removerMarcadoresDoMapaEJSON | ✅ | Cópia direta |
| 3100–3405 updateRotasSummaryTable | ❌ | HTML V1-específico |
| 3445–3548 clearingFlags/unassignAll | ❌ | Form V1 |
| 3549–3800 initializeMap + addMarkersToMap | ✅ | Adaptar config JSON |
| 5697–5825 clearMapMarkers + reinicializarMapa | ✅ | Cópia direta |
| 6274–6500 initializeDrawingManager + getMapInstance | ✅ | Cópia direta |
| 8555–8938 clusters | ✅ | Cópia direta |
| 11816–11889 virtualization | ✅ | Cópia direta |
| 13286–13354 sincronizarEstadoGlobal (textarea) | ❌ | Substituído por C2_STATE |
| 13387–13742 MarkerVisualStrategies + badges | ✅ | Cópia direta |

---

## Task 1: IIFE wrapper + C2_STATE + constantes (F-1 fase 1)

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central2/html/central2.js` (sobrescrever o stub de 16 linhas)

**Interfaces:**
- Produces: `window.C2_STATE`, `window.C2_CONST`, `iconBeltMap`, funções utilitárias básicas expostas em `window.C2`

- [ ] **Step 1: Abrir `html/central2.js` (16 linhas atuais) e substituir pelo scaffold IIFE**

```javascript
// central2.js — Central V2 (DEV ONLY)
// Asset próprio. NÃO editar maps.js. NÃO usar rooms V1.
(function (window, $, undefined) {
    'use strict';

    // ── Estado global da V2 (substitui APP_STATE + textarea .rotasGeradas) ──
    window.C2_STATE = {
        rotas:          {},         // { [rotaId]: { pedidos:[], cor:'', veiculo_id, ... } }
        markersConfig:  [],         // dados brutos vindos do backend
        markerMap:      new Map(),  // pedidoId → markerData
        rotaColorMap:   new Map(),  // rotaId → cor hex
        pedidoToRotaMap:new Map(),  // pedidoId → { rotaId, cor, tipo }
        mapInstance:    null,       // google.maps.Map
        rotaPolygons:   new Map(),  // rotaId → google.maps.Polygon
    };

    window.VIRTUALIZACAO_ATIVA = false;

    // ── Constantes de marker ──
    const AlturaMarcadorPadrao  = '20px';
    const LarguraMarcadorPadrao = '20px';
    const AlturaBadgePadrao     = '80px';
    const LarguraBadgePadrao    = '80px';
    const BadgeFontSize         = '10px';
    const MAX_ZOOM_SINGLE_POINT = 16;

    // iconBeltMap — copiar literalmente de maps.js ln 490–586
    const iconBeltMap = {
        sequencia:          { icon: 'fa-list-ol',       title: 'Sequência',    css: 'badge-primary'  },
        eta:                { icon: 'fa-clock',         title: 'ETA',          css: 'badge-info'     },
        observacaoCliente:  { icon: 'fa-comment',       title: 'Obs. cliente', css: 'badge-warning'  },
        cubagem:            { icon: 'fa-box',           title: 'Cubagem',      css: 'badge-secondary'},
        faturamento:        { icon: 'fa-dollar-sign',   title: 'Valor',        css: 'badge-success'  },
        prazo:              { icon: 'fa-calendar-alt',  title: 'Prazo',        css: 'badge-danger'   },
        // → completar com todas as entradas de maps.js ln 490–586
    };

    // ── Utilitários (portar de maps.js ln 658–778) ──
    function naturalSort(a, b) {
        // copiar de maps.js ln 658–716
        return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' });
    }

    function getOffsetCoordinates(lat, lng, id) {
        // copiar de maps.js ln 718–778
        const offset = id * 0.0001;
        return { lat: lat + offset, lng: lng };
    }

    // ── Store mutations ──
    function updateC2State(patch) {
        Object.assign(window.C2_STATE, patch);
    }

    function getRotasState() {
        const state = window.C2_STATE.rotas;
        const result = {};
        for (const [id, rota] of Object.entries(state)) {
            result[id] = { ...rota, pedidos: new Set(rota.pedidos || []) };
        }
        return result;
    }

    // ── Namespace público ──
    window.C2 = window.C2 || {};
    window.C2.updateState    = updateC2State;
    window.C2.getRotasState  = getRotasState;
    window.C2.naturalSort    = naturalSort;

    // ... (demais tarefas adicionam ao window.C2)

})(window, jQuery);
```

- [ ] **Step 2: Verificar sintaxe no browser (abrir DevTools → Console → sem erros)**

Navegar para `/missao/Logistica/Central2` (como dev — gate ativo). Verificar que `window.C2_STATE` existe no console.

- [ ] **Step 3: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central2/html/central2.js
git commit -m "feat(central2): F-1a — IIFE + C2_STATE + constantes + iconBeltMap"
```

---

## Task 2: Google Maps init + container HTML (F-1 fase 2)

**Files:**
- Modify: `html/central2.js` — adicionar `initC2Map`, `addMarkersToC2Map`, `getC2MapInstance`
- Modify: `html/central2.php` — adicionar container do mapa + config JSONs

**Interfaces:**
- Consumes: `window.C2_STATE`, Google Maps API (importLibrary)
- Produces: `window.C2.initMap()`, `window.C2.getMapInstance()`, `window.C2.addMarkers(config)`

- [ ] **Step 1: Adicionar container em `central2.php`**

```php
<?php
// Após o alert de dev (ln existente), adicionar:
?>
<!-- Config de markers (backend popula via Lab) -->
<textarea class="jsonConfig d-none" id="c2-json-config"><?= htmlspecialchars(json_encode([])) ?></textarea>
<textarea class="rotasDeCarregamento d-none" id="c2-rotas-config"><?= htmlspecialchars(json_encode([])) ?></textarea>

<div id="c2-map-container" style="width:100%;height:calc(100vh - 200px);border-radius:8px;overflow:hidden;">
    <div id="c2-map" style="width:100%;height:100%;"></div>
</div>

<script>
// Config do Google Maps (pegar do layout — mesmo mapId da V1)
window.C2_MAP_CONFIG = {
    mapId: '<?= defined('GOOGLE_MAPS_MAP_ID') ? GOOGLE_MAPS_MAP_ID : '' ?>',
    center: { lat: -23.5505, lng: -46.6333 },
    zoom: 11,
};
</script>
```

- [ ] **Step 2: Adicionar init do mapa em `central2.js`** (portar de maps.js ln 3549–3800)

```javascript
// Dentro da IIFE, após o bloco de constantes:

async function initC2Map() {
    const { Map } = await google.maps.importLibrary('maps');
    const { AdvancedMarkerElement } = await google.maps.importLibrary('marker');

    const cfg = window.C2_MAP_CONFIG || {};
    const map = new Map(document.getElementById('c2-map'), {
        center:    cfg.center || { lat: -23.5505, lng: -46.6333 },
        zoom:      cfg.zoom   || 11,
        mapId:     cfg.mapId  || '',
        // copiar cleanMapStyle de maps.js ln 1674–1701
    });

    window.C2_STATE.mapInstance = map;
    return map;
}

function getC2MapInstance() {
    return window.C2_STATE.mapInstance;
}

async function addMarkersToC2Map(markersConfig) {
    const { AdvancedMarkerElement } = await google.maps.importLibrary('marker');
    const map = getC2MapInstance();
    if (!map) return;

    for (const m of markersConfig) {
        // portar lógica de maps.js ln 3651–3800 (criação do AdvancedMarkerElement)
        const markerEl = document.createElement('div');
        markerEl.innerHTML = createIconBelt(m);  // Task 3

        const marker = new AdvancedMarkerElement({
            map,
            position: { lat: parseFloat(m.lat), lng: parseFloat(m.lng) },
            content:  markerEl,
            title:    m.destinatario || '',
        });

        window.C2_STATE.markerMap.set(Number(m.pedido_id), { ...m, markerInstance: marker });
    }

    window.C2_STATE.markersConfig = markersConfig;
}

// Expor no namespace público
window.C2.initMap    = initC2Map;
window.C2.getMap     = getC2MapInstance;
window.C2.addMarkers = addMarkersToC2Map;
```

- [ ] **Step 3: Chamar init no DOMContentLoaded**

```javascript
// No final da IIFE, antes do fechamento:
document.addEventListener('DOMContentLoaded', function () {
    initC2Map().catch(function (e) {
        console.error('[Central2] initMap falhou:', e);
    });
});
```

- [ ] **Step 4: Testar no browser**

Navegar `/missao/Logistica/Central2`. Mapa deve renderizar. `window.C2.getMap()` no console deve retornar instância do Google Maps.

- [ ] **Step 5: Commit**

```bash
git add html/central2.js html/central2.php
git commit -m "feat(central2): F-1b — Google Maps init + container HTML"
```

---

## Task 3: Icon belt + Marker visuals (F-1 fase 3)

**Files:**
- Modify: `html/central2.js` — adicionar `createIconBelt`, `updateAllMarkerVisuals`, `MarkerVisualStrategies`, badge creators

**Interfaces:**
- Consumes: `iconBeltMap`, `window.C2_STATE.markerMap`, `window.C2_STATE.pedidoToRotaMap`
- Produces: `createIconBelt(markerData): string` (HTML), `window.C2.updateAllMarkerVisuals()`

- [ ] **Step 1: Portar `createIconBelt` de maps.js ln 780–927**

```javascript
function createIconBelt(markerData) {
    // Copiar literalmente de maps.js ln 780–927.
    // Adaptar: substituir referências a window.APP_STATE por window.C2_STATE.
    // Remover qualquer referência a textarea ou form V1.
    // Retorna string HTML do cinturão de ícones do marker.
    //
    // Estrutura geral (simplificada):
    const items = [];
    if (markerData.sequencia) {
        const cfg = iconBeltMap.sequencia;
        items.push(`<span class="badge ${cfg.css}" title="${cfg.title}">
            <i class="fa ${cfg.icon}"></i> ${markerData.sequencia}
        </span>`);
    }
    // ... demais badges (eta, cubagem, valor, prazo, observacaoCliente)
    // Copiar toda a lógica de ln 780–927
    return `<div class="c2-marker-belt">${items.join('')}</div>`;
}
```

- [ ] **Step 2: Portar `updateAllMarkerVisuals` de maps.js ln 2520–2552**

```javascript
function updateAllMarkerVisuals() {
    // Para cada pedidoId em C2_STATE.markerMap,
    // aplicar a cor da rota (de C2_STATE.pedidoToRotaMap)
    // ao elemento DOM do marker (markerInstance.content).
    // Copiar lógica de maps.js ln 2520–2552.
    for (const [pedidoId, markerData] of window.C2_STATE.markerMap) {
        const rotaInfo = window.C2_STATE.pedidoToRotaMap.get(pedidoId);
        const el = markerData.markerInstance?.content;
        if (!el) continue;
        if (rotaInfo) {
            el.style.borderColor = rotaInfo.cor;
            el.classList.add('c2-marker--alocado');
        } else {
            el.style.borderColor = '';
            el.classList.remove('c2-marker--alocado');
        }
    }
}
window.C2.updateAllMarkerVisuals = updateAllMarkerVisuals;
```

- [ ] **Step 3: Portar `MarkerVisualStrategies` de maps.js ln 13387–13742**

```javascript
// Portar de maps.js ln 13447–13509:
const MarkerVisualStrategies = {
    default:    function (el, data) { /* limpar badges extras */ },
    cubagem:    function (el, data) { el.querySelector('.c2-badge-cubagem')?.classList.remove('d-none'); },
    anotacoes:  function (el, data) { /* mostrar badge de anotação */ },
    valor:      function (el, data) { /* mostrar badge de valor */ },
    prazo:      function (el, data) { /* mostrar badge de prazo */ },
};

let currentOverlayMode = 'default';
function aplicarVisualizacaoNoMarcador(el, data) {
    const strategy = MarkerVisualStrategies[currentOverlayMode] || MarkerVisualStrategies.default;
    strategy(el, data);
}
window.C2.setOverlayMode = function (mode) {
    currentOverlayMode = mode;
    updateAllMarkerVisuals();
};
```

- [ ] **Step 4: Portar badge creators de maps.js ln 13546–13742**

Copiar `createCubageBadge`, `createDeadlineBadge`, `createValueBadge` literalmente. Adaptar referências a `APP_STATE` → `C2_STATE`.

- [ ] **Step 5: Verificar no browser**

```javascript
// Console: após carregar marcadores
window.C2.addMarkers([{pedido_id:1,lat:-23.55,lng:-46.63,sequencia:1,cubagem_total:5}]);
// Verificar que marker aparece no mapa com icon belt
```

- [ ] **Step 6: Commit**

```bash
git commit -m "feat(central2): F-1c — createIconBelt + MarkerVisuals + badges"
```

---

## Task 4: Route polygons + convex hull (F-1 fase 4)

**Files:**
- Modify: `html/central2.js` — adicionar `convexHull`, `drawRoutePolygons`, `generateRouteMarkerIcon`

**Interfaces:**
- Consumes: `window.C2_STATE.rotas`, `window.C2_STATE.markerMap`, `getC2MapInstance()`
- Produces: `window.C2.drawRoutePolygons()`, `window.C2.clearRoutePolygons()`

- [ ] **Step 1: Portar `convexHull` e `crossProduct` de maps.js ln 2404–2441**

```javascript
function crossProduct(o, a, b) {
    return (a.lng - o.lng) * (b.lat - o.lat) - (a.lat - o.lat) * (b.lng - o.lng);
}

function convexHull(points) {
    // Copiar literalmente de maps.js ln 2411–2439
    if (points.length < 3) return points;
    points = points.slice().sort((a, b) => a.lng !== b.lng ? a.lng - b.lng : a.lat - b.lat);
    const lower = [];
    for (const p of points) {
        while (lower.length >= 2 && crossProduct(lower[lower.length-2], lower[lower.length-1], p) <= 0)
            lower.pop();
        lower.push(p);
    }
    const upper = [];
    for (let i = points.length - 1; i >= 0; i--) {
        const p = points[i];
        while (upper.length >= 2 && crossProduct(upper[upper.length-2], upper[upper.length-1], p) <= 0)
            upper.pop();
        upper.push(p);
    }
    upper.pop(); lower.pop();
    return lower.concat(upper);
}
```

- [ ] **Step 2: Portar `drawRoutePolygons` de maps.js ln 1248–1325**

```javascript
function drawRoutePolygons() {
    const map = getC2MapInstance();
    if (!map) return;

    // Limpar polígonos antigos
    for (const [, poly] of window.C2_STATE.rotaPolygons) {
        poly.setMap(null);
    }
    window.C2_STATE.rotaPolygons.clear();

    const rotas = window.C2_STATE.rotas;
    for (const [rotaId, rota] of Object.entries(rotas)) {
        const cor   = window.C2_STATE.rotaColorMap.get(Number(rotaId)) || '#1a73e8';
        const pedidos = Array.isArray(rota.pedidos) ? rota.pedidos : [];

        const points = pedidos.map(pid => {
            const m = window.C2_STATE.markerMap.get(Number(pid));
            return m ? { lat: parseFloat(m.lat), lng: parseFloat(m.lng) } : null;
        }).filter(Boolean);

        if (points.length < 1) continue;
        const hull = points.length >= 3 ? convexHull(points) : points;

        const polygon = new google.maps.Polygon({
            paths:         hull,
            strokeColor:   cor,
            strokeOpacity: 0.8,
            strokeWeight:  2,
            fillColor:     cor,
            fillOpacity:   0.15,
            map,
        });
        window.C2_STATE.rotaPolygons.set(Number(rotaId), polygon);
    }
}

function clearRoutePolygons() {
    for (const [, poly] of window.C2_STATE.rotaPolygons) poly.setMap(null);
    window.C2_STATE.rotaPolygons.clear();
}

window.C2.drawRoutePolygons  = drawRoutePolygons;
window.C2.clearRoutePolygons = clearRoutePolygons;
```

- [ ] **Step 3: Portar `generateRouteMarkerIcon` de maps.js ln 2392–2400**

```javascript
function generateRouteMarkerIcon(color) {
    // Retorna SVG string de pin colorido
    return `<svg width="20" height="28" viewBox="0 0 20 28" xmlns="http://www.w3.org/2000/svg">
        <path d="M10 0C4.48 0 0 4.48 0 10c0 7.5 10 18 10 18s10-10.5 10-18C20 4.48 15.52 0 10 0z"
              fill="${color}" stroke="#fff" stroke-width="1.5"/>
    </svg>`;
}
window.C2.generateRouteMarkerIcon = generateRouteMarkerIcon;
```

- [ ] **Step 4: Teste no browser**

```javascript
// Simular rota com 3 pedidos no mapa:
window.C2_STATE.rotas = { 1: { pedidos: [1,2,3] } };
window.C2_STATE.rotaColorMap.set(1, '#e53935');
window.C2.drawRoutePolygons();
// Polígono vermelho deve aparecer no mapa cobrindo os 3 markers
```

- [ ] **Step 5: Commit**

```bash
git commit -m "feat(central2): F-1d — convexHull + drawRoutePolygons + generateRouteMarkerIcon"
```

---

## Task 5: Store F-2 — updateC2State + histórico undo/redo (F-2)

**Files:**
- Modify: `html/central2.js` — completar `updateC2State`, `undo`, `redo`, `pushStateToHistory`

O store já tem `updateC2State` do Task 1. Aqui adiciona o histórico em memória (sem textarea).

**Interfaces:**
- Consumes: `window.C2_STATE.rotas`
- Produces: `window.C2.undo()`, `window.C2.redo()`, `window.C2.pushHistory()`

- [ ] **Step 1: Adicionar histórico em memória**

```javascript
const historyStack = [];
const redoStack    = [];
const MAX_HISTORY  = 20;

function pushC2History() {
    historyStack.push(JSON.parse(JSON.stringify(window.C2_STATE.rotas)));
    if (historyStack.length > MAX_HISTORY) historyStack.shift();
    redoStack.length = 0; // mutação limpa redo
}

function undoC2() {
    if (!historyStack.length) return;
    redoStack.push(JSON.parse(JSON.stringify(window.C2_STATE.rotas)));
    window.C2_STATE.rotas = historyStack.pop();
    drawRoutePolygons();
    updateAllMarkerVisuals();
}

function redoC2() {
    if (!redoStack.length) return;
    historyStack.push(JSON.parse(JSON.stringify(window.C2_STATE.rotas)));
    window.C2_STATE.rotas = redoStack.pop();
    drawRoutePolygons();
    updateAllMarkerVisuals();
}

window.C2.pushHistory = pushC2History;
window.C2.undo        = undoC2;
window.C2.redo        = redoC2;
```

- [ ] **Step 2: Verificar no browser**

```javascript
window.C2_STATE.rotas = { 1: { pedidos: [1,2] } };
window.C2.pushHistory();
window.C2_STATE.rotas = { 1: { pedidos: [1,2,3] } };
window.C2.undo();
console.log(window.C2_STATE.rotas[1].pedidos.length); // 2
```

- [ ] **Step 3: Commit**

```bash
git commit -m "feat(central2): F-2 — store C2_STATE + undo/redo em memória (sem textarea)"
```

---

## Task 6: Camada de socket desacoplada V2 (F-3)

**Files:**
- Modify: `html/central2.js` — adicionar objeto `C2Socket` com init, on/off, dispatch
- Modify: `html/central2.php` — garantir que Scaledrone SDK está carregado antes de `central2.js`

**Interfaces:**
- Consumes: `CentralBroadcastInterface` contract (contrato PHP B1-1, payload JS: `{action, data, timestamp}`)
- Produces:
  ```javascript
  C2Socket.init(droneChannelId)  // conecta às rooms _v2
  C2Socket.on(action, handler)   // registra handler para action
  C2Socket.off(action)           // remove handler
  // Rooms subscritas: 'observable-galaxia_logistica_central_v2'
  //                   'observable-galaxia_logistica_board_v2'
  ```

- [ ] **Step 1: Implementar `C2Socket`**

```javascript
const C2Socket = (function () {
    const CHANNEL_ID    = 'uzJPCX4DfT380eoN'; // mesmo da V1 (EdicaoRotaSocketNotifier)
    const ROOM_CENTRAL  = 'observable-galaxia_logistica_central_v2';
    const ROOM_BOARD    = 'observable-galaxia_logistica_board_v2';

    const handlers = {};   // { action: fn }
    let drone      = null;
    let connected  = false;

    function on(action, handler) {
        handlers[action] = handler;
    }
    function off(action) {
        delete handlers[action];
    }

    function dispatch(payload) {
        if (!payload || !payload.action) return;
        const handler = handlers[payload.action];
        if (typeof handler === 'function') {
            try { handler(payload.data, payload.timestamp); }
            catch (e) { console.error('[C2Socket] handler falhou:', payload.action, e); }
        }
    }

    function subscribeRoom(room) {
        const ch = drone.subscribe(room);
        ch.bind('message', function (_, msg) {
            const data = typeof msg === 'string' ? JSON.parse(msg) : msg;
            dispatch(data);
        });
    }

    function showOfflineAlert() {
        // Usar padrão de toast/alert já no módulo
        const el = document.getElementById('c2-socket-offline-alert');
        if (el) el.classList.remove('d-none');
    }
    function hideOfflineAlert() {
        const el = document.getElementById('c2-socket-offline-alert');
        if (el) el.classList.add('d-none');
    }

    function init(channelId) {
        channelId = channelId || CHANNEL_ID;
        drone = new Scaledrone(channelId);

        drone.on('open', function (error) {
            if (error) { console.error('[C2Socket] open error:', error); showOfflineAlert(); return; }
            connected = true;
            hideOfflineAlert();
            subscribeRoom(ROOM_CENTRAL);
            subscribeRoom(ROOM_BOARD);
        });

        drone.on('error', function (err) {
            console.error('[C2Socket] error:', err);
            showOfflineAlert();
        });

        drone.on('close', function () {
            connected = false;
            showOfflineAlert();
        });
    }

    return { init, on, off, connected: function () { return connected; } };
})();

window.C2.socket = C2Socket;
```

- [ ] **Step 2: Registrar handlers para os 3 eventos da interface**

```javascript
// No final da IIFE, antes de DOMContentLoaded:
function registrarHandlersSocket() {
    // route_update → atualiza C2_STATE.rotas + redesenha
    C2Socket.on('route_update', function (data) {
        const rotaId = Number(data.rota_id);
        if (!rotaId) return;
        window.C2_STATE.rotas[rotaId] = Object.assign(window.C2_STATE.rotas[rotaId] || {}, data);
        drawRoutePolygons();
        updateAllMarkerVisuals();
    });

    // route_delete → remove rota do estado + redesenha
    C2Socket.on('route_delete', function (data) {
        const rotaId = Number(data.rota_id);
        if (!rotaId) return;
        delete window.C2_STATE.rotas[rotaId];
        window.C2_STATE.rotaColorMap.delete(rotaId);
        // Limpar pedidoToRotaMap dos pedidos desta rota
        for (const [pid, info] of window.C2_STATE.pedidoToRotaMap) {
            if (info.rotaId === rotaId) window.C2_STATE.pedidoToRotaMap.delete(pid);
        }
        drawRoutePolygons();
        updateAllMarkerVisuals();
    });

    // markers_update → recarregar markers
    C2Socket.on('markers_update', function (data) {
        if (Array.isArray(data.markers) && data.markers.length) {
            addMarkersToC2Map(data.markers);
        }
    });
}
```

- [ ] **Step 3: Inicializar socket no DOMContentLoaded**

```javascript
document.addEventListener('DOMContentLoaded', function () {
    initC2Map().catch(function (e) { console.error('[Central2] initMap:', e); });
    registrarHandlersSocket();
    C2Socket.init();
});
```

- [ ] **Step 4: Adicionar alert de offline em `central2.php`**

```php
<!-- Alerta offline — oculto por padrão, C2Socket.init() exibe quando socket cai -->
<div id="c2-socket-offline-alert" class="alert alert-danger d-none position-fixed bottom-0 end-0 m-4" style="z-index:9999">
    <i class="fa fa-wifi-slash me-2"></i>
    <?= show('Sincronização offline — atualize a página') ?>
</div>
```

- [ ] **Step 5: Verificar no browser**

```javascript
// DevTools → Network → offline
// Aguardar ~5s → alert "Sincronização offline" deve aparecer
// DevTools → Network → online → alert deve sumir ao reconectar
```

- [ ] **Step 6: Commit**

```bash
git add html/central2.js html/central2.php
git commit -m "feat(central2): F-3 — C2Socket desacoplado rooms _v2 + handlers route_update/delete/markers"
```

---

## Task 7: i18n — todas as strings via `show()` (F-4)

**Files:**
- Modify: `html/central2.js` — envolver todas as strings PT em `show("...")`
- Modify: `html/central2.php` — idem

- [ ] **Step 1: Grep por strings hardcoded em PT**

```bash
grep -n '"[A-Za-zÀ-ú]' html/central2.js | grep -v 'show(' | grep -v '//'
grep -n "'[A-Za-zÀ-ú]" html/central2.js | grep -v 'show(' | grep -v '//'
```

- [ ] **Step 2: Envolver cada string encontrada**

Para JS dentro de `central2.js`:
```javascript
// Antes:
alert('Selecione os pedidos');
// Depois:
alert(show('Selecione os pedidos'));
```

> **Atenção:** `show()` no contexto JS precisa estar disponível no escopo global — verificar se está sendo exportado do PHP para a view. Padrão: passar strings já traduzidas via `data-*` no container do PHP, ou usar o objeto de tradução global do Galaxia.

- [ ] **Step 3: Verificar no browser com idioma ES ou EN** (configurar nas prefs do tenant no dev)

- [ ] **Step 4: Commit**

```bash
git commit -m "feat(central2): F-4 — i18n via show() em central2.js + central2.php"
```

---

## Checklist final (self-review)

- [ ] `window.C2_STATE` existe e tem todos os campos (rotas, markerMap, rotaColorMap, pedidoToRotaMap, mapInstance, rotaPolygons)
- [ ] `maps.js` não foi alterado (diff limpo)
- [ ] Nenhuma referência a `.rotasGeradas` ou `textarea` de estado em `central2.js`
- [ ] Nenhuma room sem `_v2` no `C2Socket`
- [ ] Nenhum `typeof fn === 'function'` — handlers registrados via `C2Socket.on()`
- [ ] Todas as strings PT passam por `show()`
- [ ] DevTools console sem erros ao abrir `/missao/Logistica/Central2`
- [ ] Socket offline exibe alert; reconexão limpa o alert
- [ ] `window.C2.drawRoutePolygons()` redesenha polígonos ao mudar `C2_STATE.rotas`
