# Reset de Item da Missão (gestor) — Plano técnico

## Resumo de decisão arquitetural

Feature é **bug-fix/extensão cirúrgica em código legado V1** (Inteligencias/Satelites do `MissaoLogistica`). Não cria Sinal novo, não cria Sonda nova, não cria Adapter, não cria Registry. Toda a alteração concentra-se em um único método (`voltarItemPendente`) do Satélite `MissoesItensEdicao`, mais um enum case novo em `AcaoExtrato`, mais ajuste de copy no `data-confirm-msg`. A spec é explícita ("mantém-se no `MissoesItensEdicao` atual") e não há sinal de extensibilidade futura — qualquer padrão V2 aqui seria prematuro.

## Sinal de referência

- **Imitar (mesmo arquivo / mesmo padrão V1)**: `componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesItensEdicao.php` — handlers irmãos `recomecarItem`, `pularItem`, `remover`. Já usam `MissaoLogisticaItem::atualizar()`, `RegistraExtratoTrait`, `BuscaMissaoTrait` e `Message`. Estender `voltarItemPendente` mantendo essa convenção.
- **Imitar (transição de missão)**: `componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesStatus.php::transicionar()` (linhas 371-420) — é como o resto do sistema muda status de missão e gera efeitos colaterais. Usaremos o `MissaoLogistica::atualizar()` direto (idêntico ao que `transicionar` faz internamente para o caso simples), porque o reset não dispara hooks de pós-finalização.
- **Não imitar**: nada do diretório `componentes/Missoes/Logistica/sinais/Designacoes/Strategies/` (Strategies V2 que não se aplicam aqui), nem pattern dos Sinais OTP/Edicaorota (V2 modernos) — esta feature é manutenção em código legado V1 e introduzir Sondas/Laboratórios novos para um único método seria contradizer a spec.

## Estrutura proposta

### Novos arquivos

Nenhum. A spec confirma "Schema: nenhuma migration nova".

### Arquivos alterados

1. **`componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesItensEdicao.php`**
   - Reescrever `voltarItemPendente()` (linhas 160-217) para zerar campos de execução E rebotar status da missão quando aplicável.
   - Manter assinatura, manter contrato de retorno (`Message::render()`).

2. **`componentes/Constelacoes/Logistica/MissaoLogistica/Enums/AcaoExtrato.php`**
   - Adicionar `case MISSAO_REABERTA = 'MISSAO_REABERTA';` (decisão TL — nome curto, alinhado ao padrão `MISSAO_*` existente).
   - Atualizar os três `match()` (descricao, icon, categoria) com a nova entrada:
     - descricao: `'Missão reaberta (item resetado)'`
     - icon: `'bi-arrow-counterclockwise'`
     - categoria: `'missao'`

3. **`componentes/Missoes/Logistica/sinais/Designacoes/html/designacao_painel.php` (linha 1247)**
   - Atualizar `data-confirm-msg` para mencionar o rebote da missão. Texto sugerido (curto, mesma linha de tom do confirm atual):
     > `"Reiniciar este item para PENDENTE? Os dados de execução serão apagados. Se a missão estiver Aguardando Aprovação, ela voltará para Em Andamento."`

## Sondas e schema

**N/A** — sem migration. Auditoria do schema (`tables.sql:79-134`) confirma os campos de execução já existentes em `missao_logistica_itens`:

| Campo | Tipo | A zerar? | Observação |
|---|---|---|---|
| `status` | VARCHAR(30) | sim → `'pendente'` | já tratado hoje |
| `quantidade_bipada` | DECIMAL(15,4) | sim → `0` | AC1.1 |
| `dados_coletados` | JSONB | **sim → `NULL`** | spec menciona `dados_adicionais` mas o campo correto que carrega execução é `dados_coletados` (ver `query_comparativo_analitico.sql:17,22-26`). `dados_adicionais` carrega metadados de origem (cubagem, etc.) e **não deve** ser limpo. |
| `concluido_em` | TIMESTAMP | sim → `NULL` | AC1.1 |
| `concluido_por` | INTEGER | sim → `NULL` | AC1.1 |
| `iniciado_em` | TIMESTAMP | sim → `NULL` | simétrico (item pendente não pode ter iniciado_em) |
| `iniciado_por` | INTEGER | sim → `NULL` | idem |
| `tempo_execucao` | VARCHAR(20) | sim → `NULL` | AC1.1 (campo de execução listado no schema) |
| `solicitou_recomeco` | SMALLINT | sim → `0` | volta ao default; item resetado não tem solicitação pendente |
| `motivo_remocao` | TEXT | **não** | só usado em remoção, não em execução |
| `observacoes` | TEXT | **append** (não zerar) | AC1.5 — preserva histórico |
| `pulado_vezes` | INTEGER | **não** | métrica acumulada da vida do item |
| `dados_adicionais` | JSONB | **não** | metadados de domínio (cubagem, gôndola), não execução |

**Resolução de Dúvida da spec (linha 98)**: a spec confunde `dados_adicionais` com `dados_coletados`. **TL define**: zerar `dados_coletados`, **não** mexer em `dados_adicionais`. Não existe coluna `ocorrencia_separacao` — ocorrências vivem dentro de `dados_coletados` JSONB (chaves `ocorrencia_tipo`, `ocorrencia_motivo`), portanto serão limpas implicitamente ao zerar `dados_coletados`. AC1.3 atendido.

## Endpoints / fluxo

| Camada | Path | Ação |
|---|---|---|
| Front (botão) | `Designacoes/html/designacao_painel.php:1245-1259` | já dispara `MissoesItensEdicao::voltarItemPendente` via `galaxia-action` — sem alteração de roteamento |
| Handler | `MissoesItensEdicao::voltarItemPendente($data)` | recebe `missao_id`, `item_id` |
| Auth | `isGestorLogistico` (UI) — backend não altera | mantido |

Não há rota REST em `/api/v1/`. O fluxo é Galáxia-action (legado), idêntico ao atual.

## Lógica do método reescrito (pseudocódigo)

```
voltarItemPendente(data):
    valida missao_id, item_id           -> Message::error se faltar
    missao = buscarMissao(missao_id)    -> error se não existe
    statusMissao = StatusMissao::tryFrom(missao.status)

    se statusMissao NÃO ∈ {EM_ANDAMENTO, AGUARDANDO_APROVACAO, PAUSADA, PENDENTE, RASCUNHO}:
        return error (regra 2 da spec; status finalizados bloqueados — AC1.6)

    item = MissaoLogisticaItem.find(id=item_id)   -> error se não existe
    se item.status == 'pendente': return info ('Item já está pendente')   -- regra 3

    [transação lógica — ver "Atomicidade" abaixo]

    1) MissaoLogisticaItem.atualizar({
         status: 'pendente',
         quantidade_bipada: 0,
         dados_coletados: null,
         concluido_em: null,
         concluido_por: null,
         iniciado_em: null,
         iniciado_por: null,
         tempo_execucao: null,
         solicitou_recomeco: 0,
         observacoes: <obs original> + ' [Resetado p/ pendente por <nome>]'
       }, "id = :id", "id={item.id}")
       -> se falhou: abortar com error

    2) registrarExtrato(missao_id, ITEM_REINICIADO, item.id, {de, para, observacao})

    3) se statusMissao == AGUARDANDO_APROVACAO:
         MissaoLogistica.atualizar({status: 'em_andamento'}, "id=:id", "id=missao_id")
         -> se falhou: tentar reverter passo 1 (best-effort) e retornar error
         registrarExtrato(missao_id, MISSAO_REABERTA, item.id, {
             de: 'aguardando_aprovacao',
             para: 'em_andamento',
             motivo: 'Reset de item via painel de gestão',
             item_id: item.id
         })
         msgSuccess = 'Item voltado para pendente. Missão reaberta para Em Andamento.'
       senão:
         msgSuccess = 'Item voltado para pendente com sucesso.'

    return Message::success(msgSuccess)->render()
```

## Padrões de extensibilidade

**N/A.** Spec descreve um único fluxo, sem variação por tipo de missão ("Funcionar para qualquer tipo de missão logística — comportamento consistente"). Adapter/Registry/Strategy não se aplicam.

## Atomicidade (resolve a Dúvida em aberto da spec, linha 97)

A spec pergunta se Genesis oferece transação explícita. **TL define**: a infra atual (`MissaoLogisticaItem::atualizar()` e `MissaoLogistica::atualizar()`) **não orquestra** transação cross-table — `MissoesPersistencia.php:157` mostra que o framework chegou a flertar com `beginTransaction` mas está comentado. Portanto:

- **Estratégia adotada**: ordem `item → missão → extratos` com **best-effort revert** em caso de falha do passo 2.
  - Passo 1 (update do item) executa primeiro. Se falhar, retorna erro; nada foi alterado.
  - Passo 2 (update da missão para `em_andamento`) só executa se necessário. Se falhar, registramos no log e tentamos restaurar item para o status anterior + remover registro de extrato `ITEM_REINICIADO` (se já gravado). Mensagem de erro explícita ao gestor.
  - Passo 3 (extrato `MISSAO_REABERTA`) é o último; falha aqui é logada mas **não reverte** a operação principal (extrato é auxiliar).

- **Risco residual**: numa janela de microssegundos pode haver item resetado sem missão rebotada. Mitigação: idempotência — chamar `voltarItemPendente` de novo com item já pendente cai no early-return ('Item já está pendente') sem tocar a missão. Spec aceita esse approach (regra 5 fala de "unidade lógica", não exige `BEGIN/COMMIT` SQL).

- **Alternativa rejeitada**: introduzir uma `Connect::beginTransaction()` ad-hoc neste único método quebraria a uniformidade do Satélite (nenhum outro método dele transaciona) e não tem precedente em `MissoesStatus::transicionar()`, que já faz updates compostos sem transação SQL.

## Ordem de implementação (sequencial)

1. Adicionar `case MISSAO_REABERTA` em `AcaoExtrato.php` + atualizar os 3 `match()`.
2. Reescrever `voltarItemPendente()` em `MissoesItensEdicao.php` conforme pseudocódigo.
3. Atualizar `data-confirm-msg` em `designacao_painel.php:1247`.
4. Rodar `php -l` em ambos os PHPs alterados.
5. Teste manual em `dev_gx_goldie`:
   - 5a. Reset de item concluído em missão `em_andamento` → item zerado, missão permanece `em_andamento`, sem registro `MISSAO_REABERTA`.
   - 5b. Reset de item concluído em missão `aguardando_aprovacao` → item zerado, missão volta para `em_andamento`, dois registros de extrato (ITEM_REINICIADO + MISSAO_REABERTA).
   - 5c. Tentar resetar item já pendente → retorna `info`, banco intocado.
   - 5d. Tentar resetar item em missão `concluida` → bloqueado (AC1.6).
6. (Opcional) Adicionar TDD em `componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/Traits/` no padrão dos `*TddTest.php` existentes — recomendado mas não bloqueante por se tratar de bug-fix.

## Riscos e decisões não-óbvias

1. **Confusão `dados_adicionais` vs `dados_coletados`** — spec usa o nome errado. Decisão TL: zerar apenas `dados_coletados`. Mantém `dados_adicionais` (metadados de domínio: cubagem, gôndola).
2. **Atomicidade sem transação SQL** — risco residual aceito; mitigação por idempotência (item já pendente → no-op). Documentar no commit.
3. **`buscarMissao` retorna objeto possivelmente cacheado** — antes do passo 3 confirmar `statusMissao` lendo de novo o registro fresh; evita race com pausar/cancelar concorrente.
4. **`MISSAO_REABERTA` é case novo no enum** — qualquer consumidor que faça `match` exaustivo sobre `AcaoExtrato` sem `default` quebra. Buscar consumidores: `grep -rn "AcaoExtrato::" componentes/` antes do PR e validar exaustividade.
5. **Não aplicar `transicionar()`** — `MissoesStatus::transicionar()` dispara hooks pesados (`gerarMissaoCarregamento`, `processarPosFinalizacaoRetorno` etc.). Para AGUARDANDO_APROVACAO → EM_ANDAMENTO esses hooks **não devem** rodar. Decisão TL: update direto via `MissaoLogistica::atualizar()` evitando o caminho de `transicionar()`. Validar que `podeTransicionarPara` permite a transição (já confirmado em `StatusMissao.php:151`) só por sanidade — mas não chamar.

## Breaking changes

Nenhum funcional. Único impacto técnico: novo `case MISSAO_REABERTA` no enum `AcaoExtrato` — código que itere/match sobre o enum sem `default` precisa ser atualizado (ver risco 4).

## Plano de teste

- **Unit (recomendado, não bloqueante)**: TDD em `Satelites/Traits/` no padrão `*TddTest.php` cobrindo as 4 ramificações (já-pendente, status-bloqueado, reset-sem-rebote, reset-com-rebote).
- **Integration (manual contra `dev_gx_goldie`)**: cenários 5a-5d listados na ordem de implementação.
- **Smoke do gestor**: abrir `designacao_painel`, resetar item de missão em `aguardando_aprovacao`, confirmar que o modal exibe o novo `data-confirm-msg`, que a missão some da fila de aprovação e reaparece em "Em Andamento", e que a tela do operador para o item resetado abre limpa (sem peso/quantidade fantasma).

## Out of scope (confirmando spec)

- Criar Laboratório novo ou mover handler.
- Alterar UI do botão (ícone, posição, condições de exibição).
- Alterar `recomecarItem`, `pularItem`, `duplicarItem`, `remover`.
- Reset em missões `concluida`/`cancelada`.
- Reset em massa.
- Mudar regras de permissão.
- Notificar operador original.
- Migration nova.
- **Acréscimos do TL ao out-of-scope**:
  - Não migrar este código de Inteligencias V1 → Sondas V2 nesta feature.
  - Não introduzir transação SQL explícita; fica para uma issue separada se a equipe decidir hardenar a camada de persistência.
  - Não tocar `MissoesStatus::transicionar()` — este reset é caminho dedicado e não precisa atravessar o mecanismo geral de transição.
