# Task 006 — Laboratórios `Criar` e `Enviar`

**Depende de:** 002, 003, 004, 005
**Bloqueia:** 010

## Objetivo

Dois Laboratórios V2 (mesma forma de `AprovarSolicitacaoLaboratorio`) — receber input, validar, persistir.

## Deliverables

### `Validators/CriarOtpValidator.php`

Valida:
- `contexto` não vazio (string)
- `referencia_externa` não vazio
- `destinatario` não vazio
- `canal` se fornecido, deve estar em `OtpCanalRegistry::listar()` — se ausente, usa `canal_padrao` da config.
- `OtpCanalFactory::build($canal)->validaDestinatario($destinatario)` deve retornar true (do contrário acumula erro)

### `Laboratorios/CriarOtpLaboratorio.php`

Construtor `(array $entrada)`:
1. Roda Validator.
2. Resolve `OtpConfig::paraContexto($contexto)`.
3. Se já existir um OTP **ativo** (não-final) para o par `(contexto, referencia_externa)`, **cancela** o antigo (status → CANCELADO, `motivo_cancelamento` = `'novo_otp_solicitado'`).
4. Gera código numérico aleatório no tamanho da config — `random_int(10^(n-1), 10^n - 1)`.
5. `password_hash($codigo, PASSWORD_BCRYPT)` → `codigo_hash`.
6. Persiste via `OtpVerificacaoSonda::save(returnData=true)` com:
   - `contexto`, `referencia_externa`, `canal`, `destinatario`
   - `codigo_hash`, `tamanho_codigo`
   - `expira_em = now + validade_segundos`
   - `validade_segundos`, `max_tentativas` (snapshot)
   - `dados_adicionais` (JSON do payload livre)
   - `status = AGUARDANDO_ENVIO`
7. Registra evento `criado` em `verificacoes_otp_eventos` (via `OtpEventoSonda`).
8. **Retorna no `response`:**
   - `notification_class` = `success`
   - `otp_id`
   - `status`
   - `expira_em`
   - `_codigo_para_envio` (apenas em memória — usado por `EnviarOtp`. NUNCA persistido em log/response final exposto a HTTP).

> ⚠️ Como o `_codigo_para_envio` precisa fluir para `Enviar`, a alternativa é fazer `Criar` e `Enviar` num único Laboratório `CriarEEnviarOtpLaboratorio`. **Adotar a versão fundida** — é mais segura (o código nunca sai do Laboratório). Manter a separação somente se houver caso de uso para criar sem enviar (ex.: agendamento).

**Decisão:** o Laboratório padrão é `CriarEEnviarOtpLaboratorio` (cria, persiste, envia, atualiza status para `ENVIADO`, retorna `otp_id`+`status`). Manter `CriarOtpLaboratorio` puro e `EnviarOtpLaboratorio` separados como API "interna" para casos especiais — eles não recebem o código entre si; em vez disso, `Enviar` regenera código novo e atualiza hash. (Isso preserva "novo código a cada acesso" sem vazamento de código.)

### `Laboratorios/EnviarOtpLaboratorio.php`

Recebe `otp_id`. Comportamento:
1. Carrega OTP. Se não existe ou está em estado final → erro.
2. Se status `ENVIADO`, **regenera** código (novo `random_int`, novo hash) e mantém o status `ENVIADO` — efeito "reenviar".
3. Resolve canal via `OtpCanalFactory::build($otp->canal)` e chama `enviar(destinatario, codigo, dados_adicionais)`.
4. Persiste status `ENVIADO`, atualiza `expira_em` (validade_segundos a partir de agora — reset do relógio no reenvio é decisão do produto; por ora **resetamos**).
5. Registra evento `enviado` ou `falha_envio` em `verificacoes_otp_eventos`.
6. Resposta: `notification_class`, `otp_id`, `status`, `expira_em`.

## Critério de aceite

- Cada Laboratório expõe `public array $response`.
- Erros tipados via `Base_config\Helpers\Message`.
- Idempotência: tentar `Enviar` em OTP `VALIDADO`/`CANCELADO` retorna erro claro sem tocar no canal.
