# Ícone "Endereços" no Card de Separação

**Data:** 2026-07-23
**Escopo:** Adicionar um ícone/badge "Endereços" no card de item da missão de Separação que, ao ser clicado, exibe em um modal todos os endereços WMS onde aquele produto está registrado e a quantidade em cada um — para o operador se guiar durante a separação. Construído como componente plugável (PHP `include` + função com array de config) para futura reutilização em outras telas de missão (Carregamento, Pesagem, etc.), mas a integração nessa primeira entrega é **só em Separação**.

---

## Contexto

O sinal `AlocacaoWms` (`componentes/Missoes/Logistica/sinais/AlocacaoWms/`) já registra, a cada bipagem de uma missão do tipo `ALOCACAO_WMS`, uma linha em `wms_movimentos` (produto + endereço + quantidade + status). Esse dado hoje só é consumido de duas formas:

1. `ResolverEnderecoProdutoLaboratorio` (`AlocacaoWms/Laboratorios/`) — devolve **o último endereço confirmado** de um produto, usado por `GestaoMissoesSeparacao::enriquecerLocalizacaoWms()` (`Designacoes/Laboratorios/GestaoMissoesSeparacao.php:100-117`) só para gravar um snapshot `dados_adicionais.endereco_wms` no item, na **criação** da missão de separação.
2. O badge `separacaoEnderecoWmsBadge` (`App/html/separacao/endereco_wms.php` + `.js`, `EnderecoWmsGate`) — exibe esse único endereço esperado como *gate* de bipagem (não é lista, é auditoria de 1 endereço).

Não existe hoje nenhum caminho que devolva **todos** os endereços de um produto com quantidade em cada — é isso que esta feature adiciona.

O card de item da Separação é montado por `execucao_card()` em `App/html/shared/components/execucao/execucao_card.php:29`, reusado por Separação/Pesagem/Inventário/AlocacaoWms/Retorno (mas só Separação recebe a integração nesta entrega). A área de badges já plugáveis é `div.carga-produto-info-extra` (`execucao_card.php:241-291`), onde já convivem o badge de endereço-esperado e o de ruptura, cada um incluído condicionalmente via `require_once` + chamada de função.

O framework já tem um mecanismo de modal genérico via AJAX (`GalaxiaRoute::abrirModal()` / `galaxiaButtonModal()`, `controladores/_Ajudadores/functions.php:927`), usado hoje pelo botão "ACOMPANHAR" do Carregamento (`App.php:198-199` → `App::statusSeparacaoCarregamento()` → view com `showModalHeader()`). Este é o mecanismo que o popover de Endereços vai reaproveitar — sem JS de fetch novo.

---

## Decisões

- **Fonte do saldo:** soma local de `wms_movimentos` com `status='confirmada'` por endereço — mesma convenção já usada em `saldoPorLocalizacaoProduto()`, com o mesmo aviso implícito de que é o saldo **registrado no Genesis**, não necessariamente o saldo oficial do WMS externo (não há API de leitura do WMS neste repo).
- **Estado vazio:** se o produto não tem nenhum endereço com saldo > 0, o ícone **não é renderizado** (sem clique em vazio).
- **Acesso aos dados:** Laboratório-fachada interno em `AlocacaoWms`, consumido diretamente pelo sinal `App` (mesma convenção informal já usada por `Designacoes`→`AlocacaoWms` hoje). Sem endpoint REST novo em `/api/v1` — não há consumidor externo, e o fluxo já é 100% interno/sessão.

---

## Componentes novos

### 1. Sonda — `WmsMovimentoSonda` (`AlocacaoWms/Sondas/WmsMovimentoSonda.php`)

Novo método `mapaSaldoPorProduto(array $produtoIds): array`, análogo a `saldoPorLocalizacaoProduto()` (linhas 239-252) mas invertendo o eixo de agregação:

```php
public function mapaSaldoPorProduto(array $produtoIds): array
{
    // find('produto_id IN (...) AND status = confirmada'), agrupado por produto_id,
    // cada entrada: [ ['localizacao_codigo' => ..., 'quantidade' => float], ... ]
}
```

### 2. Laboratório — `EnderecosProdutoWmsLaboratorio` (novo, `AlocacaoWms/Laboratorios/`)

Espelha `ResolverEnderecoProdutoLaboratorio`. Recebe 1+ `produto_id`, chama a Sonda acima, devolve lista pronta para a view (endereço + quantidade, ordenado por quantidade desc). Fica no sinal `AlocacaoWms` para manter o conhecimento de WMS encapsulado — o sinal `App` só consome.

### 3. Rota de modal — `App.php`

Novo bloco de dispatch em `App::start()`, seguindo o padrão de `statusSeparacaoCarregamento` (`App.php:198-199`, `328-416`) e `sugerirCodigoBarrasModal` (`App.php:201-202`, `428`):

```php
if (strpos($sinal, 'enderecosProdutoWms') !== false) {
    return $this->enderecosProdutoWms($data);
}
```

`App::enderecosProdutoWms($data)` lê `produto_id` da query, chama `EnderecosProdutoWmsLaboratorio`, devolve `$galaxia->visual('html/shared/wms/modal_enderecos_produto', [...])`.

### 4. View do modal — `App/html/shared/wms/modal_enderecos_produto.php` (novo)

Usa `showModalHeader('Endereços', $produtoNome)` (mesmo helper do modal de status de separação) + lista simples `endereço — quantidade`.

### 5. Componente plugável (badge) — `App/html/shared/components/wms/enderecos_produto_wms.php` (novo)

Função `enderecosProdutoWmsBadge(array $config)` — recebe `produto_id` (e opcionalmente `missao_id` para contexto futuro). Internamente:
- Se não há saldo > 0 para o produto → não imprime nada.
- Se há → imprime o ícone/badge "Endereços" com atributos `data-galaxia-sinal`/`data-target=".modal-config"` (via `galaxiaButtonModal()`), apontando para a rota `enderecosProdutoWms?produto_id=...`.

Não precisa de JS próprio: o clique é interceptado pelo handler global de modal do tema (já usado pelas outras chamadas de `abrirModal()` nessas telas). Isso é o que torna o componente "plugável" — qualquer card futuro só precisa `require_once` + chamar a função passando `produto_id`.

### 6. Integração no card de Separação

Em `execucao_card.php`, dentro de `div.carga-produto-info-extra` (linhas ~272-291, ao lado do badge de endereço-esperado e do badge de ruptura), adiciona:

```php
if (!function_exists('enderecosProdutoWmsBadge')) {
    require_once __DIR__ . '/../wms/enderecos_produto_wms.php';
}
enderecosProdutoWmsBadge(['produto_id' => $item->produto_id]);
```

Condicional ao mesmo `$tipo === 'separacao'` usado pelo badge de endereço-esperado, para não afetar Pesagem/Inventário/Retorno/AlocacaoWms nesta entrega.

---

## Arquivos Tocados

| Arquivo | Mudança |
|---|---|
| `AlocacaoWms/Sondas/WmsMovimentoSonda.php` | Novo método `mapaSaldoPorProduto()` |
| `AlocacaoWms/Laboratorios/EnderecosProdutoWmsLaboratorio.php` | Novo — fachada produto→endereços+quantidade |
| `App/App.php` | Novo bloco de dispatch + método `enderecosProdutoWms()` |
| `App/html/shared/wms/modal_enderecos_produto.php` | Novo — view do modal |
| `App/html/shared/components/wms/enderecos_produto_wms.php` | Novo — componente plugável (badge) |
| `App/html/shared/components/execucao/execucao_card.php` | Integração do badge, condicional a `$tipo === 'separacao'` |
| `AlocacaoWms/Testes/Laboratorios/EnderecosProdutoWmsLaboratorioTddTest.php` | Novo — TDD do Laboratório/Sonda |

**Sem migrations novas** (reaproveita `wms_movimentos` existente). **Sem endpoint `/api/v1` novo.**

---

## Critérios de Aceitação

1. No card de item da Separação, produtos com ao menos um endereço registrado (saldo local > 0) exibem o ícone "Endereços".
2. Produtos sem nenhum endereço registrado não exibem o ícone.
3. Ao clicar, abre um modal (mecanismo `abrirModal()` já existente) listando cada endereço e a quantidade correspondente, ordenados por quantidade decrescente.
4. A quantidade exibida reflete apenas movimentos `status='confirmada'` — movimentos cancelados não entram na soma.
5. Pesagem, Inventário, Retorno e AlocacaoWms não são afetados (badge condicional a `$tipo === 'separacao'`).
6. O componente (`enderecosProdutoWmsBadge`) pode ser incluído em outro card (ex.: Carregamento) futuramente só com `require_once` + chamada passando `produto_id`, sem duplicar lógica de busca ou view do modal.
