# Gaps e Dívidas

## 1. Bugs confirmados

### B1 — Item inativo aparece no portal (categoria filha)

```php
// V2/Modules/DadosAbertos/TransparenciaFiscal/Controllers/Portal/TransparencyPortalController.php:17-19
$categories = V2TransparencyCategory::active()->root()->ordered()->with(['children','items' => function ($q) {
    $q->active();
}])->get();
```

O escopo `active()` é aplicado apenas à relação `items` da **raiz**. A view
itera `$child->items` (`V2/Modules/DadosAbertos/TransparenciaFiscal/Views/portal/index.blade.php:54`) sem filtro algum.
Efeito: desativar um item de categoria filha **não** o remove da página
pública. Violação de expectativa do operador e risco de exposição de dado
desatualizado.

### B2 — Não é possível desativar um item pelo formulário de edição

`V2/Modules/DadosAbertos/TransparenciaFiscal/Views/cms/edit.blade.php:65-66` renderiza o checkbox sem campo oculto:

```blade
<input type="checkbox" class="form-check-input" id="is_active" name="is_active" value="1"
       {{ old('is_active', $item->is_active) ? 'checked' : '' }}>
```

Desmarcar e salvar **não envia** `is_active`; `TransparencyRequest` não tem
`required`/`sometimes` para o campo; `update($r->validated())` recebe o array
sem a chave e mantém o valor anterior (`V2/Modules/DadosAbertos/TransparenciaFiscal/Controllers/Admin/TransparencyCmsController.php:98`).
Resultado: item só pode ser desativado por SQL ou por um `store` novo com o
checkbox desmarcado — que também não envia o campo (default do banco = `true`).

### B3 — Renomear item não atualiza a URL

O model só tem `static::creating` (`V2/Modules/DadosAbertos/TransparenciaFiscal/Models/V2TransparencyItem.php:63-72`). Após
`update(['title' => 'Novo título'])`, o slug continua o antigo. Links antigos
seguem funcionando, mas a URL deixa de refletir o título — inconsistente com o
model de categoria, que tem `static::updating` (`V2/Modules/DadosAbertos/TransparenciaFiscal/Models/V2TransparencyCategory.php:71-77`).

### B4 — Slug de item sem constraint `UNIQUE`

`2026_08_31_000001_...:17-18` cria `nullable` + índice comum. Nada impede dois
itens com o mesmo slug (importador ou script). `show()` usa
`where('slug',...)->firstOrFail()`, servindo o primeiro encontrado.

### B5 — Categoria com mesmo nome em ramos distintos falha

`slug` é `unique` global e derivado de `Str::slug(name)`. Duas filhas "Outras"
em raízes diferentes violam a constraint. O seeder contorna com slugs
artificiais (`outras-receitas`), mas a UI não permite criar categorias e,
portanto, não há tratamento de erro.

### B6 — N+1 no portal e no CMS

- Portal: `$child->items` lazy por filha (ver B1).
- CMS `index`: `$row->category->name` por linha (`V2/Modules/DadosAbertos/TransparenciaFiscal/Views/cms/index.blade.php:7-9`), sem
  `with('category')` no controller (`V2/Modules/DadosAbertos/TransparenciaFiscal/Controllers/Admin/TransparencyCmsController.php:39-41`).

### B7 — Ordem dos itens no portal é indefinida

A view usa `$cat->items` / `$child->items` sem `orderBy`. O escopo `ordered()`
existe (`reference_date DESC`) mas **não é usado** no portal. Dependendo do
plano do Postgres, a ordem muda entre consultas.

## 2. Funcionalidades incompletas ou mortas

| # | Item | Situação |
|---|---|---|
| I1 | CRUD de categorias | inexistente; só seeder/importador |
| I2 | Hierarquia de 3+ níveis | schema e importador criam; portal renderiza 2 |
| I3 | Anexos | nenhum campo/tabela; `metadata.legado.arquivos` nunca exibido |
| I4 | Exportação CSV/JSON/XML | inexistente |
| I5 | API pública | inexistente |
| I6 | Busca no portal | inexistente (só no CMS) |
| I7 | Paginação no portal | inexistente |
| I8 | Filtro por exercício/competência | inexistente |
| I9 | Totalização por categoria | inexistente |
| I10 | `metadata` | gravado pelo importador, nunca lido |
| I11 | `TransparencyRequest::$id` | código morto (`:21`) |
| I12 | `is_active` no formulário de store | sempre `1`/ausente; default do banco decide |
| I13 | SEO | apenas `setTitle`; sem description, canonical nem schema.org |
| I14 | Sitemap / busca global | módulo não indexa em `v2_public_search_index` |

## 3. Riscos de dados

| # | Risco | Detalhe |
|---|---|---|
| D1 | Item importado pode ficar invisível | o importador recria a cadeia V1 completa (raiz → ... → direta). Se o item pertence a uma **neta**, `v2_transparency_category_id` aponta para o 3º nível; o portal só desce 1 nível ⇒ item nunca renderizado |
| D2 | `value` nulo na importação | perda declarada (`V2/Modules/Support/MigracaoLegada/Services/TransparenciaFiscalLegacyImporter.php:141`); relatórios fiscais importados não têm valor |
| D3 | HTML→texto plano | `resolveDescription` descarta formatação, tabelas e links do conteúdo V1 |
| D4 | Arquivos não exibidos | PDFs/planilhas copiados para a galeria ficam órfãos do ponto de vista do usuário |
| D5 | Duplicidade de lançamentos | nenhuma constraint em `(title)`, `(reference_date)` ou `(category, reference_date)` |
| D6 | `value float` | soma e formatação sujeitas a erro de ponto flutuante |
| D7 | Órfãos por categoria | `ON DELETE SET NULL` + soft delete do item não limpam categorias vazias |
| D8 | `order` sem índice | ordenação por `order, name` faz sort em memória em tabelas grandes (mundo real: pequenas) |

## 4. Dívidas técnicas

| # | Dívida | Custo |
|---|---|---|
| T1 | Duas checagens de permissão (middleware + controller + Request) | 3 pontos de manutenção |
| T2 | Log de auditoria em `index` | ruído no sink |
| T3 | Formulários create/edit duplicados | correção em dobro |
| T4 | Sem camada de serviço/action | regra de slug/ordenação presa ao Eloquent |
| T5 | `ModuleProvider` declara `group => 'Conteudo'` que é sobrescrito | configuração enganosa |
| T6 | Parâmetros `v2.permission:transparencia,read` ignorados pelo middleware | falsa sensação de configuração |
| T7 | Zero testes do módulo | regressões invisíveis (B1-B7) |
| T8 | Rota CMS usa POST para update/delete | fora do padrão REST (herança 5.5) |

## 5. Perguntas em aberto (negócio)

1. **A granularidade é item ou dataset?** O V7 exige dataset legível por
   máquina. Um item isolado (uma linha) justifica um dataset? A resposta
   proposta é **não**: um dataset por categoria/competência, com os itens como
   linhas.
2. **Quais categorias são obrigatórias por lei?** Receitas, despesas, licitações,
   contratos, servidores, diárias, obras — o módulo atual cobre apenas as duas
   primeiras. O escopo LAI precisa ser fechado antes do porte.
3. **Competência é mensal, diária ou por exercício?** Hoje é `date` livre. O
   padrão de contas públicas é mensal (`YYYY-MM`), com fechamento anual.
4. **Quem aprova a publicação?** O V7 tem ação `aprovar` (ADR-033); no legado
   qualquer `write` publica.
5. **Anexos são obrigatórios?** A LAI exige o dado bruto; o legado só tem PDF na
   galeria.
6. **Valores negativos?** `min:0` impede estornos/retificações negativas.
7. **Quem faz a curadoria da reentrada manual?** A ADR-042 descarta ETL e importador no V7;
   o conteúdo de transparência precisa ser reentrado e revisado pela equipe editorial.

## 6. Mapa entre versões

| Comportamento | V1 (`app/Http/Controllers/Portal/TransparencyController.php`) | V2 (este módulo) | V7 (proposto) |
|---|---|---|---|
| Fonte de dados | `contents` + `category_contents` | `v2_transparency_*` | `dados_abertos.datasets` + `dataset_categories` |
| Hierarquia | N níveis | 2 renderizados | N níveis, render 2 |
| Valores | ausentes | `decimal(15,2)` | coluna `valor` na matriz `jsonb` |
| Competência | `publication_date` | `reference_date` | coluna `competencia` |
| Anexos | `archives` | nenhum | distribuição do dataset (Media/S3) |
| Export | nenhum | nenhum | CSV/JSON/XML + ETag |
| API | nenhum | nenhum | `/api/v1/datasets` |
| Permissão | — | `read`/`write` | `ver/editar/aprovar/arquivar` |
| Slug | `contents.slug` | gerado na criação | dado único e editável |
| Ativação | status do conteúdo | `is_active` | `status` + `published_at` |
| Auditoria | logs administrativos | `V2AuditLogger` | nativa (ADR-035) |
| Testes | nenhum específico | nenhum | suíte dedicada |

## 7. Prioridade de correção (se o legado continuar em produção)

| Ordem | Correção | Impacto |
|---|---|---|
| 1 | `children.items` com `active()` + eager load | corrige exposição de dado inativo e N+1 |
| 2 | campo oculto `is_active=0` no checkbox | torna a desativação possível |
| 3 | `UNIQUE` em `slug` + atualização no update | integridade de URL |
| 4 | CRUD mínimo de categorias | operação sem seeder |
| 5 | export CSV + rota estável | conformidade LAI mínima |
| 6 | testes P1-P6, C1-C6 | trava as correções |
