# PRD — Módulo OTP genérico

> Caminho: `componentes/Missoes/Mensageria/sinais/Otp/`
> Namespace: `GalaxiaMissoes\Mensageria\sinais\Otp`

## 1. Contexto

Hoje o sinal `EdicaoRota` carrega seu próprio sub-pacote `Otp/` (`OtpSenderInterface`, `OtpSenderLog`, `OtpSenderFactory` + tabela `edicoes_rota_otp` + `GerarOtpLaboratorio` + `ValidarOtpLaboratorio`). Funciona, mas:

- A tabela `edicoes_rota_otp` referencia `edicoes_rota` por FK — qualquer outra feature que quiser OTP precisaria criar a sua própria tabela e duplicar todo o ciclo de vida (gerar, expirar, contar tentativas, marcar validado).
- O `OtpSenderFactory` resolve uma única implementação global — não permite que dois consumidores escolham canais diferentes (um por SMS, outro por e-mail).
- A lógica de "novo código a cada acesso", "limite de tentativas", "expiração de 5 min" está hard-coded dentro do EdicaoRota e não é configurável por consumidor.

**Resultado pretendido:** um módulo genérico que qualquer feature possa consumir — "preciso enviar um código pra confirmar X via Y canal e quero ser avisado quando o usuário acertar". O consumidor não conhece a tabela do OTP nem o mecanismo de envio; o módulo OTP não conhece o domínio do consumidor.

**Cenários de uso (não-exaustivos):**
- Edição de rota pelo motorista (atual `EdicaoRota`) — código por SMS/WhatsApp.
- Confirmação de alteração de senha — código por e-mail.
- Confirmação de cadastro de novo usuário — código por SMS ou e-mail.
- Aprovação de operação financeira sensível — código por canal preferido do operador.

## 2. Requisitos

### 2.1 Funcionais — consumidor (qualquer sinal/satélite)

- Solicitar a criação de um OTP fornecendo: contexto, canal, destinatário, referência externa (id próprio do consumidor) e dados adicionais opcionais.
- Receber em troca um identificador opaco (`otp_id`) e o status inicial (`AGUARDANDO_ENVIO`).
- Acionar o envio (síncrono — laboratório resolve canal e dispara).
- Validar um código digitado pelo usuário, recebendo `validado` ou erro tipado (expirado, código incorreto, tentativas excedidas, já validado, cancelado).
- Cancelar/invalidar OTPs ativos (ex.: ao trocar de canal).
- Consultar o estado atual sem efeitos colaterais.

### 2.2 Funcionais — sistema

- Persistir cada OTP com hash do código (nunca em claro), expiração, contador de tentativas e auditoria de eventos (envios, validações, falhas).
- Suporte a múltiplos canais via Registry (`sms`, `email`, `whatsapp`, `log`/stub, plugáveis).
- Suporte a múltiplos eventos pós-validação via Registry (cada contexto registra seu callback).
- Política de "novo código a cada solicitação" e invalidação implícita dos anteriores do mesmo `(contexto, referencia_externa)`.
- Configuração por contexto: validade em segundos, máximo de tentativas, tamanho do código (4–8 dígitos).
- Auditoria completa: tabela secundária `verificacoes_otp_eventos` registrando cada criação, envio, tentativa errada, validação, expiração, cancelamento.

### 2.3 Não-funcionais

- **Desacoplamento total** — o módulo OTP não importa nada do consumidor. O consumidor importa só `Satelites/OtpSatelite` (interface estável) e, opcionalmente, registra um Evento.
- **Padrão Galaxia/V2** — Sondas (V2), Laboratórios, Validators, Enums backed, Satélite como porta de entrada interna. `App.php` com `Observatorio()` e `GalaxiaAutoLoad()`.
- **Próximo de Designacoes/Solicitacoes** — usar Strategy/Factory para canais e eventos; Registry estático para registro; testes TDD em `Testes/<espelho>/`.
- **Segurança** — código nunca persistido em claro; hash via `password_hash` (BCRYPT); rate-limit por `(contexto + destinatário)`.
- **Stub canal log** — toda configuração funciona em ambiente de dev sem provider real.

---

## 3. Arquitetura

```
componentes/Missoes/Mensageria/sinais/Otp/
├── App.php                   ← entry-point Galaxia (Observatorio, GalaxiaAutoLoad, dispatcher interno)
├── PRD.md
├── PADRAO_INTEGRACAO.md      ← guia para outros sinais consumirem (criado na task de docs)
├── tasks/                    ← especificações deste PRD desmembradas
├── Migrations/               ← schema versionado
├── Sondas/                   ← V2 — persistência de OTP e eventos de auditoria
├── Inteligencias/            ← (não usar — vide convenção V2)
├── Laboratorios/             ← use cases (Criar, Enviar, Validar, Cancelar, Consultar)
├── Validators/               ← regras de input por laboratório
├── Enums/                    ← Status + máquina de transições
├── Satelites/                ← OtpSatelite — porta de entrada para outros módulos
├── Canais/                   ← Strategy de envio
│   ├── OtpCanalInterface.php
│   ├── OtpCanalRegistry.php
│   ├── OtpCanalFactory.php
│   ├── LogCanal.php          ← stub (escreve em error_log)
│   └── (sms/email/wpp serão registrados externamente conforme houver provider)
├── Eventos/                  ← Strategy pós-validação
│   ├── OtpEventoInterface.php
│   └── OtpEventoRegistry.php
├── Configuracao/             ← OtpConfig por contexto (validade, max_tentativas, tamanho)
├── html/                     ← (vazio inicialmente — módulo é backend; consumidores trazem suas próprias telas)
└── Testes/                   ← TDD
```

### 3.1 Modelo de dados

#### Tabela `verificacoes_otp`
| Coluna | Tipo | Descrição |
|---|---|---|
| `id` | SERIAL PK | |
| `contexto` | VARCHAR(50) | ex.: `edicao_rota`, `alteracao_senha` |
| `referencia_externa` | VARCHAR(255) | identificador do consumidor (token, id, etc.) |
| `canal` | VARCHAR(20) | `sms`, `email`, `whatsapp`, `log` |
| `destinatario` | VARCHAR(255) | telefone, e-mail, etc. |
| `codigo_hash` | VARCHAR(255) | `password_hash(BCRYPT)` do código |
| `tamanho_codigo` | INT | 4–8 |
| `expira_em` | TIMESTAMP | |
| `validade_segundos` | INT | snapshot da config no momento da criação |
| `max_tentativas` | INT | snapshot |
| `tentativas` | INT DEFAULT 0 | |
| `status` | VARCHAR(30) | máquina de estados (§3.2) |
| `dados_adicionais` | JSONB | livre (ex.: `{"identificador_motorista":"João"}`) |
| `validado_em` | TIMESTAMP NULL | |
| `cancelado_em` | TIMESTAMP NULL | |
| `motivo_cancelamento` | TEXT | |
| `ip_origem` | VARCHAR(45) | |
| `user_agent` | VARCHAR(500) | |
| (block padrão Galaxia) | | |

#### Tabela `verificacoes_otp_eventos` (auditoria append-only)
| Coluna | Tipo | Descrição |
|---|---|---|
| `id` | SERIAL PK | |
| `verificacao_otp_id` | INT FK | |
| `tipo_evento` | VARCHAR(30) | `criado`, `enviado`, `tentativa_errada`, `validado`, `expirado`, `cancelado`, `falha_envio` |
| `detalhes` | JSONB | dados do evento (motivo, contagem de tentativa atual, erro do canal, etc.) |
| `ip_origem` | VARCHAR(45) | |
| `user_agent` | VARCHAR(500) | |
| `data_cadastro` | TIMESTAMP DEFAULT NOW() | |
| (block) | | |

### 3.2 Máquina de estados

```
AGUARDANDO_ENVIO ──enviar()──► ENVIADO ──validar(ok)──► VALIDADO (final)
       │                          │                        
       │                          ├─tentativas≥max──► INVALIDADO_TENTATIVAS (final)
       │                          ├──expirou────────► EXPIRADO (final)
       │                          └──cancelar()─────► CANCELADO (final)
       │                          
       └──cancelar()──► CANCELADO (final)
```

`EXPIRADO` é descoberto por consulta lazy (a cada `validar()` chama `expiraSeNecessario()`).

### 3.3 Canais (Strategy + Factory + Registry)

```php
interface OtpCanalInterface {
    public function enviar(string $destinatario, string $codigo, array $contextoExtra = []): bool;
    public function nome(): string;       // 'sms', 'email', 'whatsapp', 'log'
    public function validaDestinatario(string $destinatario): bool;
}
```

- `OtpCanalRegistry::registrar('sms', SmsTwilioCanal::class)` — chamado em bootstrap (App.php).
- `OtpCanalFactory::resolver('sms')` — devolve instância nova (sem singleton, pra evitar estado compartilhado em testes).
- `LogCanal` é o default, sempre registrado, pra dev/test.

### 3.4 Eventos pós-validação (Strategy + Registry)

```php
interface OtpEventoInterface {
    public function aoValidar(object $otp, array $extras = []): void;
    public function aoExpirar(object $otp): void;          // default no-op
    public function aoCancelar(object $otp): void;         // default no-op
}
```

- `OtpEventoRegistry::registrar('edicao_rota', EdicaoRotaOtpEvento::class)` — registrado pelo consumidor no bootstrap (App.php do EdicaoRota).
- `aoValidar` é disparado depois que o status muda para `VALIDADO` e a transação é confirmada.
- Erros no evento **não** revertem a validação (a validação é o source of truth) — falhas vão pro log de eventos.

### 3.5 Configuração por contexto

```php
OtpConfig::registrar('edicao_rota', [
    'validade_segundos' => 300,
    'max_tentativas'    => 3,
    'tamanho_codigo'    => 6,
    'canal_padrao'      => 'sms',
]);
```

Sem registro, usa defaults globais (300s, 3 tentativas, 6 dígitos, canal `log`).

### 3.6 Porta de entrada — `OtpSatelite`

API pública estável que consumidores devem usar (em vez de chamar Laboratórios diretamente):

```php
$resp = (new OtpSatelite($galaxia, 'criar', [
    'contexto'           => 'edicao_rota',
    'referencia_externa' => $tokenEdicao,
    'canal'              => 'sms',
    'destinatario'       => '11999999999',
    'dados_adicionais'   => ['identificador_motorista' => 'João'],
]))->response;
// $resp['otp_id'], $resp['status'], $resp['expira_em']

$resp = (new OtpSatelite($galaxia, 'enviar', ['otp_id' => $resp['otp_id']]))->response;

$resp = (new OtpSatelite($galaxia, 'validar', [
    'contexto'           => 'edicao_rota',
    'referencia_externa' => $tokenEdicao,
    'codigo'             => '123456',
    'extras'             => ['ip' => '...', 'user_agent' => '...'],
]))->response;
// dispara OtpEventoRegistry::resolver('edicao_rota')->aoValidar(...)
```

Métodos suportados: `criar`, `enviar`, `validar`, `cancelar`, `consultar`.

### 3.7 App.php — Observatorio + GalaxiaAutoLoad

Espelha o padrão de Designacoes:
- `Observatorio($available)` — pós-processa respostas de actions internas (ex.: emitir `atualizarPagina()` após validação OK em painel logado).
- `GalaxiaAutoLoad($available)` — delega para `Observatorio()`.
- Dispatcher de ações públicas (`criar`, `enviar`, `validar`, `cancelar`, `consultar`) — para uso direto via roteador Galaxia quando necessário (ex.: chamada AJAX direta).
- **Bootstrap de canais e configs default** (sempre registra `log`, registra eventuais canais conforme env vars).

---

## 4. Migração do `EdicaoRota`

Sequência (sem big-bang; por etapas):

1. Subir o módulo OTP novo + migrations + canal `log`.
2. EdicaoRota passa a ter `Eventos/EdicaoRotaOtpEvento.php` que implementa `OtpEventoInterface::aoValidar()` — abrindo a sessão `$_SESSION['edicao_rota_<token>']` que hoje vive no `App.php` do EdicaoRota.
3. EdicaoRota passa a usar `OtpSatelite` em vez do antigo `GerarOtpLaboratorio`/`ValidarOtpLaboratorio`. Os Laboratórios antigos viram thin wrappers (deprecated, deletáveis em PR seguinte).
4. Tabela `edicoes_rota_otp` é descontinuada (drop em migration nova). Os dados não são migrados — OTP é efêmero por design.
5. Sub-pacote `EdicaoRota/Otp/` é removido.

---

## 5. Fora de escopo deste PRD

- Implementações reais de canais (SmsTwilio, EmailSes, WhatsAppMeta) — apenas a interface + Registry. Cada provider real é PR próprio.
- Painel admin para listar OTPs ativos / históricos — pode existir, mas não está coberto aqui.
- Multi-fator (2FA, TOTP, hardware key) — escopo de outro módulo (`Verificacao/sinais/Mfa`).
- Rate-limit avançado (sliding window, IP-based) — versão 1 usa apenas tentativas por OTP.

---

## 6. Critérios de aceite

- [ ] `OtpSatelite::criar`/`enviar`/`validar`/`cancelar`/`consultar` cobertos por testes TDD.
- [ ] Máquina de estados rejeita transições inválidas com cobertura.
- [ ] `LogCanal` funciona out-of-the-box em ambiente sem provider.
- [ ] `OtpEventoRegistry` dispara `aoValidar` corretamente após validação OK; falha do evento não reverte status.
- [ ] EdicaoRota migrado para usar o módulo OTP, com seu evento próprio. Tabela `edicoes_rota_otp` removida via migration.
- [ ] Documentação `PADRAO_INTEGRACAO.md` cobre o fluxo de uso por um novo consumidor (com exemplo do "alteração de senha por e-mail").
- [ ] App.php expõe `Observatorio()` e `GalaxiaAutoLoad()` no padrão Galaxia.
- [ ] Nenhum import de Constelacoes (módulo é puramente Missoes).
- [ ] Sondas V2; nenhum `Inteligencias/`.

---

## 7. Tasks

Especificação operacional desmembrada em [`tasks/`](tasks/). Ordem sugerida:

1. [`001_setup_e_migrations.md`](tasks/001_setup_e_migrations.md)
2. [`002_enum_status.md`](tasks/002_enum_status.md)
3. [`003_sondas.md`](tasks/003_sondas.md)
4. [`004_canais_interface_registry_log.md`](tasks/004_canais_interface_registry_log.md)
5. [`005_configuracao.md`](tasks/005_configuracao.md)
6. [`006_laboratorios_criar_enviar.md`](tasks/006_laboratorios_criar_enviar.md)
7. [`007_laboratorio_validar.md`](tasks/007_laboratorio_validar.md)
8. [`008_eventos_interface_registry.md`](tasks/008_eventos_interface_registry.md)
9. [`009_laboratorios_cancelar_consultar.md`](tasks/009_laboratorios_cancelar_consultar.md)
10. [`010_satelite_porta_de_entrada.md`](tasks/010_satelite_porta_de_entrada.md)
11. [`011_app_observatorio_dispatcher.md`](tasks/011_app_observatorio_dispatcher.md)
12. [`012_testes_tdd.md`](tasks/012_testes_tdd.md)
13. [`013_migracao_edicaorota.md`](tasks/013_migracao_edicaorota.md)
14. [`014_documentacao_integracao.md`](tasks/014_documentacao_integracao.md)
