# Módulos da V2 — como estão implementados

> 31 módulos de negócio após ignorar `_Exemplos` (inventário em
> [`10-INVENTARIO-DE-MODULOS.md`](10-INVENTARIO-DE-MODULOS.md)). As seções abaixo cobrem os que têm
> **contraparte nominal** no V7/backlog e foram lidos em profundidade. O dossiê completo (9 arquivos)
> existe para oito deles; os demais estão no inventário e no backlog de aproveitamento.

| Módulo | Status no V7 | Padrão a portar | Veredito |
|---|---|---|---|
| `Publicacoes/Noticias` | **entregue** | taxonomia + workflow de aprovação + preview + sitemap | aproveitável |
| `Publicacoes/Legislacao` | backlog | agendamento por data + anexos + importação idempotente | aproveitável |
| `Servicos/Ouvidoria` | backlog | protocolo legível + dicionários | parcial |
| `Publicacoes/Licitacoes` | panorama | anexos por magic bytes + órgãos N:N | parcial |
| `DadosAbertos/TransparenciaFiscal` | backlog + `DadosAbertos` entregue | hierarquia + competência × valor | parcial |
| `Infraestrutura/FormulariosDinamicos` | — | motor versionado com score | motor aproveitável; módulo não |
| `Infraestrutura/GestaoPermissoes` | ADR-033 | matriz de perfil | parcial (só referência) |
| `Support/StructuralLandingEditor` | F4/widgets | seções JSON + registry + prepare em lote | parcial |
| `Support/SupportConfigs` | ADR-034/036 | flags com efeito em menu+rota | parcial |
| `Institucional/BasicPage` | módulo Home/páginas | página como dado + fallback de home | parcial |

---

## 1. Notícias

**Modelo.** `v2_news` com UUID, soft delete, `status` `draft|pending_approval|published|rejected`,
`draft_token`, `reviewer_id`, `metadata` jsonb, `is_highlighted`/`is_super_highlighted` e
`search_vector` (`Models/News.php:142-145`;
`Migrations/2026_07_08_000001_create_v2_news_table.php:59-90`). Taxonomia completa: categoria
hierárquica (`parent_id`, `is_system`, `color`, `sort_order`), tags N:N e galeria N:N com ordem
(`Models/NewsCategory.php:100-140`; `Models/News.php:246-270`).

**Workflow.** `draft|rejected → pending` (`Controllers/NoticiasCmsController.php:552-580`),
`pending → published` com `published_at` e `reviewer_id` (`:592-617`), `pending → rejected` com motivo
obrigatório (`:626-651`); notificação de aprovadores com vigência (`NewsApprover::scopeActive`,
`Services/NewsNotificationService.php:46-150`).

**Portal.** `/noticias`, `/noticia/{slug}`, preview por token e `/news-sitemap.xml`
(`Routes/web.php:15-41`); JSON-LD `NewsArticle`; prev/next determinístico por
`(COALESCE(published_at, created_at), id)` (`Controllers/NoticiasPortalController.php:186-230`).

**Riscos.** `update()` aceita `status` do formulário e publica sem aprovação (`:378-417`);
aprovar/rejeitar não invalidam o cache de páginas; `clearPageCache` usa `Redis::keys` (O(N)); RBAC
`read/write` não distingue **aprovar**; `allPublished` sem limite no seletor de super-destaque;
publicação não agenda (sempre `now()`).

## 2. Legislação

**Modelo.** `v2_legislation` (`number`, `year`, `publication_date`, `is_active`,
`is_highlighted`, categoria `set null`, `metadata`) + `v2_legislation_files` 1:N (`order`, `view_pdf`,
`original_name`, `mime_type`, `file_size`).

**Regra não óbvia.** `scopePublished` = ativo **e** `publication_date <= now()`
(`Models/V2Legislation.php:138-140`) — o agendamento é implícito na data. Categoria excluída faz
soft delete **renomeando o slug** para `-deleted-xxxxxx` e desvincula itens
(`Controllers/Admin/LegislationCmsController.php:223-250`); a unicidade considera `withTrashed()`.

**Anexos.** Allowlist de MIME + **magic bytes**, storage `legislacao/{id}`, metadados e reordenação
(`Services/LegislationFileService.php:23-121`); download força `attachment`, bloqueia não publicado e
redireciona URL externa (`Controllers/Portal/LegislationPortalController.php:105-145`).

**Lacuna central.** Não há versionamento/consolidação/revogação — o backlog do V7 pede exatamente
isso (`modulo-legislacao.md`). O V2 resolve agendamento e anexos, não a linha do tempo normativa.

## 3. Ouvidoria

**Modelo.** `v2_ombudsman_requests` com `protocol` unique `OUV-{Ymd}-{8}` (`Models/OmbudsmanRequest.php:86-88`),
`status` inteiro, `is_anonymous`, `ip`, `metadata` e FKs para tipo/assunto/origem/forma de resposta;
`v2_ombudsman_answers` 1:N. Portal com `throttle:10,1` (`Routes/web.php:5-6`).

**O que falta.** Sem prazo/SLA, sem consulta por protocolo, sem e-mail ao manifestante e sem
transição de status validada; responder apenas grava a resposta e força `status=2`
(`Controllers/Admin/OmbudsmanRequestCmsController.php:80-91`). O checkbox de consentimento não é
validado nem persistido.

**Riscos.** `$r->all()+create()` no portal permite injetar `status`, `protocol` e `is_anonymous`
(`Controllers/Portal/OmbudsmanPortalController.php:32-35`); `scopeSearch` usa `orWhere` no topo e
quebra filtros compostos (`Models/OmbudsmanRequest.php:153`); status mágico sem enum.

## 4. Licitações

`v2_licitacoes` com `object` (strip_tags), valor + `show_value`, três datas, `is_active`, `status_id`,
jsonb `jurisdicionados/arquivos/metadata` e `v1_id`; anexos 1:N com ordem/soft delete; órgãos N:N com
criação inline. Anexos validados por **magic bytes com offset**
(`Services/LicitacaoAttachmentService.php:25-121`).

Riscos: o portal carrega **todas** as linhas ativas e filtra no navegador
(`Controllers/Portal/LicitacaoPortalController.php:31`); `generateSlug` usa `static::$oldSlug` nunca
definido (sufixo `-1` ao editar); o jsonb `arquivos` legado convive com a tabela de anexos (duas
verdades); force-delete ignora anexos já soft-deleted.

## 5. Transparência Fiscal

`v2_transparency_items` (título, descrição, valor, `reference_date`, ativo, categoria, `metadata`,
slug com sufixo do id) + categorias hierárquicas com `order`. Portal agrupa raiz/filhas e mostra
valor × competência; detalhe em cards.

Lacunas: **sem CRUD de categorias**; `children` sem `active()` → N+1 **e** itens inativos exibidos
(`Controllers/Portal/TransparencyPortalController.php:17-19`); slug só na criação; sem paginação;
sem anexo/CSV/API (não atende LAI); `TransparencyRequest` calcula `$id` e não usa; **nenhum teste do
módulo**. No V7, `Portal/DadosAbertos` já cobre dataset + DCAT + export com `ETag`.

## 6. Formulários Dinâmicos (dois caminhos)

O **módulo** é legado: `v2_forms`/`v2_form_questions`/`v2_form_submissions`; `index/create/edit`
redirecionam para o motor unificado, mas `store/update/destroy` ainda gravam `V2Form` (caminho morto
e divergente — `Controllers/Admin/FormCmsController.php:32-72`), e o `POST` do portal grava
`answers = $r->except('_token')` sem validação (`Controllers/Portal/FormPortalController.php:29-30`).

O **motor** real está em `V2/Services/FormularioDinamico`: versões imutáveis com `schema` +
`scoring_config`, campos com `weight/options/rules`, submissões com `answers` jsonb + GIN
`jsonb_path_ops`, `score/max_score/result_level/result_token/consent_at`, `ip_hash` HMAC-SHA256,
scoring por faixas e diagnóstico. É o serviço com maior lacuna no V7 e o mais próximo do DDD-lite.

## 7. Gestão de Permissões

`V2Role.permissions_v2` jsonb + `users.v2_role_id` (um papel), níveis `none|read|write`; resolução
por chave canônica + aliases + normalização (`PermissionService.php:33-140`); matriz de rádio no
CMS; `super-admin` não pode ser criado/renomeado/atribuído manualmente. O middleware resolve o
módulo por `active_pattern` (ignora o parâmetro da rota), é **fail-open** fora de módulo e deixa
`GET/HEAD` passarem (`V2PermissionMiddleware.php:75-107`); `update()` de usuário não protege o alvo
super-admin.

## 8. Structural Landing Editor

Config por área em `v2_layout_base_configs.payload` jsonb; home como lista versionada v3
`{type, parameters, hide}` com whitelist e defaults espelhando partials
(`Services/HomepageSectionsService.php:18-22,194-241`); HTML de área em
`v2_structural_portal_contents`; biblioteca de snippets em `v2_sle_assets`; editor CodeMirror.
O renderer do `LandingSectionsEditor` prepara mídia em lote e delega ao widget.
**Risco maior:** `BlockRenderer` executa Blade vindo do banco (`Services/BlockRenderer.php:143-230`).

## 9. SupportConfigs

`v2_module_flags` (module_key, active, group), `v2_system_controls`, `v2_cms_theme_configs`,
`v2_portal_configs`, `v2_system_mail_configs`. Flag desativada oculta menu **e** bloqueia rota, sem
apagar dados; sem flag = ativo; 4 abas com switches AJAX. **Sem testes do módulo.** Riscos: gate por
e-mail; reset de senha de qualquer usuário; `shell_exec`/`which tail` no leitor de log
(`SystemDiagnosticsService.php:205-208`); `app_debug` mutável em runtime; flags sem invalidação
consistente.

## 10. BasicPage

`v2_basic_pages(slug unique, title, content text, content_type, is_runtime, metadata jsonb)` sem soft
delete; `metadata.layout` e `metadata.seo` por slug (`Models/BasicPage.php:61-71,257-300`). A home
compõe seções dinâmicas (Notícias/Transparência/Agenda/Operadores) + estáticas do SLE com fallback
(`Services/HomepageDataService.php:51-110,185-250`); `homepage` é slug protegido e só o SLE edita.
Contato responde JSON para AJAX e redirect+flash sem JS, com 422 por campo.
Riscos: `content` cru em `runtime.blade.php:6` (XSS armazenado se o suporte for comprometido); PII em
log (`ContactFormService.php:48-52`); contato não é persistido; reCAPTCHA via CDN (fere zero-CDN).

---

## Módulos sem dossiê (destino: panorama/inventário)

AgendaEventos, AInstituicao, EmpresasAssociadas, Faq, Glossario, JogoResponsavel, PaginasCustomizadas,
PerguntasFrequentes, SorteSua, AutoTesteJogoResponsavel, SeloOperador, Sorteios, Webhook (módulo),
MigracaoLegada, Utilitarios/*, Infraestrutura/* restantes. O domínio de loteria
(`SorteSua`/`Sorteios`/`SeloOperador`/`AutoTesteJogoResponsavel`) **não tem contraparte no V7** e não
deve entrar no roadmap por analogia.
