# Ícone "Endereços" no Carregamento (V2) 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:** Estender o ícone "Endereços" (já entregue na Separação/fila única) para a tela de Carregamento V2, cujo pipeline de renderização é 100% client-side (sem re-render de card por item).

**Architecture:** `CarregamentoPresenter` faz uma chamada batch a `EnderecosProdutoWmsLaboratorio::resolverMapa()` (já existente, sem mudanças) e expõe `tem_enderecos_wms` por item no `jsItensData`. Um botão novo, oculto por padrão, é renderizado uma vez em `execucao_bipagem.php`; `CarregamentoV2.selecionarItem()` (JS) mostra/esconde esse botão e reescreve seu `produto_id` a cada troca de item — mesmo mecanismo já usado pelo botão "ACOMPANHAR" (`#btn-status-separacao`) existente na mesma tela.

**Tech Stack:** PHP 8 (Galaxia framework), jQuery. Sem mudanças de banco/backend — reaproveita 100% a Sonda/Laboratório/rota de modal já construídos e revisados na feature anterior (Separação).

## Global Constraints

- Escopo só do **Carregamento** — o novo flag `exibirBadgeEnderecosWms` só é ligado em `App/html/carregamento/item.php`. `execucao_bipagem.php` é compartilhado com Retorno, que NÃO deve ganhar o botão nesta entrega.
- **1 query batch por missão**, não 1 por item — a chamada a `EnderecosProdutoWmsLaboratorio::resolverMapa()` deve ficar ANTES do `foreach` de itens no Presenter, recebendo todos os `produto_id` da missão de uma vez.
- Reaproveita 100% a infraestrutura já existente e revisada: `WmsMovimentoSonda::mapaSaldoPorProduto()`, `EnderecosProdutoWmsLaboratorio::resolverMapa()`, `App::enderecosProdutoWms()` e sua view — **nenhum desses arquivos é modificado nesta entrega**.
- Sem migrations novas, sem endpoint `/api/v1` novo.
- Testes são de inspeção de fonte (regex/substring em arquivo), seguindo o padrão já usado em `CarregamentoPresenterCubagemTddTest.php` e `CubagemCarregamentoUiTddTest.php` — não PHPUnit, sem mocks.

---

### Task 1: Batch `tem_enderecos_wms` em `CarregamentoPresenter`

**Files:**
- Modify: `componentes/Constelacoes/Logistica/MissaoLogistica/Presenters/CarregamentoPresenter.php`
- Test: `componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Presenters/CarregamentoPresenterEnderecosWmsTddTest.php`

**Interfaces:**
- Consumes: `EnderecosProdutoWmsLaboratorio::resolverMapa(array $produtoIds): array` (já existente, sem mudanças) — retorna `produto_id => [ ['localizacao_codigo'=>string,'quantidade'=>float], ... ]`.
- Produces: `jsItensData[$itemId]['tem_enderecos_wms']` (bool) — usado pelo JS na Task 2 (`item.tem_enderecos_wms`).

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

Criar `componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Presenters/CarregamentoPresenterEnderecosWmsTddTest.php`:

```php
<?php

declare(strict_types=1);

require_once '/home/goldie/www/genesis/vendor/autoload.php';

/**
 * TDD — CarregamentoPresenter: batch tem_enderecos_wms (produto -> tem endereço confirmado)
 *
 * Verificação estrutural (não chamamos present() diretamente — depende de Satélites/DB,
 * mesmo motivo do CarregamentoPresenterCubagemTddTest.php). A agregação real já é coberta
 * pelos testes de integração de WmsMovimentoSonda/EnderecosProdutoWmsLaboratorio (feature
 * anterior, já revisados).
 *
 * O que este teste garante: o Presenter chama EnderecosProdutoWmsLaboratorio::resolverMapa()
 * UMA VEZ, ANTES do foreach de itens (batch, sem N+1), e expõe tem_enderecos_wms por item.
 */

class AssertPresenterEnderecos
{
    public static int $passou = 0;
    public static int $falhas = 0;

    public static function verdadeiro(bool $condicao, string $msg): void
    {
        if ($condicao) {
            self::$passou++;
            echo "✅ [PASS] $msg\n";
        } else {
            self::$falhas++;
            echo "❌ [FAIL] $msg\n";
        }
    }

    public static function summary(): void
    {
        echo "\n--- RESUMO ---\n";
        echo "Passou: " . self::$passou . " | Falhou: " . self::$falhas . "\n";
        if (self::$falhas > 0) exit(1);
        echo "Tests passed\n";
    }
}

$presenterPath = '/home/goldie/www/genesis/componentes/Constelacoes/Logistica/MissaoLogistica/Presenters/CarregamentoPresenter.php';
$src = file_get_contents($presenterPath);

echo "Iniciando Testes: CarregamentoPresenterEnderecosWmsTddTest\n\n";

AssertPresenterEnderecos::verdadeiro(
    strpos($src, 'EnderecosProdutoWmsLaboratorio') !== false,
    'CarregamentoPresenter.php referencia EnderecosProdutoWmsLaboratorio'
);

AssertPresenterEnderecos::verdadeiro(
    strpos($src, "tem_enderecos_wms") !== false,
    'CarregamentoPresenter.php expõe tem_enderecos_wms no jsItensData'
);

AssertPresenterEnderecos::verdadeiro(
    strpos($src, 'resolverMapa(') !== false,
    'CarregamentoPresenter.php chama resolverMapa()'
);

// Batch, sem N+1: a instanciação do Laboratorio deve estar ANTES do foreach de itens.
$posForeach = strpos($src, 'foreach ($jsItensData as $itemId => &$itemJson)');
$posLab     = strpos($src, 'EnderecosProdutoWmsLaboratorio');
AssertPresenterEnderecos::verdadeiro(
    $posForeach !== false && $posLab !== false && $posLab < $posForeach,
    'EnderecosProdutoWmsLaboratorio é chamado ANTES do foreach (batch, 1 query pra missão toda)'
);

AssertPresenterEnderecos::summary();
```

- [ ] **Step 2: Rodar o teste e confirmar que falha**

Run: `php componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Presenters/CarregamentoPresenterEnderecosWmsTddTest.php`
Expected: falhas nas 4 verificações (nada disso existe ainda em `CarregamentoPresenter.php`)

- [ ] **Step 3: Implementar o batch no Presenter**

Em `componentes/Constelacoes/Logistica/MissaoLogistica/Presenters/CarregamentoPresenter.php`, o conteúdo atual do método `present()` é exatamente:

```php
    public static function present(object $missao, array $itens, ?array $progresso, $galaxia = null): array
    {
        $dadosComuns = MissaoPresenter::dadosComuns($missao, $itens, $progresso, $galaxia);

        // Enriquecimento: informações de separação (quem separou e quando)
        $separacaoInfo = \GalaxiaConstelacoes\Logistica\MissaoLogistica\Satelites\MissoesConsultas::getSeparacaoInfo($itens);

        // JS Data com campos extras de separação
        $jsItensData = MissaoPresenter::montarJsonFrontendBase($itens);
        foreach ($jsItensData as $itemId => &$itemJson) {
            $item = null;
            foreach ($itens as $iteradorItem) {
                if ($iteradorItem->id == $itemId) {
                    $item = $iteradorItem;
                    break;
                }
            }
            $romaneioId = $item ? ($item->romaneio_id ?? $item->referencia_id ?? null) : null;
            $produtoId = $item ? ($item->produto_id ?? null) : null;
            $pedidoId = $item ? ($item->pedido_id ?? null) : null;
            $hashItem = $item->hash_referencia ?? null;

            // Busca Prioritária: via Hash (Novo Padrão)
            $informacaoSeparacao = ($hashItem) ? ($separacaoInfo['hashes'][$hashItem] ?? null) : null;

            // Busca Fallback: via Identificadores (Legado)
            if (!$informacaoSeparacao && $romaneioId && $produtoId) {
                $informacaoSeparacao = $separacaoInfo['legado'][$romaneioId][$produtoId][$pedidoId] ?? null;
            }

            $daItem = is_string($item->dados_adicionais ?? '')
                ? json_decode($item->dados_adicionais ?? '', true)
                : (array)($item->dados_adicionais ?? []);
            $itemJson['cubagem_unit'] = floatval($daItem['cubagem'] ?? 0);
            $itemJson['localizacao'] = $item->localizacao_codigo ?? '';
            $itemJson['separador_nome'] = $informacaoSeparacao['separador_nome'] ?? null;
            $itemJson['separado_em'] = $informacaoSeparacao['separado_em'] ?? null;
            $itemJson['ocorrencia_tipo'] = $informacaoSeparacao['ocorrencia_tipo'] ?? null;
        }

        $dadosComuns['jsItensData'] = $jsItensData;


        //   exportCliente($dadosComuns);
        return $dadosComuns;
    }
```

Substituir por (2 mudanças: batch antes do `foreach`, e uma linha nova dentro do `foreach`):

```php
    public static function present(object $missao, array $itens, ?array $progresso, $galaxia = null): array
    {
        $dadosComuns = MissaoPresenter::dadosComuns($missao, $itens, $progresso, $galaxia);

        // Enriquecimento: informações de separação (quem separou e quando)
        $separacaoInfo = \GalaxiaConstelacoes\Logistica\MissaoLogistica\Satelites\MissoesConsultas::getSeparacaoInfo($itens);

        // JS Data com campos extras de separação
        $jsItensData = MissaoPresenter::montarJsonFrontendBase($itens);

        // [NOVO] Batch: produto_id -> tem ao menos 1 endereço WMS confirmado (1 query pra
        // missão inteira, evita N+1 — mesmo espírito de getSeparacaoInfo() acima).
        $produtoIdsMissao = array_values(array_unique(array_filter(
            array_map(fn($i) => (int)($i->produto_id ?? 0), $itens)
        )));
        $mapaEnderecosWms = (new \GalaxiaMissoes\Logistica\sinais\AlocacaoWms\Laboratorios\EnderecosProdutoWmsLaboratorio())
            ->resolverMapa($produtoIdsMissao);

        foreach ($jsItensData as $itemId => &$itemJson) {
            $item = null;
            foreach ($itens as $iteradorItem) {
                if ($iteradorItem->id == $itemId) {
                    $item = $iteradorItem;
                    break;
                }
            }
            $romaneioId = $item ? ($item->romaneio_id ?? $item->referencia_id ?? null) : null;
            $produtoId = $item ? ($item->produto_id ?? null) : null;
            $pedidoId = $item ? ($item->pedido_id ?? null) : null;
            $hashItem = $item->hash_referencia ?? null;

            // Busca Prioritária: via Hash (Novo Padrão)
            $informacaoSeparacao = ($hashItem) ? ($separacaoInfo['hashes'][$hashItem] ?? null) : null;

            // Busca Fallback: via Identificadores (Legado)
            if (!$informacaoSeparacao && $romaneioId && $produtoId) {
                $informacaoSeparacao = $separacaoInfo['legado'][$romaneioId][$produtoId][$pedidoId] ?? null;
            }

            $daItem = is_string($item->dados_adicionais ?? '')
                ? json_decode($item->dados_adicionais ?? '', true)
                : (array)($item->dados_adicionais ?? []);
            $itemJson['cubagem_unit'] = floatval($daItem['cubagem'] ?? 0);
            $itemJson['localizacao'] = $item->localizacao_codigo ?? '';
            $itemJson['separador_nome'] = $informacaoSeparacao['separador_nome'] ?? null;
            $itemJson['separado_em'] = $informacaoSeparacao['separado_em'] ?? null;
            $itemJson['ocorrencia_tipo'] = $informacaoSeparacao['ocorrencia_tipo'] ?? null;
            // [NOVO] Endereços WMS — só a flag de presença; a lista completa é buscada
            // sob demanda pelo modal (App::enderecosProdutoWms), sem duplicar payload.
            $itemJson['tem_enderecos_wms'] = !empty($mapaEnderecosWms[(int)$produtoId]);
        }

        $dadosComuns['jsItensData'] = $jsItensData;


        //   exportCliente($dadosComuns);
        return $dadosComuns;
    }
```

- [ ] **Step 4: Rodar o teste e confirmar que passa**

Run: `php componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Presenters/CarregamentoPresenterEnderecosWmsTddTest.php`
Expected: `Tests passed` (Passou: 4 | Falhou: 0)

- [ ] **Step 5: Lint de sintaxe PHP**

Run: `php -l componentes/Constelacoes/Logistica/MissaoLogistica/Presenters/CarregamentoPresenter.php`
Expected: `No syntax errors detected`

- [ ] **Step 6: Commit**

Este projeto não commita automaticamente nesta sessão — pule este passo (deixe as mudanças na working tree). Se estiver executando este plano em outra sessão/contexto sem essa restrição, commit normalmente:
```bash
git add componentes/Constelacoes/Logistica/MissaoLogistica/Presenters/CarregamentoPresenter.php componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Presenters/CarregamentoPresenterEnderecosWmsTddTest.php
git commit -m "feat(carregamento): batch tem_enderecos_wms no CarregamentoPresenter"
```

---

### Task 2: Botão "Endereços" no Carregamento (view + JS)

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/App/html/carregamento/item.php` (liga o flag no `$configTipo`)
- Modify: `componentes/Missoes/Logistica/sinais/App/html/shared/components/v2/execucao_bipagem.php` (lê o flag, renderiza o botão)
- Modify: `componentes/Missoes/Logistica/sinais/App/html/carregamento/item.js` (mostra/esconde + reescreve `produto_id`)
- Test: `componentes/Missoes/Logistica/sinais/App/Testes/Html/EnderecosWmsCarregamentoUiTddTest.php`

**Interfaces:**
- Consumes: `jsItensData[id].tem_enderecos_wms` e `jsItensData[id].produto_id` (Task 1); rota `enderecosProdutoWms` (já existente, feature anterior).
- Produces: botão `#btn-enderecos-wms`, visível/oculto e com `produto_id` atualizado a cada troca de item.

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

Criar `componentes/Missoes/Logistica/sinais/App/Testes/Html/EnderecosWmsCarregamentoUiTddTest.php`:

```php
<?php

/**
 * TDD — Botão "Endereços WMS" no Carregamento V2 (execucao_bipagem.php + item.php + item.js)
 *
 * Testes de inspeção de fonte que garantem:
 * 1. carregamento/item.php liga o flag exibirBadgeEnderecosWms no $configTipo
 * 2. execucao_bipagem.php lê o flag e renderiza o botão condicionalmente, oculto por padrão
 * 3. carregamento/item.js mostra/esconde o botão e reescreve produto_id a cada troca de item
 */

class AssertEnderecosCarregamentoUi
{
    public static int $passou = 0;
    public static int $falhas = 0;

    public static function verdadeiro(bool $condicao, string $msg): void
    {
        if ($condicao) {
            self::$passou++;
            echo "✅ [PASS] $msg\n";
        } else {
            self::$falhas++;
            echo "❌ [FAIL] $msg\n";
        }
    }

    public static function contemTexto(string $src, string $trecho, string $msg): void
    {
        self::verdadeiro(strpos($src, $trecho) !== false, $msg);
    }

    public static function summary(): void
    {
        echo "\n--- RESUMO ---\n";
        echo "Passou: " . self::$passou . " | Falhou: " . self::$falhas . "\n";
        if (self::$falhas > 0) exit(1);
        echo "Tests passed\n";
    }
}

$itemPhp = '/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/App/html/carregamento/item.php';
$bipagem = '/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/App/html/shared/components/v2/execucao_bipagem.php';
$itemJs  = '/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/App/html/carregamento/item.js';

$srcItemPhp = file_get_contents($itemPhp);
$srcBipagem = file_get_contents($bipagem);
$srcJs      = file_get_contents($itemJs);

echo "Iniciando Testes: EnderecosWmsCarregamentoUiTddTest\n\n";

echo "── carregamento/item.php ──────────────────────────────────────\n";
AssertEnderecosCarregamentoUi::contemTexto($srcItemPhp, "'exibirBadgeEnderecosWms'   => true",
    'item.php liga exibirBadgeEnderecosWms no configTipo do Carregamento');

echo "\n── execucao_bipagem.php ───────────────────────────────────────\n";
AssertEnderecosCarregamentoUi::contemTexto($srcBipagem, "\$exibirBadgeEnderecosWms",
    'execucao_bipagem.php lê o flag exibirBadgeEnderecosWms');
AssertEnderecosCarregamentoUi::contemTexto($srcBipagem, 'id="btn-enderecos-wms"',
    'Renderiza o botão btn-enderecos-wms');
AssertEnderecosCarregamentoUi::contemTexto($srcBipagem, "enderecosProdutoWms?produto_id=",
    'Botão aponta pra rota enderecosProdutoWms via abrirModal()');
AssertEnderecosCarregamentoUi::contemTexto($srcBipagem, 'style="display:none;"',
    'Botão nasce oculto (JS decide mostrar por item)');

// O botão deve estar condicionado ao flag (dentro do if), não incondicional.
$posFlag = strpos($srcBipagem, '$exibirBadgeEnderecosWms');
$posBtn  = strpos($srcBipagem, 'id="btn-enderecos-wms"', $posFlag !== false ? $posFlag : 0);
AssertEnderecosCarregamentoUi::verdadeiro(
    $posFlag !== false && $posBtn !== false,
    'Botão aparece depois da leitura do flag (condicional, não incondicional)'
);

echo "\n── carregamento/item.js — selecionarItem ──────────────────────\n";
AssertEnderecosCarregamentoUi::contemTexto($srcJs, "btn-enderecos-wms",
    'item.js referencia #btn-enderecos-wms');
AssertEnderecosCarregamentoUi::contemTexto($srcJs, "tem_enderecos_wms",
    'item.js lê item.tem_enderecos_wms (vem do CarregamentoPresenter)');

$posSelecionar = strpos($srcJs, "selecionarItem: function (id, salvar = true, metodo = 'click', isVisualizacao = false)");
AssertEnderecosCarregamentoUi::verdadeiro($posSelecionar !== false, 'item.js contém selecionarItem()');
if ($posSelecionar !== false) {
    $posBtnEnd = strpos($srcJs, 'btn-enderecos-wms', $posSelecionar);
    AssertEnderecosCarregamentoUi::verdadeiro(
        $posBtnEnd !== false,
        'A lógica de #btn-enderecos-wms está dentro de selecionarItem()'
    );
}

AssertEnderecosCarregamentoUi::summary();
```

- [ ] **Step 2: Rodar o teste e confirmar que falha**

Run: `php componentes/Missoes/Logistica/sinais/App/Testes/Html/EnderecosWmsCarregamentoUiTddTest.php`
Expected: falhas em praticamente todas as verificações (nada disso existe ainda)

- [ ] **Step 3: Ligar o flag em `carregamento/item.php`**

Em `componentes/Missoes/Logistica/sinais/App/html/carregamento/item.php`, o array `$configTipo` atual termina assim (linhas 22-27):

```php
    'exibirBadgeGondola'   => true,
    'exibirFiltroFab'      => true,
    'exibirBadgeSequencia' => true,
    'exibirInfoSeparacao'  => true,
    'exibirOcorrenciaBadge' => true,
];
```

Substituir por:

```php
    'exibirBadgeGondola'   => true,
    'exibirFiltroFab'      => true,
    'exibirBadgeSequencia' => true,
    'exibirInfoSeparacao'  => true,
    'exibirOcorrenciaBadge' => true,
    'exibirBadgeEnderecosWms'   => true,
];
```

- [ ] **Step 4: Declarar o default do flag em `execucao_bipagem.php`**

No topo do arquivo `componentes/Missoes/Logistica/sinais/App/html/shared/components/v2/execucao_bipagem.php`, a linha 42 atual é:

```php
$exibirOcorrenciaBadge = $configTipo['exibirOcorrenciaBadge'] ?? false;
```

Substituir por:

```php
$exibirOcorrenciaBadge = $configTipo['exibirOcorrenciaBadge'] ?? false;
$exibirBadgeEnderecosWms = $configTipo['exibirBadgeEnderecosWms'] ?? false;
```

- [ ] **Step 5: Renderizar o botão em `execucao_bipagem.php`**

No mesmo arquivo, o trecho atual (linhas 708-716) é exatamente:

```php
                    if ($algumItemSemInfo):
                ?>
                    <button type="button" id="btn-status-separacao"
                        <?= $galaxia->abrirModal('statusSeparacaoCarregamento?missao_id=' . $missaoIdAtual.'&item_id=', ['startSinal' => 'App'])['html']; ?>
                        title="Ver status da separação">
                        <i class="bi bi-people-fill"></i> ACOMPANHAR
                    </button>
                <?php endif; } ?>
                                <?php if ($exibirBadgeSequencia): ?>
```

Substituir por (adiciona o novo botão logo depois do bloco do `btn-status-separacao`, antes do `exibirBadgeSequencia`):

```php
                    if ($algumItemSemInfo):
                ?>
                    <button type="button" id="btn-status-separacao"
                        <?= $galaxia->abrirModal('statusSeparacaoCarregamento?missao_id=' . $missaoIdAtual.'&item_id=', ['startSinal' => 'App'])['html']; ?>
                        title="Ver status da separação">
                        <i class="bi bi-people-fill"></i> ACOMPANHAR
                    </button>
                <?php endif; } ?>
                                <?php if ($exibirBadgeEnderecosWms): ?>
                                    <!-- [NOVO] Botão de Endereços WMS — nasce oculto; JS (CarregamentoV2.selecionarItem)
                                         mostra/esconde e reescreve produto_id a cada troca de item, mesmo mecanismo
                                         do #btn-status-separacao acima. Reaproveita a rota/modal já existentes
                                         (App::enderecosProdutoWms), sem nenhuma mudança de backend. -->
                                    <button type="button" id="btn-enderecos-wms" style="display:none;"
                                        <?= $galaxia->abrirModal('enderecosProdutoWms?produto_id=', ['startSinal' => 'App'])['html']; ?>
                                        title="Ver endereços deste produto">
                                        <i class="bi bi-geo-alt-fill"></i> ENDEREÇOS
                                    </button>
                                <?php endif; ?>
                                <?php if ($exibirBadgeSequencia): ?>
```

- [ ] **Step 6: Mostrar/esconder + reescrever `produto_id` em `item.js`**

Em `componentes/Missoes/Logistica/sinais/App/html/carregamento/item.js`, dentro de `selecionarItem`, o trecho atual é exatamente:

```js
        const itemAnterior = this.itemAtualId;
        this.itemAtualId = id;
        this.contagemManualAtiva = true;
        // Atualizar item_id no botão de status da separação
        const $btnSep = $('#btn-status-separacao');
        if ($btnSep.length && id && id !== 'FINALIZAR') {
            const s = $btnSep.attr('data-galaxia-sinal') || '';
            const novoSinal = s.replace(/&item_id=[^&]*/g, '') + '&item_id=' + this.itensData[id].produto_id;
            $btnSep.attr('data-galaxia-sinal', novoSinal);
            $btnSep.data('galaxia-sinal', novoSinal);
        }

        if (id !== 'FINALIZAR' && this.itensData[id]) {
```

Substituir por (adiciona o bloco do novo botão logo depois do bloco do `$btnSep`):

```js
        const itemAnterior = this.itemAtualId;
        this.itemAtualId = id;
        this.contagemManualAtiva = true;
        // Atualizar item_id no botão de status da separação
        const $btnSep = $('#btn-status-separacao');
        if ($btnSep.length && id && id !== 'FINALIZAR') {
            const s = $btnSep.attr('data-galaxia-sinal') || '';
            const novoSinal = s.replace(/&item_id=[^&]*/g, '') + '&item_id=' + this.itensData[id].produto_id;
            $btnSep.attr('data-galaxia-sinal', novoSinal);
            $btnSep.data('galaxia-sinal', novoSinal);
        }

        // [NOVO] Mostrar/esconder o botão de Endereços WMS + reescrever produto_id
        const $btnEnderecos = $('#btn-enderecos-wms');
        if ($btnEnderecos.length && id && id !== 'FINALIZAR') {
            const itemAtualEnd = this.itensData[id];
            if (itemAtualEnd && itemAtualEnd.tem_enderecos_wms) {
                const sEnd = $btnEnderecos.attr('data-galaxia-sinal') || '';
                const novoSinalEnd = sEnd.replace(/produto_id=[^&]*/, 'produto_id=' + itemAtualEnd.produto_id);
                $btnEnderecos.attr('data-galaxia-sinal', novoSinalEnd);
                $btnEnderecos.data('galaxia-sinal', novoSinalEnd);
                $btnEnderecos.show();
            } else {
                $btnEnderecos.hide();
            }
        }

        if (id !== 'FINALIZAR' && this.itensData[id]) {
```

- [ ] **Step 7: Rodar o teste e confirmar que passa**

Run: `php componentes/Missoes/Logistica/sinais/App/Testes/Html/EnderecosWmsCarregamentoUiTddTest.php`
Expected: `Tests passed` (Passou: 9 | Falhou: 0)

- [ ] **Step 8: Lint de sintaxe PHP**

Run: `php -l componentes/Missoes/Logistica/sinais/App/html/carregamento/item.php && php -l componentes/Missoes/Logistica/sinais/App/html/shared/components/v2/execucao_bipagem.php`
Expected: `No syntax errors detected` para os dois arquivos (`item.js` não é PHP, não precisa de `php -l`)

- [ ] **Step 9: Commit**

Este projeto não commita automaticamente nesta sessão — pule este passo (deixe as mudanças na working tree). Se estiver executando este plano em outra sessão/contexto sem essa restrição, commit normalmente:
```bash
git add componentes/Missoes/Logistica/sinais/App/html/carregamento/item.php componentes/Missoes/Logistica/sinais/App/html/shared/components/v2/execucao_bipagem.php componentes/Missoes/Logistica/sinais/App/html/carregamento/item.js componentes/Missoes/Logistica/sinais/App/Testes/Html/EnderecosWmsCarregamentoUiTddTest.php
git commit -m "feat(carregamento): botão de Endereços WMS (view + JS)"
```

---

### Task 3: Verificação manual

**Files:** nenhum.

- [ ] **Step 1: Rodar os dois testes automatizados juntos**

Run:
```bash
php componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Presenters/CarregamentoPresenterEnderecosWmsTddTest.php
php componentes/Missoes/Logistica/sinais/App/Testes/Html/EnderecosWmsCarregamentoUiTddTest.php
```
Expected: ambos `Tests passed`

- [ ] **Step 2: Verificação manual no Carregamento**

Abrir uma missão de Carregamento com ao menos um produto que tenha endereço WMS confirmado (ex.: o mesmo produto usado na verificação da Separação). Verificar:
1. Ao selecionar um item com endereço, o botão "Endereços" aparece.
2. Clicar abre o mesmo modal já usado na Separação, com a lista correta pro produto atual.
3. Trocar para um item sem endereço esconde o botão.
4. Trocar de volta pro item com endereço reaparece o botão, apontando pro produto certo (não o anterior).
5. Testar a tela de Retorno (mesmo `execucao_bipagem.php`) e confirmar que o botão NÃO aparece lá.

- [ ] **Step 3: Reportar ao usuário**

Confirmar ao usuário que a verificação manual foi feita (ou que ficou pendente) antes de considerar a entrega concluída.
