# Aproveitamento no V7

> Comparação entre o módulo legado `V2/Modules/DadosAbertos/TransparenciaFiscal`
> e o módulo V7 `src/modules/Portal/DadosAbertos` (raiz `public-v7`).
> Leitura obrigatória: ADR-018 (`wiki/adr/ADR-018-dados-abertos.md`) e o backlog
> `wiki/backlog/modulo-transparencia-fiscal.md`.

## 1. O que o V7 já tem

| Capacidade V7 | Arquivo | Efeito |
|---|---|---|
| Dataset como entidade | `src/modules/Portal/DadosAbertos/Database/Models/DadosAbertosDataset.php` | título, slug, sumário, descrição, publisher, theme, license, periodicity, version, `columns`, `rows`, `tags`, `metadata`, `status`, `published_at` |
| Schema dedicado, UUIDv7 | `src/modules/Portal/DadosAbertos/Database/Migrations/2026_10_21_000000_create_dados_abertos_datasets_table.php` | `dados_abertos.datasets`, `jsonb`, `lock_version`, soft delete, índice parcial de publicados |
| Metadados DCAT/INDA | `src/modules/Portal/DadosAbertos/Database/Models/DadosAbertosDataset.php:46-49` | `FORMATS = csv|json|xml`, `PERIODICITIES` |
| Escopo público único | `src/modules/Portal/DadosAbertos/Database/Models/DadosAbertosDataset.php:103-108` | `publicados()` = status + `published_at <= now()` |
| Export determinístico | `src/modules/Portal/DadosAbertos/Logic/Actions/DadosAbertosExport.php` | CSV/JSON/XML, SHA-256, neutralização de CSV injection |
| Download com `ETag`/304 | `src/modules/Portal/DadosAbertos/Logic/Controllers/DadosAbertosExportController.php:19-48` | URL estável `/{slug}/{format}`, `X-Checksum-Sha256` |
| API pública paginada | `src/modules/Portal/DadosAbertos/Logic/Controllers/DadosAbertosApiIndexController.php` | `/api/v1/datasets`, 50/página, `ETag`, sem PII |
| Catálogo + detalhe | `src/modules/Portal/DadosAbertos/Logic/Controllers/DadosAbertosIndexController.php`, `src/modules/Portal/DadosAbertos/Logic/Controllers/DadosAbertosShowController.php` | paginação 12, JSON-LD `Dataset` |
| CMS completo | `src/modules/Portal/DadosAbertos/Logic/Controllers/Cms/*` | Create/Edit/Store/Update/Status/Destroy |
| RBAC de 4 ações | `src/modules/Portal/DadosAbertos/ModuleProvider.php:29-37` | `Permission::ACTIONS` (ADR-033) |
| Sitemap | `src/modules/Portal/DadosAbertos/ModuleProvider.php:21-23` | `DadosAbertosSitemapSource` (ADR-027) |

O legado equivalente tem **duas tabelas, zero export, zero API, zero metadados
padronizados**.

## 2. Comparação direta

| Eixo | Legado Transparência Fiscal | V7 Dados Abertos | Veredito |
|---|---|---|---|
| Entidade central | `v2_transparency_items` (linha) | `datasets` (conjunto + matriz) | V7 supera |
| Valores | coluna `value decimal(15,2)` por item | células em `rows jsonb` | V7 supera, com adaptação de competência |
| Hierarquia | `parent_id` + `order` | `theme`/`tags` (plano) | **legado tem algo a ensinar** |
| Competência | `reference_date` (date) | `published_at` (publicação) + `periodicity` | **legado tem algo a ensinar** |
| Legibilidade por máquina | nenhuma | CSV/JSON/XML + checksum | V7 supera |
| Licença | nenhuma | `license` CC-BY-4.0 default | V7 supera |
| Metadados | `metadata` livre | DCAT/INDA + `schema.org/Dataset` | V7 supera |
| Publicação | `is_active` booleano | `status` + `published_at` | V7 supera |
| API | nenhuma | versionada, paginada, `ETag` | V7 supera |
| Cache/revalidação | nenhuma | `Cache-Control` + 304 | V7 supera |
| Auditoria | `V2AuditLogger` manual | `log_event` + telemetria | V7 supera |
| Slug | gerado no model, não único | gerado no model, `unique` | V7 supera |
| Views | Bootstrap 5 + partials | Tailwind/daisyUI + CSP | V7 supera |

## 3. O que PORTAR (padrão concreto)

### 3.1 Hierarquia ordenada de categorias

**Portar a ideia, não a tabela**: categoria fiscal é um eixo de navegação com
posição explícita. No V7, isso vira uma tabela de catálogo do módulo de dados
abertos:

| Campo legado | Campo V7 | Regra |
|---|---|---|
| `name` | `name` | obrigatório |
| `slug` | `slug` | `UNIQUE`, editável, `^[a-z0-9]+(-[a-z0-9]+)*$` |
| `parent_id` | `parent_id` | FK auto-referente `nullOnDelete` (o legado não tem FK) |
| `order` | `position` | `unsignedInteger` + índice `(parent_id, position)` |
| `is_active` | `status` | `rascunho|publicado` (ADR-018/ADR-034) |
| `description` | `description` | opcional, exibida no catálogo |
| `metadata` | `metadata` | `jsonb` com default `{}` (ADR-017) |

Justificativa: ADR-018 exige catálogo navegável; o legado resolve navegação com
`order` + `parent_id` e esse é o único ponto em que ele é mais rico que o V7
atual (`theme` é texto plano). Não criar árvore de profundidade ilimitada na
UI: renderizar 2 níveis (equivalência funcional).

### 3.2 Competência como dado de primeira classe

**Portar `reference_date` como `competencia`**, com semântica declarada:

- valor canônico `YYYY-MM` (mensal) com possibilidade de `YYYY-MM-DD` para
  lançamentos pontuais;
- obrigatória em datasets fiscais;
- entra como **coluna da matriz** (`columns`), não como campo solto;
- usada para ordenar, filtrar e compor séries temporais no catálogo.

Justificativa: ADR-018 §"Proveniência/atualização" (`frequencia`, `versao`,
`atualizado_em`) e o backlog `wiki/backlog/modulo-transparencia-fiscal.md` (séries
temporais). Sem competência, o dataset fiscal é uma tabela sem eixo temporal.

### 3.3 Semântica de valores monetários

- preservar a escala 2 e o intervalo `decimal(15,2)`;
- no V7, **string decimal** dentro do `jsonb` (nunca `float` PHP, ao contrário
  de `V2TransparencyItem::$casts`);
- formatação apenas na view/export (`NumberFormatter`), nunca no dado.

Justificativa: ADR-017 (dados desacoplados) e determinismo de export
(ADR-001 citada em `src/modules/Portal/DadosAbertos/Logic/Actions/DadosAbertosExport.php:15-18`).

## 4. O que ADAPTAR

| Elemento legado | Adaptação V7 | Motivo |
|---|---|---|
| Um item = um registro | um dataset por categoria/competência, itens como linhas | ADR-018: dataset é a unidade exportável |
| `description` livre | `summary` (curto) + `description` (longo) | DCAT/INDA |
| `is_active` + soft delete | `status` + `published_at` + `arquivar` | ADR-033/ADR-018 |
| Permissão `read`/`write` | `ver/editar/aprovar/arquivar` | ADR-033 |
| Importador V1 (model Support) | **não portar**: reentrada manual pelo CMS | ADR-042 (sem ETL nem retrocompatibilidade) |
| URL `/transparencia/{slug}` | `/dados-abertos/{slug}`, sem redirect | ADR-042 (sem camada de compatibilidade) |
| Badge "Publicado/Inativo" no detalhe | remover; item público só existe publicado | ADR-018 |
| Seeder como fonte de conteúdo | seed apenas de categorias (dicionário); conteúdo vem do CMS (reentrada manual) | ADR-034 |

## 5. O que REJEITAR

| Elemento | Por que rejeitar | Regra V7 |
|---|---|---|
| CRUD de itens isolados sem export | não é dado aberto; "transparência só no HTML" | ADR-018 §Contexto |
| `metadata` livre sem contrato | quebra interoperabilidade e migração | ADR-017 |
| `slug` gerado no model e imutável | URL deixa de refletir o título; sem `UNIQUE` | ADR-004 (token canônico) + SEO |
| `value` com cast `float` | erro de arredondamento em valores fiscais | ADR-001 |
| Duas tabelas sem repositório/action | regra espalhada em controller/model/view | ADR-013 (DDD-lite) |
| `is_active` booleano | não expressa rascunho/aprovação/publicação | ADR-018/ADR-033 |
| Relação `children` sem escopo | expõe inativo e gera N+1 | ADR-001/performance |
| Bootstrap + partials do CMS | UI do V7 é Tailwind/daisyUI | ADR-009 |
| `v2_uuid()` v4 | V7 usa UUIDv7 no banco | ADR-017 |
| Rota POST para update/delete | V7 usa verbos REST (convenção de `src/modules/Portal/DadosAbertos/Routes/cms.php`) | rotas sem closure (ADR-008) |
| `try/catch` silencioso no SEO | erro engolido; observabilidade zero | ADR-024 |

## 6. O que já está superado (não portar de forma alguma)

| Necessidade do legado | Solução V7 existente |
|---|---|
| Publicar valores no portal | dataset publicado + catálogo |
| Formatar `R$ 1.234,56` | `NumberFormatter`/helper de view |
| Evitar cache obsoleto | `ETag`/304 (ADR-022) |
| Download de dado | `DadosAbertosExport` |
| Consumo por terceiros | API `/api/v1/datasets` (ADR-018 §4) |
| Indexação por buscadores | JSON-LD `Dataset` + sitemap (ADR-027) |
| Permissão por módulo | RBAC de 4 ações (ADR-033) |
| Ativação do módulo | feature flag por dado (ADR-034) |
| Auditoria de publicação | `log_event`/`Telemetry` (ADR-024/035) |
| CSV injection | neutralização no export (`src/modules/Portal/DadosAbertos/Logic/Actions/DadosAbertosExport.php:178-185`) |
| PII | datasets não expõem dados pessoais (ADR-019) |

## 7. Decisão recomendada

**Criar `TransparenciaFiscal` como módulo irmão de `DadosAbertos` no grupo
`Portal`**, não como CRUD novo:

```
src/modules/Portal/
├── DadosAbertos/          (dataset, export, API, catálogo)
└── TransparenciaFiscal/   (categorias fiscais + datasets fiscais + visão)
```

O módulo novo:

1. depende das Actions existentes (`DadosAbertosSave`, `DadosAbertosExport`), sem
   duplicar export;
2. acrescenta apenas `dataset_categories` (árvore) e a visão "por categoria e
   competência";
3. declara `permissions => Permission::ACTIONS` e registra sitemap;
4. não cria tabela de itens: cada item é linha.

Alternativa aceitável (menor esforço): estender `DadosAbertos` com a tabela de
categorias e um filtro de catálogo. Nesse caso, o nome do módulo permanece
`dados-abertos` e a Transparência Fiscal é apenas um **tema** (`theme`).

Critério de escolha: se o órgão precisa de URL própria `/transparencia` e de um
recorte editorial (receitas/despesas), o módulo irmão é o caminho; se o catálogo
único já basta, estender.

## 8. Checklist de porte

| # | Tarefa | Aceite |
|---|---|---|
| 1 | Migration `dataset_categories` (UUIDv7, FK, posição) | `php artisan migrate` idempotente; `down()` preserva backup |
| 2 | Model com escopos `publicadas`/`ordenadas` | árvore de 2 níveis ordenada |
| 3 | `datasets.category_id` + índice | dataset fiscal vinculado |
| 4 | Request com `category_id` e validação de competência | erro claro para `2026-13` |
| 5 | Export reaproveitado | CSV com cabeçalho `item,competencia,valor,observacao`; `ETag` muda ao editar |
| 6 | API expõe `category` | `/api/v1/datasets` com nó de categoria |
| 7 | RBAC | perfil só `ver` não acessa `/cms`; `aprovar` publica |
| 8 | URLs novas | links internos e sitemap apontam para `/dados-abertos`; sem redirect |
| 9 | Sitemap | categorias e datasets publicados listados |
| 10 | Conteúdo V1 | reentrada manual pelo CMS; nenhum ETL no V7 |
| 11 | Testes | portal, export, API, RBAC, competência inválida |
| 12 | Remoção do legado | remover rotas/blades V2 e o importador após a reentrada manual |

## 9. Riscos do porte

| Risco | Mitigação |
|---|---|
| Perder a leitura "por categoria" que o cidadão conhece | catálogo agrupado por categoria + filtro |
| Competência heterogênea nos dados V1 (`publication_date`) | na reentrada manual, normalizar para `YYYY-MM` e registrar a data original em `metadata.legado` |
| Itens V1 em neta (3º nível) | associar à filha de 1º nível na reentrada, guardando o caminho completo em `metadata` |
| Volume de datasets | um por categoria/competência, não um por item |
| Duplicidade de valores | chave natural `(categoria, competencia, item)` validada na Action |
| Links externos | sitemap e links internos atualizados; sem redirect (ADR-042) |
