# Task 004 — Canais: Interface + Registry + Factory + LogCanal (stub)

**Depende de:** 001
**Bloqueia:** 006, 011

## Objetivo

Definir a abstração de canal de envio e fornecer o stub `LogCanal`. Não implementar provider real (SMS/Email/WhatsApp) — fica para PRs externos.

## Deliverables

### `Canais/OtpCanalInterface.php`

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

### `Canais/OtpCanalRegistry.php`

```php
class OtpCanalRegistry {
    public static function registrar(string $nome, string $classe): void;     // valida is_subclass_of
    public static function temRegistro(string $nome): bool;
    public static function resolver(string $nome): OtpCanalInterface;          // throws RuntimeException se não houver
    public static function listar(): array;                                    // nomes registrados
    public static function reset(): void;                                       // testes
}
```

### `Canais/OtpCanalFactory.php`

Wrapper conveniente. `OtpCanalFactory::build($nome)` = `OtpCanalRegistry::resolver($nome)`. Permite override em testes (`setOverride(?OtpCanalInterface $impl, ?string $forceNome = null)`).

### `Canais/LogCanal.php`

Implementação stub:
- `nome()` retorna `'log'`.
- `validaDestinatario()` aceita qualquer string não-vazia.
- `enviar()` faz `error_log("[Otp][LogCanal] dest=... codigo=... contexto=...")` e retorna `true`.

## Critério de aceite

- Registry rejeita classes que não implementam a interface (testes em 012).
- `LogCanal` registrado por padrão no bootstrap (task 011).
- Lint OK.
