# Relatório Estatístico da Ouvidoria — Gaps, Dúvidas e Comparativo v5 -> v6

> Módulo **existente apenas na v5**. A seção "Regressão v5 -> v6" é obrigatória e está
> ao final do documento.

---

## 1. Bugs e inconsistências encontrados (v5)

| # | Achado | Evidência | Impacto |
|---|---|---|---|
| B1 | Botão **"Limpar filtro"** da página da Ouvidoria aponta para a rota `relatorio-estatististico` (LAI) | `v5/resources/views/portal/layouts/modelo1/transparencia-fiscal/relatorio-estatistico-ouvidoria/index.blade.php` (mesmo nas linhas equivalentes de modelo2..modelo6) | O cidadão sai da página de Ouvidoria ao tentar limpar o filtro |
| B2 | `$porAssunto` é calculado e serializado, mas **nunca exibido** (não existe `#chart_assunto`) | `v5/app/Http/Controllers/Portal/OmbudsmanController.php:344-352` e `v5/resources/views/portal/components/relatorio-estatistico-ouvidoria/charts-js.blade.php:4` | Query e JSON wasted; funcionalidade ausente |
| B3 | Soft delete inconsistente: `OmbudsmanRequest` **não** usa `SoftDeletes`, mas tem a coluna `deleted_at` | `v5/app/Http/Model/Prefecture/OmbudsmanRequest.php:15-17` | Pedidos removidos aparecem na listagem e nos cartões, mas **não** nos gráficos por tipo/assunto/gênero |
| B4 | `ano` gravado como `varchar` | migration `2026_05_27_135849_...` | Ordem lexicográfica no `orderBy('ano','desc')`; aceita "abcd" e "20255" |
| B5 | `exportRelStatitics` trata `xls` como "qualquer outro" e gera **.xlsx** | `v5/app/Http/Controllers/Portal/OmbudsmanController.php:414-431` | O botão "xls" entrega um arquivo `.xlsx` (nome divergente do rótulo) |
| B6 | `Content-Type: plain/txt` no download TXT (typo) | `v5/app/Http/Controllers/Portal/OmbudsmanController.php:445-450` | Navegadores podem não reconhecer o tipo; o `Content-Disposition` salva o arquivo de qualquer forma |
| B7 | Slug de permissão `relatorio-estatistico-ouvidoria-cms.*` **não existe** em `resources` nem em `permissions` | `v5/app/Library/Helper/appHelpers.php:22-41`; `resources` tem apenas `ouvidoria` (id 72); `permissions` tem 8 verbos genéricos | Para qualquer usuário fora do papel `super-admin`, os botões "Cadastrar", "Editar" e "Excluir" ficam ocultos — o CRUD fica inutilizável |
| B8 | Rotas de escrita (`store`, `update`, `destroy`) **sem** `check.logged` e **sem** `check.route.permission` | `v5/routes/basepro.php:342-344` | Proteção só pelo grupo `before => 'csrf'` e pela sessão já existente; um POST autenticado sem permissão de módulo é aceito |
| B9 | `authorize()` do Form Request retorna `true` | `v5/app/Http/Requests/RelatorioEstatisticoOuvidoriaRequest.php` | Validação de papel inexistente na camada de request |
| B10 | `catch (\Exception $e)` exibe `$e->getMessage()` ao operador | `v5/app/Http/Controllers/Prefecture/RelatorioEstatisticoOuvidoriaController.php` | Risco de expor mensagem interna de banco |
| B11 | `redirect()->back()` sem `withInput()` | idem | Campos digitados são perdidos em erro |
| B12 | Ao enviar novo PDF na edição, o arquivo **antigo não é removido** do storage | `saveUpload($file, 'docs', null, null, false)` — `$fileOld = null` (`v5/app/Http/Traits/FileTrait.php:50-66`) | Órfãos no disco |
| B13 | View do portal usa `$content->layout` mesmo quando a `static_pages` não existe (`$content = null`) | `OmbudsmanController@relStatitics` define `$null = true`, mas a view acessa `$content->layout` | Erro 500 quando o slug da página está ausente |
| B14 | O template `default` **não possui** a pasta `transparencia-fiscal/relatorio-estatistico-ouvidoria` | `v5/resources/views/portal/layouts/default/` não tem `transparencia-fiscal` | Se `configurations.layout = default`, a rota do portal quebra (view não encontrada) |
| B15 | Carga de **todas** as solicitações em memória para montar a lista de anos | `OmbudsmanController@relStatitics` (`OmbudsmanRequest::orderBy('created_at','desc')->get()`) | Custo de memória e tempo cresce com o histórico da Ouvidoria |
| B16 | `now()` usado como referência no cálculo de prazo | `v5/app/Http/Model/Prefecture/OmbudsmanRequest.php:114-115` | O resultado de "dentro do prazo" muda a cada visita, para pedidos antigos em tramitação |
| B17 | Tabela `relatorios_estatisticos_ouvidoria` **vazia** (0 registros) no banco v5 | `select count(*)` → 0 | A tabela de PDFs por ano aparece vazia no portal |
| B18 | View órfã `.../modelo1/informacoes-prefeitura/acesso-rapido/relatorio-estatistico-ouvidoria.blade.php` é uma **cópia da página de LAI** (usa `$informationRequests`) e não é referenciada por nada | `grep -rn "acesso-rapido/relatorio-estatistico-ouvidoria"` em `v5/resources/views` e `v5/app` → sem resultado | Código morto que confunde a leitura do legado |
| B19 | `ano` não tem unicidade: vários relatórios por ano são permitidos | migration + Request | A tabela de PDFs pode ter linhas duplicadas para o mesmo ano |
| B20 | Sem trilha de auditoria (`LogsActivity`) no model | `v5/app/Http/Model/Prefecture/RelatorioEstatisticoOuvidoria.php` | Não se sabe quem cadastrou/alterou/excluiu um relatório |

---

## 2. Funcionalidades incompletas ou mortas

| Item | Situação |
|---|---|
| Agregado por assunto (`$porAssunto`) | Calculado, serializado, **não exibido** |
| Gráfico "Por Tipo de Manifestação" | Exibido (v5); perdido na v6 |
| Gráfico "Por Gênero dos Solicitantes" | Exibido (v5); perdido na v6 |
| Cartões "Recebidos / Atendidos / Indeferidos" | Exibidos (v5); perdidos na v6 |
| Tabela de relatórios PDF por ano | Exibida (v5); perdida na v6 (sem tabela e sem cadastro) |
| View `acesso-rapido/relatorio-estatistico-ouvidoria.blade.php` | Órfã |
| `$isUpdate` no Form Request | Variável calculada, mas as duas expressões produzem a mesma regra (código morto) |
| Import `PhpOffice\PhpSpreadsheet\Calculation\Category` e `PharIo\Manifest\Manifest` | Não utilizados no controller do portal |

---

## 3. Riscos de dados (migração de schema)

| Risco | Detalhe | Mitigação |
|---|---|---|
| `ano` texto -> inteiro | A v5 aceita até 10 caracteres; valores não numéricos quebram a conversão | Auditar `select ano, count(*) from relatorios_estatisticos_ouvidoria group by ano` antes de converter; manter coluna de backup |
| Tabela vazia | Não há dados a migrar no ambiente v5 consultado, mas outros municípios podem ter | Script idempotente de migração |
| Arquivos no storage | Os PDFs ficam em `storage/app/public/docs/` e **não** são referenciados por URL absoluta | Migrar os binários para o novo disco preservando a chave `docs/<arquivo>` |
| Ligação `public/storage` | O symlink não existe no repositório (criado por `artisan storage:link`) | Documentar no checklist de deploy |
| Contagens históricas | Ao ativar `SoftDeletes` no `OmbudsmanRequest`, os registros com `deleted_at` preenchido passam a ser **excluídos** das listagens e cartões (hoje são exibidos) | Decidir explicitamente com o negócio se isso é desejado; o comportamento atual da v5 é inconsistente |

---

## 4. Perguntas em aberto para o negócio

1. Os relatórios PDF cadastrados no CMS devem continuar sendo listados no topo da página
   pública (comportamento da v5) ou virar uma seção à parte ("Relatórios Anuais")?
2. O prazo aplicável é sempre 20 dias, ou há prazo diferenciado por tipo de manifestação?
3. O agregado por assunto deve ser exibido? Se sim, em qual formato (gráfico, tabela, PDF)?
4. O cartão "Pedidos Recebidos" (total do ano) deve incluir ou não os pedidos com
   resposta em análise de prazo? Hoje os dois cartões usam bases diferentes.
5. Quem pode cadastrar relatórios? Sugestão: um `resource`/`modulo` dedicado
   (`relatorio-estatistico-ouvidoria`) na tabela de recursos, com as permissões
   `index/create/edit/delete`.
6. A exclusão deve ser definitiva (com trilha de auditoria) em vez de soft delete?
7. O botão "Limpar filtro" deve permanecer na página da Ouvidoria (corrigindo o destino)?
8. Deve haver um relatório estatístico **gerado automaticamente** (PDF anual) ou apenas o
   upload manual feito pelo operador?

---

## 5. Mapa v5 -> v6 (arquivos)

| Artefato | v5 | v6 |
|---|---|---|
| Controller do CMS | `v5/app/Http/Controllers/Prefecture/RelatorioEstatisticoOuvidoriaController.php` | **ausente** |
| Model | `v5/app/Http/Model/Prefecture/RelatorioEstatisticoOuvidoria.php` | **ausente** |
| Form Request | `v5/app/Http/Requests/RelatorioEstatisticoOuvidoriaRequest.php` | **ausente** |
| Migration | `v5/database/migrations/2026_05_27_135849_create_relatorios_estatisticos_ouvidoria_table.php` | **ausente** |
| Tabela no banco | `relatorios_estatisticos_ouvidoria` | **não existe** (verificado com `\dt relatorios*`) |
| Views do CMS | `v5/resources/views/prefecture/relatorio-estatistico-ouvidoria/{index,create,edit,form}.blade.php` | **ausentes** |
| JS do DataTable | `v5/public/assets/js/custom/config/relatorio-estatistico-ouvidoria/datatable.js` | **ausente** |
| Rotas do CMS | `v5/routes/basepro.php:339-344` e `:481` | **ausentes** |
| Item de menu no CMS | `v5/app/Providers/MenuServiceProvider.php:137` ("Relatório Estatístico" dentro de "Ouvidoria") | **removido** (o `case 'ouvidoria'` da v6, `v6/app/Providers/MenuServiceProvider.php:152-177`, tem 6 itens e nenhum de relatório) |
| Rota do portal | `v5/routes/web.php:32` (`/relatorio-estatistico-ouvidoria`) | **mantida** — `v6/routes/web.php:23` |
| Rota de exportação | `v5/routes/web.php:33` (`/relatorio-estatistico-ouvidoria/exportacao`) | **removida** — a v6 exporta por `?type=` na própria rota (`v6/app/Http/Controllers/Portal/OmbudsmanController.php:275-277`, `356-370`) |
| Controller do portal | `v5/app/Http/Controllers/Portal/OmbudsmanController.php` (`relStatitics`, `exportRelStatitics`) | **mantido**, com implementação simplificada (`v6/app/Http/Controllers/Portal/OmbudsmanController.php:257-329`) |
| Views do portal | 12 arquivos (modelo1..modelo6 × index/js) | **mantidos** (12 arquivos) |
| Componente de cartões + gráficos | `v5/resources/views/portal/components/relatorio-estatistico-ouvidoria/{stats,charts-js}.blade.php` | **ausentes** na v6 |
| Componente da tabela de PDFs | `v5/resources/views/portal/components/relatorio-estatistico/table.blade.php` | **ausente** na v6 |
| Componente de botões de exportação | `v5/resources/views/portal/components/relatorio-estatistico-export-button.blade.php` | **ausente** na v6 (substituído por `exportarRelatorio()` dentro do `js.blade.php` do portal) |
| Consulta ao model do CMS no portal | `RelatorioEstatisticoOuvidoria::orderBy('ano','desc')` | **removida** da v6 |

---

## 6. Regressão v5 -> v6

### 6.1 O que a v6 **mantém**

| Item | Situação na v6 |
|---|---|
| Rota pública `/relatorio-estatistico-ouvidoria` | **Mantida** (`v6/routes/web.php:23`, `Route::any` → `Portal\OmbudsmanController@relStatitics`) |
| Controller do portal | **Mantido** (`v6/app/Http/Controllers/Portal/OmbudsmanController.php:257-329`), com os mesmos 8 contadores de prazo, a mesma lista de anos e a mesma leitura de `full_list_requests_disabled` |
| 12 views de portal (modelo1..modelo6 × `index`/`js`) | **Mantidas** (`v6/resources/views/portal/layouts/modelo1/transparencia-fiscal/relatorio-estatistico-ouvidoria/` e equivalentes em modelo2..modelo6) |
| Gráfico de pizza de status (Google Charts) | **Mantido** (`v6/resources/views/portal/layouts/modelo1/transparencia-fiscal/relatorio-estatistico-ouvidoria/js.blade.php`) |
| Modal Ajax da solicitação (`POST /ajax/get-answers/ouvidoria`) | **Mantido** (`v6/routes/web.php:73`) |
| Exportação XLSX/CSV/PDF/TXT | **Mantida**, porém acionada por `?type=` na própria rota em vez de uma rota dedicada |
| Filtro por ano de gestão | **Mantido** |

### 6.2 O que a v6 **perdeu** (regressão funcional)

| # | Perda | Evidência na v6 | Consequência |
|---|---|---|---|
| R1 | **Todo o CMS de relatórios PDF** (controller, model, request, 4 views, JS do DataTable, 6 rotas, migration e tabela) | `grep -rn "relatorio-estatistico\|RelatorioEstatistico" v6/app v6/routes v6/resources v6/database` → nenhuma ocorrência de `RelatorioEstatisticoOuvidoria`; `\dt relatorios*` → "Did not find any relation" | A prefeitura **não consegue mais cadastrar** o relatório oficial (PDF) por ano de gestão, e o portal não tem mais essa seção |
| R2 | **Item de menu** "Relatório Estatístico" no CMS | `v6/app/Providers/MenuServiceProvider.php:152-177` — o `case 'ouvidoria'` da v6 lista 6 itens (Lista de Chamados, Tipos de Chamado, Assuntos, Respostas Prontas, Formas de Resposta, Origens de Chamado), sem o relatório | Mesmo que existisse a tela, ela não é alcançável pelo menu |
| R3 | **Cartões de resumo** (Pedidos Recebidos / Atendidos / Indeferidos) | não existe a pasta `portal/components/relatorio-estatistico-ouvidoria/` na v6 (nem o arquivo `stats.blade.php`) | O público perde a leitura direta dos números |
| R4 | **Gráfico "Por Tipo de Manifestação"** e **"Por Gênero dos Solicitantes"** | não existe o arquivo `charts-js.blade.php` na pasta de componentes da v6; o `js.blade.php` da v6 desenha **apenas** o `chart_div` de status | Perdida a análise por tipo e por gênero, que é o principal ganho do módulo |
| R5 | **Tabela de relatórios PDF por ano** na página pública | a pasta `portal/components/` da v6 não tem `relatorio-estatistico/table.blade.php` | Mesmo que houvesse dados, não haveria como publicá-los |
| R6 | **Componente de botões de exportação** com os 4 formatos | não existe `relatorio-estatistico-export-button.blade.php` em `portal/components/` na v6 | Substituído por `exportarRelatorio()` no `js.blade.php` — comportamento similar, mas com `?type=` |
| R7 | **Rota dedicada de exportação** `/relatorio-estatistico-ouvidoria/exportacao` | `v6/routes/web.php` não a declara | Quebra de links externos e de scripts que apontem para a URL antiga |
| R8 | **Cabeçalho "Ano de Gestão"** nos arquivos exportados (XLSX/CSV/PDF) | `v6/resources/views/exports/ombudsman-requests.blade.php` mostra apenas "Data da exportação"; a v5 mostra "Ano de Gestão: {{ $filtro_ano }}" | A exportação da v6 **não informa o ano filtrado** — perda de rastreabilidade do documento |
| R9 | **Nome da prefeitura** no cabeçalho da exportação | `v6/resources/views/exports/ombudsman-requests.blade.php` usa o título fixo "Ouvidoria - Relatório Estatístico"; a v5 usa `{{ config('app.name') }}` | Documento menos identificável |
| R10 | **Máscara do texto da resposta** no modal | a v5 devolve `value.answer`; a v6 devolve `value.mask_answer` (`v6/app/Http/Controllers/Portal/OmbudsmanController.php:344-352`) | Mudança de comportamento (não necessariamente regressão), mas precisa ser comunicada: o texto exibido não é mais o integral |
| R11 | **`?ano=` na exportação** | `v6/app/Http/Controllers/Portal/OmbudsmanController.php:275-277` — o `?type=` é lido antes dos filtros, mas o ano continua sendo aplicado ao `$ombudsmanRequests` | Mantido, porém o tratamento mudou de rota para parâmetro |
| R12 | Resposta HTTP de "protocolo não encontrado" | v5: **404**; v6: **200** com corpo de erro (`v6/app/Http/Controllers/Portal/OmbudsmanController.php:250`) | Mudança de contrato de API (impacto em consumidores) |

### 6.3 O que a v6 **mantém como bug**

| Bug | Situação na v6 |
|---|---|
| Botão "Limpar filtro" apontando para a rota de LAI | **Mantido** (`v6/resources/views/portal/layouts/modelo1/transparencia-fiscal/relatorio-estatistico-ouvidoria/index.blade.php:149` e equivalentes) |
| Template `default` sem a pasta `transparencia-fiscal/relatorio-estatistico-ouvidoria` | **Mantido** (mesma estrutura de pastas em `v6/resources/views/portal/layouts/`) |
| `OmbudsmanRequest` sem `SoftDeletes` (contagens inconsistentes com os gráficos por tipo/gênero) | **Mantido** (a v6 nem tem os gráficos por tipo/gênero, mas a inconsistente de base permanece) |

### 6.4 Conclusão da regressão

A v6 **não removeu o portal** do relatório: a rota, o controller, as 12 views, o gráfico de
status, o modal e as exportações continuam lá. O que a v6 removeu foi **todo o módulo de
gestão** do relatório (o CRUD de PDFs e o item de menu) e **toda a camada de análise**
(cartões de resumo, gráficos por tipo e por gênero, tabela de relatórios por ano), além de
trocar a rota de exportação por um parâmetro e simplificar o cabeçalho dos arquivos gerados.

Na prática, a v6 voltou ao **relatório estatístico da LAI/Transparência Fiscal** com o
nome de Ouvidoria: número de pedidos, status e listagem — sem as métricas que o módulo
havia consolidado. **Para a reconstrução em Laravel moderno, a referência funcional
completa é a v5**, e não a v6.
