# Módulo LGPD (`Portal/Lgpd`)

> Contrato de leitura do módulo (ADR-004/ADR-014). Mantenha sincronizado com o código.

## Propósito

Operar o **programa de privacidade** (ADR-019): central do titular (**DSAR** — acesso/correção/
eliminação/portabilidade), política de privacidade + canal do **encarregado (DPO)**, atendimento no
CMS e **retenção/anonimização** dos protocolos encerrados. Fora do escopo **deste módulo**:
ROPA/DPIA formal e banner de consentimento (sem cookies não essenciais por padrão). O runbook de
incidente/ANPD vive em `wiki/runbooks/anpd-incidentes.md`.

- **Grupo:** `Portal` (organizacional — **não** entra no token, ADR-014).
- **Áreas:** portal · CMS.
- **Ativação:** `php artisan core:module:activate Lgpd [--disable]` (ADR-034).

## Estrutura

```
src/modules/Portal/Lgpd/
├── ModuleProvider.php        # register: registra o alvo de retenção no Core
├── Database/{Migrations,Models}
├── Logic/{Actions,Controllers,DTOs,Requests}
├── Retention/LgpdDsarRetention.php   # implementa Core\Contracts\Ports\RetentionTarget
├── Routes/{portal,cms}.php
├── Layout/Views/{privacy,index,received,cms/{index,show}}.blade.php
├── Lang/{pt_BR,en,es,fr}/{menu,index,privacy,cms}.php
└── Test/LgpdTest.php
```

## Token canônico (ADR-004)

**`lgpd`** — classes (`Lgpd*`), rotas (`lgpd.*`), tabela (`lgpd.dsar_requests`), spans
(`lgpd.*`). Verificação: `tools/name-check.sh --grep lgpd`.

## Classificação e cifra (ADR-019)

O **dado pessoal** do titular (`name`/`email`/`description`/`response_note`) é gravado **cifrado**
(cast `encrypted`) — a coluna crua não contém o texto claro. Nenhum dado pessoal entra no log
técnico (ADR-024): o `log_event` carrega apenas `id`/`type`/`status`.

## Rotas

| Nome | Método | URI | Acesso |
|---|---|---|---|
| `lgpd.privacy` | GET | `/politica-de-privacidade` | pública |
| `lgpd.dsar.form` | GET | `/lgpd` | pública |
| `lgpd.dsar.store` | POST | `/lgpd` | pública (`throttle:lgpd-dsar` + honeypot/time-trap) |
| `lgpd.dsar.received` | GET | `/lgpd/recebido` | pública (exibe o protocolo) |
| `lgpd.cms.index` | GET | `/cms/lgpd` | `lgpd.ver` |
| `lgpd.cms.show` | GET | `/cms/lgpd/{dsar}` | `lgpd.ver` (leitura auditada — `audit.access`) |
| `lgpd.cms.update` | PUT | `/cms/lgpd/{dsar}` | `lgpd.editar` |

## Permissões (ADR-033)

Declaradas em `ModuleProvider::config()` (`'permissions' => Permission::ACTIONS`) e aplicadas com
`Permission::authorize()`: `lgpd.{ver|editar|aprovar|arquivar}`.

## Eventos e observabilidade (ADR-024/ADR-035)

- **Emite:** `lgpd.submit_dsar` (portal, auditoria **explícita**) e `lgpd.update_dsar` (CMS,
  auditoria automática) — span + `log_event` sem PII.
- **Retenção:** `LgpdDsarRetention` registrado no `Core\Contracts\RetentionRegistry`; o comando
  `retention:expunge` (Core, agendado mensal) anonimiza protocolos encerrados além do prazo,
  **respeitando `legal_hold`** e sem apagar histórico (ADR-017). O legal hold é gerido no CMS
  (`/lgpd/{protocolo}` → Aplicar/Liberar, `lgpd.aprovar`) e auditado como `lgpd.legal_hold`; a
  retenção pagina (`chunkById`) e `type`/`status` têm `CHECK` no banco.

## Convenções (checklist)

- [x] `declare(strict_types=1)`; DI (sem `new`/`resolve` no corpo); sem traits/abstract/generics.
- [x] Dado pessoal cifrado (`encrypted`); classificação dirige cifra/trilha/retenção.
- [x] Logs JSON + `log_event` sem PII (ADR-024); DSAR auditado explicitamente (ADR-035).
- [x] 4 locales no `Lang/` com paridade de chaves (`ModuleI18nTest`).
- [x] A11y eMAG/WCAG 2.2 AA; alvos ≥ 2.75rem no mobile (ADR-025).

## Verificação

```bash
make exec cmd='bash tools/name-check.sh --grep lgpd'
make exec cmd='php tools/arch-check' && make exec cmd='php tools/migration-check'
make check            # cadeia completa (lint, PHPStan max, gates, testes, cobertura)
make check E2E=1      # + e2e/axe/sniff/perf quando a UI muda
```
