## 3 - POSSÍVEL BUG

1. O gatilho incorreto está na pós-finalização da missão `RETORNO_ENTREGAS`, não no momento de criação da missão.
2. O método `transicionar()` em `genesis/componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesStatus.php` chama incondicionalmente `processarPosFinalizacaoRetorno($missao)` sempre que a missão é do tipo `RETORNO_ENTREGAS`.
3. Dentro de `processarPosFinalizacaoRetorno()`, em `genesis/componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/Traits/PosFinalizacaoRetornoTrait.php`, o sistema percorre todos os pedidos distintos da missão e executa `retornarPedidoAoMapa()` para cada um.
4. A rotina `retornarPedidoAoMapa()` não valida se:
   1. o pedido já retornou ao mapa anteriormente por esse mesmo evento de falha;
   2. o pedido já foi re-roteirizado depois da falha;
   3. a missão está sendo finalizada por baixa forçada;
   4. a missão já executou anteriormente a automação de retorno ao mapa.
5. A única proteção atual é verificar se existe movimentação “para hoje”. Essa proteção é insuficiente, porque:
   1. não garante idempotência por missão/evento;
   2. não impede nova disponibilização quando a movimentação anterior já foi consumida/alterada pela operação;
   3. não considera o estado logístico atual do pedido.
6. Em termos técnicos, há uma quebra de regra de negócio por ausência de guarda de idempotência e ausência de validação de contexto operacional no pós-processamento da missão de retorno.

## 4 - ANÁLISE DE CÓDIGO E PLANO DE CORREÇÃO

### 4.1 Arquivos e pontos exatos do fluxo

1. `genesis/componentes/Missoes/Logistica/sinais/Ocorrencias/Laboratorios/ProcessarOcorrenciasRetornoLaboratorio.php`
   - Responsável por processar ocorrências e criar a missão de retorno.
   - O fluxo de criação está concentrado em:
     - `processarGrupoOcorrencias()`
     - chamada de `$this->servicoMissao->criar(...)`
   - Esse arquivo mostra que a missão nasce a partir da ocorrência de falha do romaneio/pedido, o que é compatível com a narrativa operacional.

2. `genesis/componentes/Missoes/Logistica/sinais/Ocorrencias/Servicos/RetornoMissaoServico.php`
   - Responsável por persistir a missão `RETORNO_ENTREGAS`.
   - Apenas cria a missão e grava configurações como `origem`, `numeros_pedidos` e `ocorrencias`.
   - Não é o ponto da duplicidade.

3. `genesis/componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesStatus.php`
   - Método `transicionar()`.
   - Aqui está o gatilho global da pós-finalização:

```php
if ((int)$missao->tipo_missao_id === TipoMissao::RETORNO_ENTREGAS->id()) {
    $this->processarPosFinalizacaoRetorno($missao);
}
```

   - Esse disparo ocorre para qualquer finalização/transição elegível da missão de retorno.

4. `genesis/componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/Traits/PosFinalizacaoRetornoTrait.php`
   - Método `processarPosFinalizacaoRetorno($missao)`.
   - Este é o ponto central do bug.
   - O trecho que redisponibiliza pedidos é:

```php
foreach ($pedidosDistintos as $pedidoId => $numeroPedido) {
    $this->retornarPedidoAoMapa((int) $pedidoId, $numeroPedido);
}
```

   - A rotina executa sempre que a missão é finalizada e não há guarda para impedir segunda execução.

5. Ainda em `PosFinalizacaoRetornoTrait.php`
   - Método `retornarPedidoAoMapa(int $pedidoId, ?string $numeroPedido)`.
   - Ele apenas evita duplicar se já existir movimentação no dia atual:

```php
if (!is_error($existeHoje) && !empty($existeHoje['movimentacaoData'])) {
    return;
}
```

   - Essa regra é fraca para o caso relatado e não representa a regra de negócio real.

### 4.2 Diagnóstico técnico direto

1. A lógica de “retorno ao mapa” foi acoplada à finalização da missão de retorno.
2. Pelo processo narrado, esse retorno ao mapa já ocorre no momento anterior, quando a entrega falha e os pedidos são baixados como não entregues.
3. Portanto, a finalização da missão de retorno está repetindo um efeito colateral de um estágio anterior do processo.
4. O bug é estrutural: a automação certa está no lugar errado, ou ao menos sem condicionais suficientes para saber quando deve rodar.

### 4.3 Plano de correção recomendado

1. Remover o retorno automático ao mapa da finalização genérica da missão de retorno.
2. Manter na pós-finalização apenas:
   1. criação de lotes de débito logístico;
   2. criação de lote/stage de recebimento;
   3. webhook de itens devolvidos;
   4. atualizações internas da missão.
3. Se houver necessidade de manter esse comportamento em alguns cenários, transformar o retorno ao mapa em comportamento condicionado por flag explícita da missão, por exemplo:
   1. `configuracoes['permitir_retorno_mapa_pos_finalizacao'] === true`
   2. e gravar essa flag apenas em cenários realmente compatíveis.
4. Adicionar idempotência forte no pós-processamento:
   1. registrar em banco/configuração que a missão já executou o pós-processamento;
   2. impedir nova execução se essa marca já existir.
5. Adicionar validação de estado do pedido antes de recriar movimentação:
   1. se já houve nova roteirização após a ocorrência, não retornar ao mapa;
   2. se já existe evento de retorno ao mapa associado à ocorrência/missão, não repetir;
   3. se a missão foi concluída por baixa forçada, não disparar retorno ao mapa automaticamente.
6. Em termos de regra de negócio, o mais seguro é:
   1. retorno ao mapa no evento de falha/não entrega;
   2. finalização da missão de retorno sem qualquer redisponibilização.

## 5 - REFATORAÇÃO OBJETIVA SUGERIDA

### 5.1 Ajuste mínimo e mais seguro

1. Remover da pós-finalização a parte que reenvia pedidos ao mapa.

### 5.2 Trecho sugerido para `PosFinalizacaoRetornoTrait.php`

```php
private function processarPosFinalizacaoRetorno($missao): void
{
    $verificacaoModelo = new MissaoLogistica();
    $missaoAtualizada = $verificacaoModelo->find("id = :id", "id={$missao->id}")->fetch();

    $statusesFinalizados = [StatusMissao::CONCLUIDA->value, StatusMissao::AGUARDANDO_APROVACAO->value];
    if (!$missaoAtualizada || !in_array($missaoAtualizada->status, $statusesFinalizados)) {
        return;
    }

    $configuracoes = !empty($missaoAtualizada->configuracoes)
        ? (is_string($missaoAtualizada->configuracoes) ? json_decode($missaoAtualizada->configuracoes, true) : (array) $missaoAtualizada->configuracoes)
        : [];

    if (($configuracoes['origem'] ?? '') === 'lote' || strtolower((string)($missaoAtualizada->referencia_tipo ?? '')) === 'lote') {
        return;
    }

    $itemModelo = new MissaoLogisticaItem();
    $itensDaMissao = $itemModelo->find(
        "missao_logistica_id = :mid AND status NOT IN ('removido', 'cancelado')",
        "mid={$missao->id}"
    )->fetch(true);

    if (empty($itensDaMissao)) {
        return;
    }

    $itensNaoEncontrados = [];
    $itensFaltaProduto = [];
    $itensConfirmados = [];
    $itensDevolvidosWebhook = [];

    foreach ($itensDaMissao as $item) {
        $dadosAdicionais = !empty($item->dados_adicionais)
            ? (is_string($item->dados_adicionais) ? json_decode($item->dados_adicionais, true) : (array) $item->dados_adicionais)
            : [];

        $dadosColetados = !empty($item->dados_coletados)
            ? (is_string($item->dados_coletados) ? json_decode($item->dados_coletados, true) : (array) $item->dados_coletados)
            : [];

        $motivoRetorno = strtolower(trim($dadosAdicionais['motivo_retorno'] ?? ''));
        $tipoProblema = strtolower(trim($dadosAdicionais['tipo_problema'] ?? ''));
        $observacaoItem = strtolower(trim($item->observacoes ?? ''));
        $ocorrenciaTipoApp = strtolower(trim($dadosColetados['ocorrencia_tipo'] ?? ''));

        $termosBusca = [$motivoRetorno, $tipoProblema, $observacaoItem, $ocorrenciaTipoApp];

        if ($this->contemOcorrencia($termosBusca, self::OCORRENCIAS_NAO_ENCONTRADO) || $ocorrenciaTipoApp === 'nao_encontrado') {
            $itensNaoEncontrados[] = $item;
        } elseif ($this->contemOcorrencia($termosBusca, self::OCORRENCIAS_FALTA_PRODUTO) || str_contains($ocorrenciaTipoApp, 'falta')) {
            $itensFaltaProduto[] = $item;
        } else {
            $itensConfirmados[] = $item;
        }

        if ($this->contemOcorrencia($termosBusca, self::OCORRENCIAS_DEVOLUCAO) || str_contains($ocorrenciaTipoApp, 'devolvi')) {
            $codigoProduto = $dadosAdicionais['codigo_produto'] ?? $dadosColetados['codigo_produto'] ?? $item->codigo_produto ?? null;
            $itensDevolvidosWebhook[] = [
                'pedido_id'      => $item->pedido_id ?? ($dadosAdicionais['pedido_id'] ?? null),
                'numero_pedido'  => $dadosAdicionais['numero_pedido'] ?? $dadosColetados['numero_pedido'] ?? null,
                'produto_id'     => $item->produto_id,
                'codigo_produto' => $codigoProduto,
                'motivo_retorno' => $motivoRetorno ?: 'Produto Devolvido'
            ];
        }
    }

    $itensProblema = array_merge($itensNaoEncontrados, $itensFaltaProduto);

    if (!empty($itensProblema)) {
        $this->criarLotePendencia(
            $missao,
            $itensProblema,
            LoteCategoria::DEBITOS_LOGISTICA,
            'Débitos Logísticos (Não encontrados / Falta de produto) - Retorno Missão #' . $missao->id
        );
    }

    if (!empty($itensConfirmados)) {
        $this->criarLotePendencia(
            $missao,
            $itensConfirmados,
            LoteCategoria::RETORNO_RECEBIMENTO,
            'Itens para recebimento (Stage) - Retorno Missão #' . $missao->id
        );
    }

    if (!empty($itensDevolvidosWebhook)) {
        $this->enviarWebhookItensDevolvidos($itensDevolvidosWebhook, $missao->id);
    }
}
```

## 6 - ALTERNATIVA CASO O RETORNO AO MAPA PRECISE EXISTIR EM ALGUNS CENÁRIOS

1. Encapsular a chamada em flag de configuração:

```php
$deveRetornarAoMapa = (bool)($configuracoes['retornar_ao_mapa_na_finalizacao'] ?? false);

if ($deveRetornarAoMapa) {
    foreach ($pedidosDistintos as $pedidoId => $numeroPedido) {
        $this->retornarPedidoAoMapa((int) $pedidoId, $numeroPedido);
    }
}
```

2. Só preencher essa flag na criação da missão em cenários explicitamente aprovados pela regra de negócio.
3. Mesmo assim, ainda é recomendável registrar execução única:

```php
if (!empty($configuracoes['pos_finalizacao_processada_em'])) {
    return;
}
```

4. Depois de processar:

```php
$configuracoes['pos_finalizacao_processada_em'] = date('Y-m-d H:i:s');
```

5. Essa alternativa só deve ser usada se houver cenário real e validado em que a finalização da missão de retorno precise devolver pedidos ao mapa. Caso contrário, a remoção total do disparo continua sendo a opção mais segura e aderente ao processo operacional.
