# Legislação — Aproveitamento para o V7

> **Fonte analisada:** `lotep/V2/Modules/Publicacoes/Legislacao`
> **Destino:** V7 (`src/modules/Publicacoes/Legislacao`)
> **Regra de ouro:** portar **regra e comportamento**, nunca transporte de
> implementação. Cada decisão abaixo cita a ADR ou regra do V7 que a justifica.

---

## 1. Veredito em uma tabela

| Bloco do legado | Veredito | Justificativa |
|---|---|---|
| Predicado de publicação (ativo + data ≤ agora) | **Portar** | Regra de produto central; adaptar para estado explícito. |
| Agendamento por data futura | **Portar** | Requisito de comunicação oficial; vira `agendado_para` + publicação por ação. |
| Categorias com ordem e ativo | **Portar** | Taxonomia simples e suficiente; sem árvore. |
| CRUD de norma com resumo e texto rico | **Portar** | Núcleo editorial; conteúdo em AST (ADR-028). |
| Múltiplos anexos com ordem e metadados | **Portar** | Requisito; migrar armazenamento para `Media` (ADR-026). |
| `view_pdf` (abrir em leitor) | **Portar** | Comportamento útil; restringir a PDF real. |
| Busca por título/resumo/número + filtros de data | **Adaptar** | Manter campos; trocar `ILIKE` por full-text PG 18 (ADR-007). |
| Importação de planilha com chave `number+year` | **Adaptar** | Boa ideia operacional; exige índice único e `upsert` (ADR-017). |
| Allowlist de MIME + magic bytes | **Portar** | Defesa em profundidade; completar com `finfo` e Media. |
| Download condicionado a publicado | **Portar** | Impede vazamento de ato agendado; reforçar com policy. |
| Auditoria de mutações | **Adaptar** | Trocar `V2AuditLogger` por `AuditWriter` nativo (ADR-035). |
| Importador V1 (`v1_id`, `imported_at`) | **Rejeitar** | ADR-042: sem ETL; conteúdo migrado manualmente. |
| Schema `v2_*` com `longText` para JSON | **Rejeitar** | ADR-017 exige `jsonb` e schema por domínio. |
| `V2Legislation::destroy` com prefixos de storage | **Rejeitar** | Frágil; mídia deve ser gerida pelo service. |
| Nó de HTML livre no editor | **Rejeitar** | ADR-028 proíbe HTML armazenado; risco de XSS. |
| Slug mutável sem histórico | **Adaptar** | Precisa de URL canônica/estável para citação (backlog). |
| JavaScript inline e Bootstrap | **Rejeitar** | ADR-022 (CSP sem inline) e ADR-009 (DaisyUI). |
| Seeder dentro do módulo | **Rejeitar** | ADR-041: demo em `src/demonstration`. |
| Versionamento/consolidação/revogação | **Construir** | Ausente no legado; exigido por `wiki/backlog/modulo-legislacao.md`. |

## 2. O que portar (padrão concreto)

### 2.1 Publicação como predicado + agendamento

O comportamento observável a preservar:

```php
$q->where('is_active', true)->where('publication_date', '<=', now())
```

(`V2/Modules/Publicacoes/Legislacao/Models/V2Legislation.php:138-141`)

No V7, manter a semântica, mas com estado explícito:

- `situacao = 'publicado'` **e** `publicado_em <= now()` governam o portal;
- `agendado_para` é um campo de intenção, não a fonte da verdade;
- publicar/despublicar é ação auditada (`LegislacaoPublicarAction`), com token
  `legislacao.aprovar` (ADR-033) e evento `legislacao.publicada` (ADR-024/035).

**Por que não portar o par de booleanos:** dois campos (`is_active` e data)
permitem estados impossíveis (ativo sem data, data futura sem agendamento) e não
deixam rastro de quem publicou. A ADR-001 exige determinismo e previsibilidade.

### 2.2 Anexos múltiplos, ordenados e com metadados

Portar o contrato de dados: título exibível com fallback, descrição, posição
inteira, flag de leitura embutida, nome original, MIME e tamanho. O portal deve
continuar oferecendo dois caminhos:

- PDF com `visualizar_pdf = true` abre `inline` em nova aba;
- todo o resto responde `attachment`.

No V7, o binário não fica em `file_url`; fica uma chave do `Gallery`/`Media`
(ADR-026), com checksum e variantes. O componente
`<x-service-media::document>` resolve a URL, e o download é uma rota com policy.

### 2.3 Importação operacional idempotente

Portar a **ideia**: planilha com cabeçalho, preview que reporta erros por linha,
processamento que cria ou atualiza pela chave natural `numero+ano`, criação
automática de categoria desconhecida e auditoria por linha.

Correções obrigatórias no porte:

1. Índice único parcial `(numero, ano) WHERE numero IS NOT NULL AND ano IS NOT NULL AND deleted_at IS NULL`.
2. `upsert` em transação por lote, não `first()` seguido de `update()` (evita
   duplicata sob concorrência).
3. **Não** sobrescrever `content` de registro existente; importar apenas campos
   presentes na planilha (`legislationImportAttrs` atual zera o texto).
4. Relatório de linhas ignoradas/atualizadas com `AuditWriter`, não log solto.

Base legal: ADR-017 §6-§8 (integridade e idempotência) e ADR-035 (evidência).

### 2.4 Validação de arquivo em profundidade

Portar a allowlist e a checagem de assinatura
(`V2/Modules/Publicacoes/Legislacao/Services/LegislationFileService.php:25-57`,
`:189-242`), com as seguintes adaptações:

- validação nativa do Laravel (`mimes`) **mais** `finfo` no servidor;
- `Services\Media` como porta única (ADR-026) — o módulo não grava em disco
  diretamente;
- limite de tamanho em um só lugar (config do service);
- rejeitar ZIP/RAR se o produto não precisar de pacotes; cada formato extra
  amplia a superfície de ataque.

### 2.5 Download bloqueado para não publicado

Portar exatamente a regra de `LegislationPortalController::downloadFile`
(`:105-133`): só anexo de norma publicada é servido; caminho local validado;
nome de download sanitizado; URL externa redirecionada.

No V7:

- policy `LegislacaoAnexoPolicy@download` ou consulta `publicadas()` na action;
- `Content-Disposition` definido pelo serviço;
- link assinado quando o anexo for grande ou estiver em storage externo
  (ADR-030), para não expor bucket público.

### 2.6 Categorias simples com slug e ordem

Portar o modelo plano (nome, slug, descrição, ativo, ordem) e a tela de gestão.
A ordem ascendente com desempate por nome é uma regra boa e barata.

**Não portar** a renomeação de slug na exclusão. No V7, arquivar é uma transição
de estado e a unicidade é um índice parcial `WHERE deleted_at IS NULL`
(ADR-017 §6); o slug de um registro arquivado pode ser reutilizado sem mutar o
histórico. A geração do slug deve ser determinística (ADR-001).

## 3. O que adaptar

| Item legado | Adaptação V7 | Fundamento |
|---|---|---|
| Tabela `v2_legislation` | `legislacao.legislacao` com `uuidv7`, `timestamptz`, `situacao`, `lock_version` | ADR-017, ADR-007 |
| `content` `longText` com JSON | `jsonb` AST TipTap; proibido HTML | ADR-028, ADR-017 §4 |
| `file_url` principal + tabela de anexos | Uma única coleção de anexos via `Media` | ADR-026, ADR-015 |
| `is_active`/`is_highlighted` | `situacao` + `destaque` com ação própria | ADR-001 |
| Slug gerado sem colisão | Geração determinística com sufixo estável + `slug_historico` | backlog (URL citável) |
| `read`/`write` | `ver`/`editar`/`aprovar`/`arquivar` | ADR-033 |
| Rotas POST com `/delete` | PUT/PATCH/DELETE + rotas de ação | ADR-010 |
| Bootstrap/Icons/JS inline | Tailwind 4 + DaisyUI 5 + Vite | ADR-009, ADR-010, ADR-022 |
| `str_limit()` | `Str::limit()` | framework 12 |
| `V2AuditLogger` | `AuditWriter` com antes/depois e hash | ADR-035 |
| `SEOService` no controller | `metadata['seo']` + `<x-service-seo::tags>` com `@graph` e `Legislation` | ADR-027 |
| `LegislacaoDataSource` com SQL cru | Consultas no módulo ou contrato de métricas do Core | ADR-013/ADR-015 |
| Seed de categorias no módulo | Migration idempotente (referência) + demo em `src/demonstration` | ADR-017, ADR-041 |
| Importador V1 | Removido; conteúdo entra pelo CMS | ADR-042 |
| `metadata` livre | `metadata` com chaves conhecidas (`seo`, `origem`, `migracao`) | ADR-017 |

### 3.1 Sobre a busca

O legado usa `ILIKE '%termo%'` em três colunas, sem índice. O V7 deve:

- gerar `search_vector` a partir de título, ementa, número e texto extraído do
  AST (ou de uma versão textual materializada);
- indexar com GIN (ADR-007);
- manter filtros por tipo, ano, órgão e intervalo de publicação;
- devolver 200 com lista vazia, nunca erro, para termo inválido.

### 3.2 Sobre a importação de planilha

Ela não conflita com a ADR-042: a ADR proíbe **ETL do banco v5**. Importar uma
planilha preparada pela equipe editorial é uma função do CMS e pode ser portada,
desde que a planilha não seja fonte de verdade permanente e que cada lote seja
auditado.

## 4. O que rejeitar

| Item | Por que rejeitar |
|---|---|
| `ContentRichHtmlFreeRenderer` | Devolve `attrs.html` cru; contraria ADR-028 e CSP (ADR-020/022). |
| `LinkRenderer` sem allowlist de esquema | Aceita `javascript:`; contraria ADR-020. |
| `onclick`, `addslashes()` e `confirm()` | Escape inadequado e JS inline; ADR-022 e ADR-025. |
| Renomear slug para `-deleted-<uniqid>` | Muta identificador público e usa aleatoriedade não determinística; ADR-001. |
| Sufixo aleatório de slug (`uniqid`) | Colisão teórica e URL instável; usar contador determinístico. |
| Unicidade global de slug com soft delete | Impede recadastro legítimo; usar índice parcial. |
| `destroy` apagando só prefixos conhecidos | Regra implícita e incompleta; mídia pertence ao service. |
| `file_url` como "documento principal" duplicado | Duas fontes de verdade para anexos; ADR-015/026. |
| Importador `v1_id`/`imported_at` | ADR-042. |
| Seeder com documentos demo dentro do módulo | ADR-041. |
| `str_limit`, helpers removidos | Compatibilidade com Laravel 12 / ADR-010. |
| `try/catch` vazio no SEO | Falha silenciosa; ADR-024 exige observabilidade. |
| Status derivado de `publication_date` nula | Ambiguidade; estado explícito (ADR-001). |

## 5. O que construir (não existe no legado)

O backlog `wiki/backlog/modulo-legislacao.md` é explícito: o módulo V7 deve ser
uma **referência jurídica estável e citável**. Isso exige:

1. **Entidade de norma** com `tipo`, `numero`, `ano`, `titulo`, `ementa`,
   `orgao`, datas e `situacao`.
2. **Versões imutáveis** (`legislacao_versoes`) com `vigencia_inicio`,
   `vigencia_fim`, `motivo` e `ato_alterador_id`.
3. **Consolidação**: o texto vigente é a composição das versões; o original
   permanece acessível para consulta.
4. **Revogação** como relação entre atos, não como exclusão.
5. **URL canônica/estável** para citação, independente do slug editorial.
6. **Full-text** com GIN e filtros por tipo/ano/órgão.
7. **Dados abertos** (CSV/JSON/XML) com licença e checksum (ADR-018).
8. **Assinatura/carimbo do tempo** quando o ato for oficial (backlog ICP-Brasil).
9. **Trilha de auditoria** com antes/depois de texto e metadados (ADR-035).
10. **Sitemap/SEO** com `Legislation` no `@graph` (ADR-027).

## 6. Checklist de aceite do porte

- [ ] Nenhum HTML armazenado: `content` é `jsonb` AST e o renderer tem allowlist
      de nós, marks e esquemas de URL.
- [ ] `situacao` explícita; publicar/despublicar/arquivar exigem
      `legislacao.aprovar`/`legislacao.arquivar`.
- [ ] Toda edição pós-publicação cria versão imutável com antes/depois.
- [ ] `numero+ano` tem índice único parcial e a importação usa `upsert`.
- [ ] Slug é único entre ativos, estável e tem histórico/redirect.
- [ ] Anexos usam `Media`, com checksum, MIME validado por `finfo` e download
      condicionado à publicação.
- [ ] Nenhum `<script>`/`onclick`/estilo inline nas views; JS no Vite.
- [ ] Layout e componentes DaisyUI, acessíveis por teclado (ADR-025).
- [ ] SEO via `metadata['seo']` e `@graph` com schema `Legislation`.
- [ ] Exportação de dados abertos publicada com contrato estável.
- [ ] Auditoria nativa com `request_id`, ator, origem e evidência.
- [ ] Nenhum importador do schema V1; conteúdo entra pelo CMS (ADR-042).
