# Arquitetura

## 1. Fluxo da requisição

### 1.1 Portal público

```
GET /transparencia
  │
  ├─ [middleware global] web + MaintenanceMiddleware
  │     (V2/Core/Providers/V2ServiceProvider.php:515-519)
  ├─ V2\Modules\DadosAbertos\TransparenciaFiscal\Controllers\Portal\TransparencyPortalController@index
  │     ├─ V2TransparencyCategory::active()->root()->ordered()
  │     │     ->with(['children', 'items' => fn($q) => $q->active()])
  │     ├─ [$child->items lazy na view]  ← N+1
  │     └─ view('v2-transparenciafiscal::portal.index')
  │
  └─ Response HTML (layout v2-portal::app-layout)

GET /transparencia/{slug}
  ├─ TransparencyPortalController@show
  │     ├─ V2TransparencyItem::active()->where('slug', $slug)->firstOrFail()
  │     ├─ app(SEOService::class)->setTitle($item->title)   (try/catch)
  │     └─ view('v2-transparenciafiscal::portal.show')
```

### 1.2 CMS

```
GET|POST /cms/v2/transparencia[...]
  │
  ├─ web
  ├─ module.active:TransparenciaFiscal      (V2/Core/Providers/V2ServiceProvider.php:521-524)
  │     └─ ModuleAccessMiddleware → ModuleFlagService::isActive
  ├─ v2.authenticate → CmsAuthenticate (Sentinel)
  ├─ v2.permission:transparencia,read|write → V2PermissionMiddleware
  │     └─ resolve pelo active_pattern do ModuleProvider (parâmetros são ignorados)
  ├─ TransparencyCmsController@index|create|store|edit|update|destroy
  │     ├─ v2_can('transparencia', 'write')  (checagem redundante dentro do controller)
  │     ├─ TransparencyRequest (store/update)
  │     ├─ V2TransparencyItem / V2TransparencyCategory (Eloquent)
  │     ├─ V2AuditLogger::activity(model, evento, props)
  │     ├─ CmsActionLogger::info('transparencia', ação, contexto)
  │     └─ redirect()->route('v2.transparencia.items.index')
  └─ view('v2-transparenciafiscal::cms.*')
```

## 2. Descoberta e registro do módulo

O `V2ServiceProvider` varre `V2/Modules/<Grupo>/<Módulo>`
(`V2/Core/Providers/V2ServiceProvider.php:93-127`) e, para este módulo:

| Recurso | Regra | Resultado |
|---|---|---|
| Provider | `V2\Modules\DadosAbertos\TransparenciaFiscal\ModuleProvider` | registra config no container `v2.modules.configs` |
| Views | `loadViewsFrom(<module>/Views, 'v2-' . strtolower(ModuleName))` | namespace `v2-transparenciafiscal::` |
| Migrations | `loadMigrationsFrom(<module>/Migrations)` | 4 migrations |
| Rotas web | `V2/Modules/DadosAbertos/TransparenciaFiscal/Routes/web.php`, namespace `...\Controllers` | `/transparencia`, `/transparencia/{slug}` |
| Rotas CMS | `V2/Modules/DadosAbertos/TransparenciaFiscal/Routes/cms.php` + `module.active:TransparenciaFiscal` | `/cms/v2/transparencia*` |
| Rotas API | ausente | — |

Chave canônica do módulo = **nome do diretório**: `TransparenciaFiscal`. É a
chave usada em `v2_roles.permissions_v2` e pelo `V2PermissionMiddleware`.
O alias `transparencia → TransparenciaFiscal` existe para o legado de
`v2_can()` (`V2/Core/Services/PermissionService.php:28`), não para o middleware.

## 3. Classes envolvidas

| Classe | Caminho | Papel |
|---|---|---|
| `ModuleProvider` | `V2/Modules/DadosAbertos/TransparenciaFiscal/ModuleProvider.php` | título, ícone, posição 34, `active_pattern`, rota inicial |
| `TransparencyCmsController` | `V2/Modules/DadosAbertos/TransparenciaFiscal/Controllers/Admin/TransparencyCmsController.php` | CRUD de itens, filtros, auditoria |
| `TransparencyPortalController` | `V2/Modules/DadosAbertos/TransparenciaFiscal/Controllers/Portal/TransparencyPortalController.php` | listagem e detalhe públicos |
| `TransparencyRequest` | `V2/Modules/DadosAbertos/TransparenciaFiscal/Requests/TransparencyRequest.php` | validação de item |
| `V2TransparencyCategory` | `V2/Modules/DadosAbertos/TransparenciaFiscal/Models/V2TransparencyCategory.php` | árvore de categorias, escopos `active/ordered/root` |
| `V2TransparencyItem` | `V2/Modules/DadosAbertos/TransparenciaFiscal/Models/V2TransparencyItem.php` | item fiscal, escopos `active/ordered/search` |
| `TransparenciaSeeder` | `V2/Modules/DadosAbertos/TransparenciaFiscal/Seeds/TransparenciaSeeder.php` | dicionário de categorias + demo opcional |
| `BaseController` | `V2/Core/Controllers/BaseController.php` | base abstrata (Laravel) |
| `V2AuditLogger` | `V2/Core/Services/V2AuditLogger.php` | auditoria persistida (`activity()` na linha 635) |
| `CmsActionLogger` | `V2/Core/Logging/CmsActionLogger.php` | envelope `module/action/target_id` |
| `V2PermissionMiddleware` | `V2/Core/Middleware/V2PermissionMiddleware.php` | RBAC por `active_pattern` |
| `ModuleAccessMiddleware` | `V2/Core/Middleware/ModuleAccessMiddleware.php` | flag de ativação do módulo |
| `PermissionService` | `V2/Core/Services/PermissionService.php` | resolução `none|read|write` + aliases |
| `SEOService` | `V2/Services/SEO/Services/SEOService.php` | título/meta do detalhe |
| `TransparenciaFiscalLegacyImporter` | `V2/Modules/Support/MigracaoLegada/Services/TransparenciaFiscalLegacyImporter.php` | carga V1 (módulo Support) |

## 4. Configuração declarada

```php
// V2/Modules/DadosAbertos/TransparenciaFiscal/ModuleProvider.php:12-15
public function getConfig():array
{
    return['title' => 'Transparência Fiscal','description' => 'Dados de transparência fiscal do portal.','icon' => 'bi bi-eye','position' => 34,'active_pattern' => 'cms/v2/transparencia*','route' => 'v2.transparencia.items.index','group' => 'Conteudo'];
}
```

Observações:

- `group => 'Conteudo'` é **sobrescrito** pelo diretório físico:
  `$config['group'] = $groupName;` (`V2/Core/Providers/V2ServiceProvider.php:106`) ⇒ o grupo real
  é `DadosAbertos`.
- `active_pattern` é o que o `V2PermissionMiddleware` usa (`:40-48`). O
  middleware de rota recebe `transparencia,read`, mas **não lê esses
  parâmetros** — a assinatura `handle($request, Closure $next)` não os recebe.

## 5. Dependências externas

| Dependência | Uso no módulo |
|---|---|
| `illuminate/database` (Eloquent) | models, migrations |
| `illuminate/validation` | `TransparencyRequest` |
| `cartalyst/sentinel` | autenticação CMS (`Sentinel::check` no middleware) |
| PostgreSQL (`jsonb`, `ILIKE`) | schema e busca |
| Bootstrap 5 + Bootstrap Icons | blades (via layouts do CMS/portal) |

Nenhum pacote específico: sem Excel, sem PDF, sem upload, sem fila.

## 6. Limites do módulo (fronteiras)

```
┌────────────────────────── V2/Modules/DadosAbertos ──────────────────────────┐
│                                                                             │
│  TransparenciaFiscal                         Operators                      │
│  ├── Models (2)                              ├── Models (2)                 │
│  ├── Controllers (2)                         ├── Controllers (2)            │
│  ├── Views (5)                               ├── Views (5)                  │
│  ├── Requests (1)                             ├── Requests (1)               │
│  ├── Seeds (1)                               ├── Imports + Service         │
│  └── Migrations (4)                          └── Migrations (4)             │
│                                                                             │
│  Sem código compartilhado entre os dois: cada um tem seu próprio CRUD,      │
│  seus blades e seu namespace de view. Não há Service/DTO/Action comum.      │
└─────────────────────────────────────────────────────────────────────────────┘
        ▲                                            ▲
        │ usa (não é usado)                          │
  V2/Core (RBAC, auditoria, flags)            V2/Services/SEO
  V2/Modules/Support/MigracaoLegada           V2/layouts/*
```

- **Entrada**: nenhum outro módulo escreve nas tabelas de transparência, exceto
  `MigracaoLegada` (importador) e `LegacyV1FixtureFactory` (testes).
- **Saída**: nenhum módulo lê `v2_transparency_*`. A home usa um painel
  financeiro próprio (`V2/Modules/Institucional/BasicPage/Views/portal/homepage/partials/dashboard-financeiro.blade.php`),
  sem consultar este módulo.
- **Analytics** mapeia a URL `/transparencia` para o rótulo "Transparência"
  (`V2/Modules/Infraestrutura/Analytics/Controllers/AnalyticsCmsController.php:223`),
  acoplamento apenas textual.

## 7. Decisões arquiteturais implícitas

| Decisão | Evidência | Consequência |
|---|---|---|
| Duas tabelas independentes, sem repositório | controllers chamam Eloquent direto | fácil de ler; impossível reaproveitar consulta no portal e no CMS |
| Hierarquia por `parent_id` sem FK | migration de categorias não declara `foreign` | órfãos possíveis após exclusão |
| Item tem FK `ON DELETE SET NULL` | `2026_07_17_000002_...:29` | excluir categoria não apaga item; item fica "sem categoria" |
| Portal usa eager load parcial | `V2/Modules/DadosAbertos/TransparenciaFiscal/Controllers/Portal/TransparencyPortalController.php:17-19` | N+1 em `children.items` |
| Permissão checada duas vezes | middleware + `abort(403)` no controller | defensivo, porém redundante |
| Sem camada de serviço | nenhum `Services/` no módulo | regra de slug/ordenação mora no model |
