# Transparência Fiscal — Rotas e Controllers

> Todas as rotas verificadas em `v6/routes/web.php` e `v6/routes/basepro.php`.

---

## 1. Rotas do Portal (público) — `v6/routes/web.php`

Todas estão dentro do grupo `Route::middleware(['check.site.manutention'])`
(`v6/routes/web.php:7`), que bloqueia o portal quando `configurations.key =
'pagina_em_manutencao'` está ativo. Não há autenticação.

### 1.1 Transparência Fiscal (catálogo de links)

| Método | URI | Nome (`->name()`) | Controller@action | Middleware | Linha |
|---|---|---|---|---|---|
| GET | `/transparencia-fiscal` | `transparencia.index` | `Portal\TransparencyController@index` | `check.site.manutention` | 165 |
| GET | `/transparencia` | `transparencia.index` | `Portal\TransparencyController@index` | `check.site.manutention` | 166 |
| GET | `/portal-da-transparencia` | `transparencia.index` | `Portal\TransparencyController@index` | `check.site.manutention` | 167 |
| GET | `/portal-transparencia` | `transparencia.index` | `Portal\TransparencyController@index` | `check.site.manutention` | 168 |
| GET | `/transparencia/categoria/{slug}` | `transparencia.subcategoria` | `Portal\TransparencyController@subcategoria` | `check.site.manutention` | 170 |
| GET | `/transparencia-fiscal/{slug}` | `transparencia.show` | `Portal\TransparencyController@show` | `check.site.manutention` | 172 |
| GET | `/transparencia/{slug}` | `transparencia.show` | `Portal\TransparencyController@show` | `check.site.manutention` | 173 |
| GET | `/portal-da-transparencia/{slug}` | `transparencia.show` | `Portal\TransparencyController@show` | `check.site.manutention` | 174 |
| GET | `/portal-transparencia/{slug}` | `transparencia.show` | `Portal\TransparencyController@show` | `check.site.manutention` | 175 |
| GET | `/portal-da-transparencia/{slug}` | `transparencia.show` | `Portal\TransparencyController@show` | `check.site.manutention` | 176 |

**Total: 4 URIs de índice + 1 de categoria + 5 de detalhe (4 distintas, 1 duplicada).**

Todas as rotas de índice e detalhe compartilham **o mesmo nome de rota**. Consequências:

- `route('transparencia.index')` e `route('transparencia.show', $slug)` geram **sempre a
  última URI registrada** com aquele nome (`/portal-da-transparencia` e
  `/portal-da-transparencia/{slug}`), pois o `RouteCollection` indexa por nome e a última
  gravação sobrescreve a anterior.
- Os links gerados pelo Blade (`portal/components/transparencia-fiscal/item-box.blade.php`)
  usam `route('transparencia.show', $content->slug)` → apontam sempre para
  `/portal-da-transparencia/{slug}`.
- As demais 8 URIs continuam válidas para acessos diretos (links antigos, SEO, material
  impresso), mas **não são geradas por nenhum link interno**.

### 1.2 Relatório Estatístico (LAI/e-SIC)

| Método | URI | Nome | Controller@action | Middleware | Linha |
|---|---|---|---|---|---|
| ANY | `/relatorio-estatistico` | `relatorio-estatistico` | `Portal\TransparencyController@relStatitics` | `check.site.manutention` | 20 |
| POST | `/AjaxGetanswers` | `get.answers` | `Portal\TransparencyController@getAnswers` | `check.site.manutention` | 70 |

`Route::any` aceita GET e POST na mesma URI: o controller lê `?ano=` e `?type=` da query
string. O `?type=` (`xls`, `csv`, `pdf`, `txt`) transforma a resposta em download.

> Não existe rota `/relatorio-estatistico/exportacao` na v6 — ela existia na v5
> (ver `05-REGRAS-E-PORTABILIDADE.md`, seção 6).

### 1.3 Rotas adjacentes que usam as mesmas views do módulo

| Método | URI | Nome | Controller@action | Observação |
|---|---|---|---|---|
| POST | `/informacao-solicitada` | `requested-information` | `Portal\InformationController@store` | Renderiza `transparencia-fiscal.informacao-solicitada` |
| POST | `/informacao-solicitada-ajax` | `requested-information-ajax` | `Portal\InformationController@storeRequestInformacaoAjax` | — |
| ANY | `/ouvidoria` | `ouvidoria.index` | `Portal\OmbudsmanController@getIndex` | Renderiza `transparencia-fiscal.ouvidoria-solicitada` |
| ANY | `/relatorio-estatistico-ouvidoria` | `relatorio-estatistico-ouvidoria` | `Portal\OmbudsmanController@relStatitics` | Renderiza `transparencia-fiscal.relatorio-estatistico-ouvidoria.index` |
| ANY | `/estrutura-organizacional` | `estrutura-organizacional.index` | `Portal\OrganizationalStructureController@index` | Renderiza `transparencia-fiscal.estrutura-organizacional` |
| ANY | `/convenios` | `convenios.index` | `Portal\AgreementController@index` | Renderiza `transparencia-fiscal.convenio` |
| ANY | `/buscar`, `/buscando` | `search`, `search.by.text` | `Portal\PortalController@searchContents`, `@searchingContents` | Injeta `PortalTransparenciaService::search()` na busca geral |

> Essas rotas pertencem aos módulos e-SIC/Ouvidoria/Institucional; aparecem aqui porque
> **compartilham o diretório de views** `portal.layouts.{template}.transparencia-fiscal.*`.

### 1.4 Busca no portal externo de transparência

| Método | URI | Nome | Controller@action | Service |
|---|---|---|---|---|
| ANY | `/buscar` | `search` | `Portal\PortalController@searchContents` | `PortalTransparenciaService@search` |
| ANY | `/buscando` | `search.by.text` | `Portal\PortalController@searchingContents` | `PortalTransparenciaService@search` |

`PortalTransparenciaService::SEARCH_CATEGORY_SLUG = 'portal-transparencia-externo'`.
Quando `portal_transparencia_ativo = 1` e `portal_transparencia_codigo_ente` está
preenchido, a busca geral do portal devolve **links para as páginas do portal fiscal
externo** (33 entradas fixas no catálogo do provedor `elmar`), com o badge
`Portal da Transparência Elmar`.

---

## 2. Rotas do CMS — `v6/routes/basepro.php`

Todas no grupo `Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)`
(`v6/routes/basepro.php:38`), exigem sessão autenticada via o middleware
`check.route.permission` aplicado nos controllers de conteúdo.

| Método | URI completa | Nome | Controller@action | Middleware | Linha |
|---|---|---|---|---|---|
| GET | `/cms/conteudos/transparencia-fiscal` | `listar-conteudo` | `Prefecture\ContentController@genericIndex` | `check.route.permission` | 235 |
| GET | `/cms/conteudos/cadastrar/transparencia-fiscal` | `cadastrar-conteudo` | `Prefecture\ContentController@genericCreate` | `check.route.permission` | 236 |
| GET | `/cms/conteudos/editar/transparencia-fiscal/{id}` | `editar-conteudo` | `Prefecture\ContentController@genericEdit` | `check.route.permission` | 237 |
| GET | `/cms/categorias/transparencia-fiscal` | `listar-categorias` | `Prefecture\CategoryContentController@genericIndex` | `check.route.permission` | 238 |
| GET | `/cms/categorias/cadastrar/transparencia-fiscal` | `cadastrar-categorias` | `Prefecture\CategoryContentController@genericCreate` | `check.route.permission` | 239 |
| GET | `/cms/categorias/editar/transparencia-fiscal/{id}` | `editar-categorias` | `Prefecture\CategoryContentController@genericEdit` | `check.route.permission` | 240 |
| GET | `/cms/datatable/transparencia_fiscal` | `datatable.transparencia_fiscal` | `Prefecture\DatatableController@get_transparencia_fiscal` | `check.logged` (grupo CMS) | 413 |

Rotas genéricas de conteúdo usadas pelo módulo (não exclusivas dele):

| Método | URI | Nome | Controller@action |
|---|---|---|---|
| POST/PUT | `/cms/conteudos` | `conteudo.store` / `conteudo.update` | `Prefecture\ContentController@store` / `@update` |
| DELETE | `/cms/conteudos/{id}` | `categoria-conteudo.destroy` | `Prefecture\ContentController@destroy` |
| POST | `/cms/ajax/conteudos/deleteAll` | `ajax.generic.content.deleteAll` | `Prefecture\ContentController@genericDeleteAll` |
| POST/PUT/DELETE | `/cms/categoria-conteudo/...` | `categoria-conteudo.*` | `Prefecture\CategoryContentController@*` |
| GET/PUT | `/cms/configuracoes/editar/{slug}` | `edit.configuracoes.by.category` | `Prefecture\ConfigurationController@editByCategory` |

> **Não existe** um controller `Prefecture\TransparencyController` no diretório
> `v6/app/Http/Controllers/Prefecture/` (verificado por listagem do diretório). A
> administração do módulo é 100% genérica, via `ContentController`/`CategoryContentController`.
> A seção comentada como `/* Portal da Transparência */` em `v6/routes/basepro.php:541`
> refere-se ao **Mapeamento Cultural** (`mapeamento-cultural`), não a este módulo.

---

## 3. Controllers — API dos métodos

### 3.1 `App\Http\Controllers\Portal\TransparencyController`
Arquivo: `v6/app/Http/Controllers/Portal/TransparencyController.php` (239 linhas).

| Método | Assinatura | Retorno | Observações |
|---|---|---|---|
| `index` | `()` | View `transparencia-fiscal.index` | Monta `$contents` = itens-pastura (objetos `cat_<id>`) + `$contentsData`; usa `$idTransparencia` (fallback 25) |
| `subcategoria` | `($slug)` | View `transparencia-fiscal.subcategorias` | Se a categoria não existe **ou** não é filha da raiz → `redirect(route('transparencia.index'))` |
| `show` | `($slug)` | View `transparencia-fiscal.show` | `$transparencia = Content::where('slug',$slug)->with('linksUnique')->first()`; **se não existir, `$transparencia->title` estoura um erro** (acesso a propriedade de `null`) — ver `08-GAPS-E-DIVIDAS.md` |
| `relStatitics` | `(Request $request)` | View `relatorio-estatistico.index` **ou** download | Incrementa `clicks` da `static_pages`; 8 contagens de status/prazo; exporta se `type` preenchido |
| `getAnswers` | `(Request $request)` | JSON `{request, answers}` | Retorna `response()->json('Ocorreu um erro.', 204)` se o id não existir |
| `export` | `($informationRequests, $type)` | Download | `xls`/`csv` → `Excel::download`; `pdf` → `PDF::loadView('exports.informations-requests')`; demais → TXT |
| `exportTxt` | `($informationRequests, $filename)` | `Response::make` (texto) | **privado**; cabeçalho `Content-Type: plain/txt` |

### 3.2 `App\Http\Controllers\Prefecture\DatatableController@get_transparencia_fiscal`

```php
public function get_transparencia_fiscal()
{
    $transparencia_fiscal = DB::table("transparencia_fiscal")->get();
    return DataTables::of($this->struct_views($transparencia_fiscal, 'transparencia-fiscal'))->make(true);
}
```

`struct_views($data, $permission)` (`v6/app/Http/Controllers/Prefecture/DatatableController.php:504-521`)
adiciona a cada linha: `edit`, `delete`, `active` (restore) e `inactivate`, calculados com
`has_access("{permissão}.edit")`, `has_access("{permissão}.delete")`,
`has_access("{permissão}.restore")` e `has_access("{permissão}.inactivate")`.

> A carga é **integral** (`->get()` sem paginação no servidor): o DataTables pagina no
> navegador. Ver `05-REGRAS-E-PORTABILIDADE.md`.

### 3.3 `App\Http\Controllers\Prefecture\ContentController` — trechos do módulo

| Trecho | Linha | Comportamento |
|---|---|---|
| `genericIndex` | 101 | `transparencia-fiscal` → `returnIndex('Transparência Fiscal', 'transparencia-fiscal')` |
| `genericGenerate` | 179-187 | Carrega `$categoryContentSelect` (subcategorias) e chama `returnData('Transparência Fiscal', 'transparencia-fiscal', 'transparencia', [...], $id)` |
| `store` | 633-634 | Se `category_slug = 'transparencia-fiscal'` e `category_contents_id = 0`, resolve o id da raiz por `CategoryContent::where('slug','transparencia-fiscal')->whereNull('category_contents_id')->first()->id` |
| `store` | 682-690 | **Reordenação**: incrementa em 1 o `order` de todos os itens da mesma categoria com `order >= novo order` |
| `store` | 716-728 | Cria o `links` com `link` sanitizado (`sanitizeLink(normalizeVideoLink(...))`), `alt = title`, `isBlank` |
| `update` | 829-832, 957-961 | Mesma resolução da categoria raiz |
| `update` | 1063-1099 | Atualiza o `links` existente ou cria novo; grava `isBlank` |

### 3.4 `App\Http\Controllers\Prefecture\CategoryContentController` — trechos do módulo

| Trecho | Linha | Comportamento |
|---|---|---|
| `genericCreate` | 237-256 | `case 'transparencia-fiscal'`: carrega a raiz e as categorias irmãs; view `prefecture.category-contents.transparencia-fiscal.create` |
| `genericEdit` | 420-431 | `case 'transparencia-fiscal'`: view `prefecture.category-contents.transparencia-fiscal.edit` |
| `store` | 461-464 | Se o pai é `transparencia-fiscal`, persiste `description` |
| `update` | 595-598 | Idem para update |
| `destroy` | 735-737 | `slug == 'transparencia-fiscal'` → **redireciona** para `cadastrar-categorias` (a raiz não pode ser removida) |

---

## 4. Validação (Form Requests)

### 4.1 `ContentRequest` — case `transparencia-fiscal`
(`v6/app/Http/Requests/ContentRequest.php:107-120`)

| Campo | Regra | Efeito |
|---|---|---|
| `title` | `required|max:120` | Título do item |
| `category_contents_id` | `numeric` | Categoria (0 ⇒ raiz) |
| `icon` | `required` | Classe do ícone |
| `link` | `required|max:2999` | URL |
| `order` | `required` | Posição |
| `status` | `boolean` | Ativo |
| `isBlank` | `boolean` | Abrir em nova aba |
| `referencia` | `nullable|string|max:50` | Referência legal |

> `category_contents_id` **não** é `required` — o controller resolve a raiz quando o valor
> é `0`. Se o campo vier vazio (não numérico), a validação falha.

### 4.2 `CategoryContentRequest`
(`v6/app/Http/Requests/CategoryContentRequest.php:45-48`): quando o slug é
`transparencia-fiscal`, `description` passa a ser `nullable|max:500`.

### 4.3 Sem Form Request no portal
As rotas públicas não usam Form Request: `relStatitics` confia em `$request->ano`
(aceita qualquer string) e `getAnswers` confia em `$request->id` (não validado como
inteiro; a query é feita por `where('id', $request->id)`).

---

## 5. API Resources / Presenters

| Classe | Papel |
|---|---|
| `App\Exports\InformationsRequestsExport` | `FromView` do Maatwebsite; usa a view `exports.informations-requests` |
| `App\Http\Presenters\ContentPresenter` | Presenter de `Content` **e** de `Status`; `textColor()` devolve a classe CSS por status (1 warning, 2 success, 3 danger, 4 primary) |

Não há API Resource JSON dedicado: o Ajax `getAnswers` serializa a collection Eloquent
diretamente (`$answers`), expondo todas as colunas de `answers` (incluindo `archives_id`).

---

## 6. Permissões exigidas

| Recurso | Slug de permissão | Verificação |
|---|---|---|
| Ver a listagem no CMS | `transparencia-fiscal` | `resources.slug = 'transparencia-fiscal'` (`resources.id = 27`, `modulo_id = 23`) |
| Criar item | `transparencia-fiscal.create` | `has_access(['transparencia-fiscal.create'])` em `prefecture/contents/transparencia-fiscal/index.blade.php` |
| Editar item | `transparencia-fiscal.edit` | `has_access(['transparencia-fiscal.edit'])` no DataTable (`struct_views`) |
| Excluir item | `transparencia-fiscal.delete` | `has_access(['transparencia-fiscal.delete'])` |
| Ativar / inativar | `transparencia-fiscal.restore` / `.inactivate` | `struct_views` |
| Menu lateral | `transparencia-fiscal` | `Prefecture\MenuServiceProvider` adiciona o item "Transparência Fiscal" (`/cms/conteudos/transparencia-fiscal`) |

`has_access()` (`v6/app/Library/Helper/appHelpers.php:22-42`): retorna `true` para
`super-admin`; caso contrário delega ao Sentinel (`hasAnyAccess`).

### 6.1 O que o middleware `check.route.permission` realmente exige

O middleware (`v6/app/Http/Middleware/CheckRoutePermission.php`) não lê o slug do
módulo: ele deriva a permissão do **nome da rota** e do **segmento da URL**.

| URL | Segmento derivado | Permissão exigida |
|---|---|---|
| `/cms/conteudos/transparencia-fiscal` | `conteudos.index` | `listar-conteudo` **ou** `conteudos.index` |
| `/cms/conteudos/cadastrar/transparencia-fiscal` | `transparencia-fiscal.create` | `cadastrar-conteudo` **ou** `transparencia-fiscal.create` |
| `/cms/conteudos/editar/transparencia-fiscal/{id}` | `transparencia-fiscal.edit` | `editar-conteudo` **ou** `transparencia-fiscal.edit` |
| `/cms/categorias/transparencia-fiscal` | `categorias.index` | `listar-categorias` **ou** `categorias.index` |
| `/cms/categorias/cadastrar/transparencia-fiscal` | `transparencia-fiscal.create` | `cadastrar-categorias` **ou** `transparencia-fiscal.create` |
| `/cms/datatable/transparencia_fiscal` | — | **Qualquer usuário logado** (`datatable` está em `$openRoutes`) |

Sem permissão, o middleware **redireciona para a rota anterior** com o flash
"Você não tem autorização para acessar esta funcionalidade!" (não retorna 403).
Deslogado, redireciona para `route('login')`.

> Consequência: a URL de edição de conteúdo **não** exige `transparencia-fiscal.edit` —
> exige `editar-conteudo`. As flags por item (`has_access('transparencia-fiscal.edit')`)
> são verificadas no JavaScript e no `struct_views`, ou seja, a autorização fina é
> apenas de apresentação, não de backend.

> No banco inspecionado existe **apenas** o `resources` de slug `transparencia-fiscal`
> (id 27). As variações `.create`, `.edit`, `.delete`, `.restore`, `.inactivate` são
> verificadas pelo mesmo slug pelo Sentinel; a criação delas é feita fora deste módulo.

---

## 7. Breadcrumbs — `v6/routes/breadcrumbs.php`

| Nome | Uso |
|---|---|
| `transparencia-fiscal` (linha 24) | Pai de `static-transparencia`; aponta para `route('transparencia.index')` |
| `transparencia-fiscal-show` (linha 30) | Registrado, mas **não é usado** pelas views atuais (que usam `acesso-rapido` e `static-transparencia`) |
| `static-transparencia` (linha 48) | Breadcrumb do relatório estatístico: pai `transparencia-fiscal` + título da `static_pages` |
| `acesso-rapido` (linha 229) | Usado pelas views `index` e `show` da transparência |
