# Relatório Estatístico da Ouvidoria — Rotas e Controllers

> Todas as rotas abaixo são da `v5`. A v6 mantém apenas as rotas de **portal**
> listadas na seção 3 (com implementação diferente — ver `08-GAPS-E-DIVIDAS.md`).

---

## 1. Rotas do CMS (`v5/routes/basepro.php`)

Todas ficam dentro do grupo

```php
Route::group(['prefix' => 'cms', 'before' => 'csrf'], function () { /* ... */ });
```

(`v5/routes/basepro.php:35`), portanto herdam o token CSRF e o prefixo `/cms`.

| Método | URI | Nome da rota | Controller@action | Middleware | Linha |
|---|---|---|---|---|---|
| GET | `/cms/relatorios-estatisticos-ouvidoria` | `relatorio-estatistico-ouvidoria-cms.index` | `Prefecture\RelatorioEstatisticoOuvidoriaController@index` | `check.logged` | 339 |
| GET | `/cms/relatorios-estatisticos-ouvidoria/cadastrar` | `relatorio-estatistico-ouvidoria-cms.create` | `Prefecture\RelatorioEstatisticoOuvidoriaController@create` | `check.logged` | 340 |
| GET | `/cms/relatorios-estatisticos-ouvidoria/editar/{id}` | `relatorio-estatistico-ouvidoria-cms.edit` | `Prefecture\RelatorioEstatisticoOuvidoriaController@edit` | `check.logged` | 341 |
| PUT | `/cms/relatorios-estatisticos-ouvidoria/atualizar/{id}` | `relatorio-estatistico-ouvidoria-cms.update` | `Prefecture\RelatorioEstatisticoOuvidoriaController@update` | (herdado do grupo) | 342 |
| POST | `/cms/relatorios-estatisticos-ouvidoria` | `relatorio-estatistico-ouvidoria-cms.store` | `Prefecture\RelatorioEstatisticoOuvidoriaController@store` | (herdado do grupo) | 343 |
| DELETE | `/cms/relatorios-estatisticos-ouvidoria/excluir/{id}` | `relatorio-estatistico-ouvidoria-cms.destroy` | `Prefecture\RelatorioEstatisticoOuvidoriaController@destroy` | (herdado do grupo) | 344 |

### 1.1 Endpoint Ajax (DataTable)

| Método | URI | Nome da rota | Controller@action | Linha |
|---|---|---|---|---|
| GET | `/cms/datatable/relatorio-estatistico-ouvidoria` | `datatable.relatorio-estatistico-ouvidoria-cms` | `Prefecture\DatatableController@get_relatorio_estatistico_ouvidoria` | 481 |

O grupo do DataTable é `Route::group(['prefix' => 'datatable', 'as' => 'datatable.'], ...)`
(`v5/routes/basepro.php:409`). A URL é montada no JS genérico
`v5/public/assets/js/custom/utilities/table.js` (`ajax.url: '/cms/datatable/' + url`).

Implementação (`v5/app/Http/Controllers/Prefecture/DatatableController.php:461-470`):

```php
public function get_relatorio_estatistico_ouvidoria()
{
    $relatorios = DB::table('relatorios_estatisticos_ouvidoria')->whereNull('deleted_at')->orderBy('ano', 'desc')->get();
    $data = [];
    foreach ($relatorios as $index => $rel) {
        $rel->value = $index + 1;
        $data[] = $rel;
    }
    return DataTables::of($this->struct_views($data, 'relatorio-estatistico-ouvidoria-cms'))->make(true);
}
```

`struct_views()` (`v5/app/Http/Controllers/Prefecture/DatatableController.php:508-524`) injeta
em cada linha os campos `edit`, `delete`, `active` e `inactivate` conforme `has_access()`, e é
o que habilita/desabilita os botões de ação na tela.

---

## 2. Rotas do portal (`v5/routes/web.php`)

Todo o arquivo está dentro de `Route::middleware(['check.site.manutention'])->group(...)`
(`v5/routes/web.php:5`).

| Método | URI | Nome da rota | Controller@action | Linha |
|---|---|---|---|---|
| ANY | `/relatorio-estatistico-ouvidoria` | `relatorio-estatistico-ouvidoria` | `Portal\OmbudsmanController@relStatitics` | 32 |
| ANY | `/relatorio-estatistico-ouvidoria/exportacao` | `relatorio-estatistico-ouvidoria.exportacao` | `Portal\OmbudsmanController@exportRelStatitics` | 33 |
| POST | `/ajax/get-answers/ouvidoria` | `get.ombudsman.answers` | `Portal\OmbudsmanController@getOmbudsmanAnswers` | 66 |

> `Route::any` aceita GET/POST — o filtro de ano é enviado por **GET** (formulário da view)
> e também via `?ano=`.

Rotas irmãs (mesmo controller ou domínio), para não confundir:

| URI | Nome | Controller |
|---|---|---|
| `/relatorio-estatistico` | `relatorio-estatistico` | `Portal\TransparencyController@relStatitics` (LAI) |
| `/relatorio-estatistico/exportacao` | `relatorio-estatistico.exportacao` | `Portal\TransparencyController@exportRelStatitics` |

---

## 3. Controllers

### 3.1 `Prefecture\RelatorioEstatisticoOuvidoriaController` (CMS)

Arquivo: `v5/app/Http/Controllers/Prefecture/RelatorioEstatisticoOuvidoriaController.php`

| Método | Retorno | Comportamento |
|---|---|---|
| `index()` | `View` | Renderiza `prefecture.relatorio-estatistico-ouvidoria.index` (AdminLTE) |
| `create()` | `View` | Renderiza `prefecture.relatorio-estatistico-ouvidoria.create` |
| `store(RelatorioEstatisticoOuvidoriaRequest)` | `RedirectResponse` | Valida, salva o PDF (se houver) e cria o registro; `flash(...)->success()`; redireciona para `relatorio-estatistico-ouvidoria-cms.index`; em exceção, `flash($e->getMessage())->error()` e `redirect()->back()` |
| `edit($id)` | `View` | `RelatorioEstatisticoOuvidoria::findOrFail($id)` e renderiza `...edit` |
| `update(RelatorioEstatisticoOuvidoriaRequest, $id)` | `RedirectResponse` | Mesmo fluxo de `store`, com `$relatorio->update($data)` |
| `destroy($id)` | `RedirectResponse` | `findOrFail($id)->delete()` (soft delete) com flash de sucesso/erro |

Trecho de referência do `store` (upload):

```php
$data = $request->only('ano', 'descricao');

if ($request->hasFile('arquivo')) {
    $path = $this->saveUpload($request->file('arquivo'), 'docs', null, null, false);
    $data['arquivo'] = "docs/{$path}";
}

RelatorioEstatisticoOuvidoria::create($data);
```

`saveUpload()` (`v5/app/Http/Traits/FileTrait.php:50-66`) grava no **disco `public`**
(`v5/config/filesystems.php`: `'default' => env('FILESYSTEM_DRIVER', 'public')`), ou seja
`storage/app/public/docs/<uniqid><5 chars>.<ext>`, com nome gerado por
`getFileName()` (`uniqid('', false) . str_random(5) . '.' . extensão original`).
O 5º argumento `false` desliga a compressão de imagem (irrelevante para PDF, mas
significa que `compress()` não é chamado).

### 3.2 `Prefecture\DatatableController@get_relatorio_estatistico_ouvidoria` (CMS/Ajax)

Ver implementação na seção 1.1. É o único ponto que alimenta o DataTable do CMS.

### 3.3 `Portal\OmbudsmanController@relStatitics` (portal)

Arquivo: `v5/app/Http/Controllers/Portal/OmbudsmanController.php:283-410`

Fluxo (detalhado em `01-ARQUITETURA.md`):

1. Carrega a `static_pages` `relatorio-estatistico-ouvidoria`; se não existir, define
   `$title = 'Página não encontrada'` e `$null = true` (**a view continua sendo renderizada**
   e acessa `$content->layout` — ver `08-GAPS-E-DIVIDAS.md`).
2. Incrementa `clicks` da página estática.
3. Carrega a listagem de solicitações do ano filtro.
4. Calcula 8 contadores de prazo (4 status × dentro/fora do prazo).
5. Monta `$datas` (anos disponíveis) carregando **todas** as solicitações.
6. Lê a configuração `full_list_requests_disabled`.
7. Carrega `$relatorios` (PDFs cadastrados no CMS), filtrando por `ano` quando informado.
8. Calcula `$porTipo`, `$porAssunto`, `$porGenero` com `DB::table` + `DB::raw('count(*) as total')`.
9. Renderiza `portal.layouts.{template}.transparencia-fiscal.relatorio-estatistico-ouvidoria.index`.

### 3.4 `Portal\OmbudsmanController@exportRelStatitics` (portal)

Arquivo: `v5/app/Http/Controllers/Portal/OmbudsmanController.php:414-431`
(texto em `exportOmbudsmanRequestsTxt`, linhas 433-457)

| `?type=` | Formato gerado | Implementação |
|---|---|---|
| `pdf` | `ouvidoria-relatorio-estatistico.pdf` | `PDF::loadView('exports.ombudsman-requests', ...)` |
| `txt` | `ouvidoria-relatorio-estatistico.txt` | `exportOmbudsmanRequestsTxt()` (texto alinhado, com protocolo e nome **mascarados**) |
| `csv` | `ouvidoria-relatorio-estatistico.csv` | `Excel::download(new OmbudsmanRequestsExport(...), $filename)` |
| qualquer outro (inclusive `xls`) | `ouvidoria-relatorio-estatistico.xlsx` | idem, via `OmbudsmanRequestsExport` (view `exports.ombudsman-requests`) |

> Atenção: o botão do portal dispara `xls`, que **não** é tratando explicitamente e
> resulta em `.xlsx` (nome do arquivo) — ver `08-GAPS-E-DIVIDAS.md`.

### 3.5 `Portal\OmbudsmanController@getOmbudsmanAnswers` (portal/Ajax)

Arquivo: `v5/app/Http/Controllers/Portal/OmbudsmanController.php:459-497`.
Recebe `{_token, id}` por POST e devolve JSON:

```json
{
  "request": { "protocol": "...", "mask_description": "...", "status_id": 2 },
  "answers": [ { "id": 1, "answer": "...", "created_at": "..." } ]
}
```

O nome do solicitante **não** é devolvido (máscara aplicada no model), e a descrição vem de
`mask_description`.

---

## 4. Form Request / validação

`v5/app/Http/Requests/RelatorioEstatisticoOuvidoriaRequest.php` (namespace `App\Http\Requests`):

| Campo | Regra | Mensagem |
|---|---|---|
| `ano` | `required|string|max:10` | "O ano é obrigatório." |
| `descricao` | `required|string|max:255` | "A descrição é obrigatória." |
| `arquivo` | `nullable|file|mimes:pdf|max:20480` (20 MB) | "O arquivo deve ser um PDF." / "O arquivo não pode ultrapassar 20MB." |

Observações:

- `authorize()` retorna `true` — **não há** autorização no Request; a proteção vem do
  middleware `check.logged` (apenas nas rotas GET) e dos botões condicionais.
- A mesma regra vale para `store` e `update` (a variável `$isUpdate` produz a mesma regra
  nos dois casos — código morto).
- A view restringe `ano` a 4 dígitos via JavaScript, mas o servidor aceita até 10
  caracteres; `descricao` é validada em 255 enquanto a coluna é `text` (sem limite real).
- Não há validação de unicidade de `ano`: vários relatórios podem ter o mesmo ano.

---

## 5. API Resources / Presenter / saída

- O CMS não usa API Resource: a saída é o JSON do Yajra DataTables (`struct_views` + colunas).
- O portal não expõe JSON próprio; os agregados são injetados no Blade via `@json($porTipo)`,
  `@json($porAssunto)`, `@json($porGenero)` (`v5/resources/views/portal/components/relatorio-estatistico-ouvidoria/charts-js.blade.php`).
- A exportação usa `OmbudsmanRequestsExport` (`FromView`), renderizando
  `v5/resources/views/exports/ombudsman-requests.blade.php`.

---

## 6. Permissões exigidas

| Verificação no código | Origem | Situação na v5 |
|---|---|---|
| `has_access(['relatorio-estatistico-ouvidoria-cms.create'])` — botão "Cadastrar" | `v5/resources/views/prefecture/relatorio-estatistico-ouvidoria/index.blade.php` | Slug composto **não existe** na tabela `resources` (que tem `ouvidoria`, id 72) nem na tabela `permissions` (8 verbos genéricos). Só passa para o papel `super-admin`, pelo atalho de `has_access()` (`v5/app/Library/Helper/appHelpers.php:22-41`) |
| `relatorio-estatistico-ouvidoria-cms.edit` / `.delete` — botões por linha | `struct_views()` + `createBTN()` em `v5/public/assets/js/custom/utilities/table.js` | Mesma limitação acima |
| `has_access(['ouvidoria.index'])` — item de menu "Relatório Estatístico" | `v5/app/Providers/MenuServiceProvider.php:137` | Menu exibido para quem tem acesso à Ouvidoria |
| `check.logged` — rotas GET do CMS | `v5/app/Http/Middleware/CheckLogged.php` | Exige sessão Sentinel; senão redireciona para `dashboard.index` |

> As rotas de escrita (`store`, `update`, `destroy`) **não** têm `check.logged` explícito e
> **não** usam o middleware `check.route.permission` — a proteção real depende do
> `before => 'csrf'` do grupo e do fato de a sessão do CMS já existir. Ver `08-GAPS-E-DIVIDAS.md`.

---

## 7. Breadcrumbs

A view do portal usa
`Breadcrumbs::view('portal.layouts.modelo1.layout.breadcrumb', 'static-transparencia', $content)`
(`v5/routes/breadcrumbs.php:48-53`): Home › Transparência Fiscal › título da página.
O breadcrumb `static-rel` (`v5/routes/breadcrumbs.php:55-59`) aponta para a rota de LAI
`relatorio-estatistico` e **não** é usado por esta página.
