# Revisões — Gaps, Bugs e Dúvidas

> Todos os itens abaixo foram confirmados por leitura do código-fonte da `v6` (e comparação
> com a `v5`) e/ou por consulta **somente leitura** ao Postgres da v6.

---

## 1. Bugs e erros confirmados

| # | Severidade | Descrição | Evidência | Correção sugerida |
|---|---|---|---|---|
| B1 | **Alta** | **Divisão por zero no cálculo do percentual do PDF**: `round(100 * ($valorFinal / $valorTotal))` com `pts_totais = 0` | `v6/app/Repositories/Eloquent/Prefecture/Ratings/Envios/EnviosRepository.php:46` | Guardar `pts_totais > 0` antes da divisão; em PHP 8 o código atual lança `DivisionByZeroError` |
| B2 | **Alta** | **Namespace divergente nos models**: os arquivos declaram `namespace App\Prefecture;`, mas 4 arquivos importam `App\Http\Model\Prefecture\SendRating` e 1 importa `App\Http\Model\Prefecture\EmailRevisoes`; o `vendor/composer/autoload_classmap.php` ainda mapeia os nomes antigos (linhas 256 e 277) | `v6/app/Http/Model/Prefecture/SendRating.php:3`, `v6/app/Http/Model/Prefecture/EmailRevisoes.php:3`, `v6/app/Http/Controllers/Prefecture/RatingsController.php:5`, `v6/app/Http/Controllers/Prefecture/RatingsClientsController.php:6`, `v6/app/Http/Controllers/Prefecture/QuestionAnswersController.php:7`, `v6/app/Repositories/Eloquent/Prefecture/Ratings/Envios/EnviosRepository.php:5` | Unificar em `App\Models`; na v7, um único namespace resolve |
| B3 | **Alta** | **A data da avaliação é sobrescrita ao enviar o e-mail**: `$send->update(['data' => new DateTime()])` | `v6/app/Http/Controllers/Prefecture/QuestionAnswersController.php:308-309` | Criar `sent_at` e preservar `data` |
| B4 | **Média** | **Rotas registradas sem método correspondente**: `respostas.update` (o `update()` foi removido da v6), `revisoes.show`, e `revisoes/relatorios.{create,store,update,destroy}` | `v6/routes/basepro.php:221`, `v6/routes/basepro.php:267-275`, `v6/routes/basepro.php:282-285` | Remover as rotas ou implementar os métodos |
| B5 | **Média** | **Nome de rota duplicado `respostas.index`** (resource na linha 282 e rota explícita na linha 291). O controller **não implementa `index()`** — funciona só porque a rota explícita sobrescreve a do resource | `v6/routes/basepro.php:282` e `:291` | Manter apenas uma das duas |
| B6 | **Média** | **Coluna `version` morta** em `ratings`: existe no schema, não está no `$fillable`, nunca é gravada e é exportada como `null` no JSON | `v6/app/Http/Model/Prefecture/Ratings.php:16-22`, `v6/app/Http/Controllers/Prefecture/RatingsController.php:303` | Remover a coluna ou implementar versionamento de verdade |
| B7 | **Média** | **Pontuação dependente de string literal**: só a alternativa cuja `description` é exatamente `"Sim"` pontua; "Não"/"Parcial" valem 0, e o `options.value` é ignorado | `v6/app/Http/Controllers/Prefecture/QuestionAnswersController.php:398-417`, `v6/app/Repositories/Eloquent/Prefecture/Ratings/Envios/EnviosRepository.php:275-305` | Usar um enum/constante e considerar o `value` |
| B8 | **Média** | **Importação de questionário não é transacional** e **descarta as opções** quando a categoria de opções já existe | `v6/app/Http/Controllers/Prefecture/RatingsController.php:343-424` | Transação + regra explícita de sobrescrita |
| B9 | **Média** | **Cópia de avaliação não copia categorias nem opções**: as perguntas copiadas apontam para as categorias da avaliação original | `v6/app/Http/Controllers/Prefecture/RatingsController.php:262-295` | Copiar a árvore completa ou documentar que a cópia é "parcial" |
| B10 | **Baixa** | **Mensagens de validação trocadas** em `RevisoesRepostas` (`dataDeEnvio.required` diz "O campo revisor é obrigatório") | `v6/app/Http/Requests/RevisoesRepostas.php:33-37` | Corrigir os textos |
| B11 | **Baixa** | **Escrita de arquivo temporário no `public/`** ao exportar o questionário (`public/cache/revisao_*.json`), sem limpeza | `v6/app/Http/Controllers/Prefecture/RatingsController.php:297-341` | `Storage::disk('local')->temporaryUrl()` ou `streamDownload()` |
| B12 | **Baixa** | **`down()` da migration de formato 24h recria a view em vez de removê-la** | `v6/database/migrations/2023_05_29_133831_envios_revisoes_formato_24_horas.php` (`down()` com `CREATE OR REPLACE VIEW`) | `DROP VIEW IF EXISTS` |
| B13 | **Baixa** | **Uso de superglobais** (`$_SERVER['REMOTE_ADDR']`, `$_SERVER['HTTP_USER_AGENT']`) no envio de e-mail | `v6/app/Http/Controllers/Prefecture/QuestionAnswersController.php:249-251` | Usar `$request->ip()` e o User-Agent do request |
| B14 | **Baixa** | **N+1 no relatório**: uma query de `answer_forms` por pergunta | `v6/app/Repositories/Eloquent/Prefecture/Ratings/Envios/EnviosRepository.php:275-305` | `->whereIn('questions_id', $ids)->get()` + `groupBy('questions_id')` |
| B15 | **Baixa** | **`get_listagem_revisoes` usa a permissão `pagina-estatica`** em `struct_views` (copy/paste), expondo botões com a autorização errada | `v6/app/Http/Controllers/Prefecture/DatatableController.php:446-450` | Usar `revisoes-cliente` |
| B16 | **Baixa** | **`IEnviosRepository` e `IRatingsRepository` são interfaces vazias**, embora `RevisoesWidget` chame `returnEnvios()` sobre o tipo da interface | `v6/app/Repositories/Contracts/IEnviosRepository.php`, `v6/app/Repositories/Contracts/IRatingsRepository.php`, `v6/app/Services/Dashboard/Widgets/RevisoesWidget.php:44-49` | Declarar os métodos nos contratos |
| B17 | **Baixa** | **Rota `/cms/listagem/revisoes/graficos/{id}`** fica fora do padrão de resource e do namespace `revisoes.*` | `v6/routes/basepro.php:225` | Renomear para `revisoes.graficos` |

## 2. Funcionalidades incompletas ou mortas

| Item | Situação |
|---|---|
| `Route::resource('revisoes/relatorios')` | Só `index`, `show` e `edit` existem; as demais rotas quebram |
| `Route::resource('respostas')` | Só `store` e `edit` são usados; `index`, `create`, `show`, `update`, `destroy` são Mortos ou sobrescritos |
| `Route::resource('revisoes')` | `show` não é usado pelo frontend (o botão "editar" vai para `revisoes.edit`) |
| `QuestionTypesSeeder` | O tipo "Múltipla escolha" está comentado: o motor só suporta resposta única |
| `options.value` ("0.5" para Parcial) | Nunca lido pelo cálculo — campo inerte |
| `Status` / `StatusCategory` / `statuses` | Tabelas e seed existem, mas o módulo não as usa (o status das revisões é booleano) |
| `rating.version` | Coluna inerte |
| `RevisoesPresenter::pegarQuestoesPDF()` | Em uso por `v6/resources/views/prefecture/configurations/revisoes/pdf/categories.blade.php:2` (montagem do PDF) |
| `EnviosRepository::generatePDF($id, $only_output = false)` | O parâmetro `$only_output` nunca é usado no corpo do método |
| `RatingsController@childrens()` / `@loopfilhos()` | Recursão de árvore **duplicada** em `RevisoesPresenter::pegarFilhos()` |
| "Importar Resposta" (botão em `respostas/create.blade.php`) | Não é upload de arquivo: pré-preenche o formulário com as respostas de **outro envio** da mesma avaliação, via `GET /cms/respostasByRatings/{id}` (`v6/public/assets/js/custom/contents/revisoes/revisoes_resposta.js`). O campo do select é `import_ratings` |

## 3. Mapa v5 → v6

### 3.1 Classes

| Classe | v5 | v6 | Observação |
|---|---|---|---|
| `Prefecture\RatingsController` | ✅ | ✅ | **idêntico** (`diff` sem diferenças) |
| `Prefecture\RatingsClientsController` | ✅ | ✅ | **idêntico** |
| `Prefecture\QuestionController` | ✅ | ✅ | **idêntico** |
| `Prefecture\OptionsController` | ✅ | ✅ | **idêntico** |
| `Prefecture\QuestionAnswersController` | ✅ | ✅ | **divergente**: `update()` (retificação) **removido** na v6 (linhas 182-246 da v5) |
| `Model\Prefecture\Ratings` | ✅ | ✅ | **idêntico** |
| `Model\Prefecture\SendRating` | ✅ | ✅ | **idêntico** |
| `Model\Prefecture\AnswerForm` | ✅ | ✅ | **idêntico** |
| `Model\Prefecture\EmailRevisoes` | ✅ | ✅ | **idêntico** |
| `Model\Prefecture\Questions` | ✅ | ✅ | **idêntico** |
| `Model\Prefecture\QuestionType` | ✅ | ✅ | **idêntico** |
| `Model\Prefecture\QuestionCategory` | ✅ | ✅ | **idêntico** |
| `Model\Prefecture\Options` | ✅ | ✅ | **idêntico** |
| `Model\Prefecture\OptionCategory` | ✅ | ✅ | **idêntico** |
| `Model\Prefecture\OptionCategoryHasOption` | ✅ | ✅ | **idêntico** |
| `Services\Dashboard\Widgets\RevisoesWidget` | ❌ | ✅ | **novo na v6**: widget de dashboard que consome `IEnviosRepository::returnEnvios()` |

### 3.2 Rotas

| Rota | v5 | v6 | Observação |
|---|---|---|---|
| `POST /cms/ajax/revisoes/delete-por-lote` (`ajax.ratings.deleteByLote`) | ✅ (linha 138) | ✅ (linha 149) | idem |
| `Route::resource("revisoes/relatorios")` | ✅ (linha 193) | ✅ (linha 221) | idem |
| `GET /cms/listagem/revisoes/graficos/{id}` | ✅ (linha 197) | ✅ (linha 225) | idem |
| `GET /cms/data/dashboard/envios` | ✅ (linha 198) | ✅ (linha 226) | idem |
| `Route::resource('revisoes')` + nomes `revisoes.*` | ✅ (linha 238) | ✅ (linha 267) | idem |
| `copiar_revisao`, `download_revisao`, `import_revisao`, `setStatus` | ✅ (linhas 247-250) | ✅ (linhas 276-279) | idem |
| `Route::resource('respostas')` | ✅ com `'update' => 'respostas.update'` no array `names()` | ✅ **sem** o item `update` no array `names()` | o nome default (`respostas.update`) é o mesmo; só o método `update()` é que deixou de existir |
| `respostas.index`, `respostas.getEnvio`, `respostas.getEmails`, `respostas.email` | ✅ (linhas 263-266) | ✅ (linhas 291-294) | idem |
| Bloco `options/*` (categorias, opções, grupos) | ✅ (linhas 268-280) | ✅ (linhas 296-314) | idem |
| `questions/*` + `reorder` + `categories-reorder` | ✅ (linhas 289-299) | ✅ (linhas 317-327) | idem |
| `respostas/pdf/{id}` (`questions.pdf`) | ✅ (linha 295) | ✅ (linha 323) | idem |
| `relatorio.show`, `relatorio.anuais`, `respostas/get/{ano}` | ✅ (linhas 302-303) | ✅ (linhas 330-332) | idem |

### 3.3 Views e JS

| Arquivo | v5 → v6 |
|---|---|
| `respostas/create.blade.php` | **divergente**: na v5 havia dois `<form>` (edição → `respostas.update` com `PUT`; criação → `respostas.store`); na v6 há **um único** form apontando para `respostas.store` |
| `respostas/form.blade.php` | **idêntico** (o `content_id` oculto já existia) |
| demais views de `revisoes/` | **idênticas** (`diff -rq` só acusou `respostas/create.blade.php`) |
| `respostas/datatable.js` | **divergente**: Removal do wrapper `<div class="send">` |
| `revisoes_envio.js` | **divergente**: o listener do botão "Enviar" passa de `btn.closest('.send')` para `btn` diretamente (acompanha a mudança acima) |
| demais JS de `revisoes/` | **idênticos** |

### 3.4 Dados e módulo

| Item | v5 | v6 |
|---|---|---|
| Tabelas `ratings`, `questions`, `send_ratings`, `answer_forms`, `email_revisoes`, `question_types`, `question_categories`, `option_categories`, `options`, `option_category_has_options` | — | **criadas na v6** (migrations 2022-05-17 a 2023-06-30) |
| Views SQL `formulario`, `envios`, `respostas`, `listagem_clientes` | — | **criadas na v6** |
| Registro na tabela `modulos` | — | id 34, `slug = revisoes`, `status = 0` (migration de 2024-11-01) |
| `resources` | — | ids 43 (`revisoes`) e 44 (`revisoes-cliente`) |
| `menu_orders` | — | id 28, `slug = revisoes` |
| Widget de dashboard | — | `RevisoesWidget` + 2 testes (criados para o ticket #23386) |

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

| Risco | Descrição | Mitigação |
|---|---|---|
| D1 | **Views SQL não são portáveis** para um schema novo sem recriá-las | Recriar as 4 views ou substituí-las por queries (ver `06-MIGRACAO-MODERNA.md`, seção 1.2) |
| D2 | **Seeders com ids fixos** (`PrefeituraRevSeeder`, `CamaraRevSeeder`): dependem da sequência do Postgres e de `ratings_id = 1/2` e `question_categories_id` 1..34 | Não reexecutar em bases populadas; migrar os dados com scripts ownidos, não com seeds |
| D3 | **`question_categories.ratings_id` sem FK**: pode apontar para avaliação inexistente | Recriar com FK e um backfill |
| D4 | **`options.value` é `text`** com valores "1", "0", "0.5" — se virar `decimal`, "Não" e "Parcial" perde o rótulo | Migrar para `decimal(3,2)` + manter `description` como rótulo |
| D5 | **`protocol` é `integer`**: o formato "AAAAMM"+rand pode colidir | Índice único em `(ratings_id, protocol)` na v7 |
| D6 | **`send_ratings.data` já foi sobrescrita** por envios de e-mail em installs antigos | Não há como recuperar a data original da avaliação; documentar a limitação |
| D7 | **`SoftDeletes` sem filtro em várias queries**: `Questions::where('ratings_id', $ratings_id)->get()` **inclui** perguntas soft-deleted (o model usa `SoftDeletes`, então o scope global já filtra — mas `AnswerForm`/`SendRating` são consultados por FK sem checagem de `deleted_at`) | Verificar o comportamento do `SoftDeletes` global scope em cada consulta da v7 |
| D8 | **PDFs já gerados** ficam em `storage/app/public/content/revisoes/{id}/pdf/` e são referenciados por `send_ratings.path` | Preservar o prefixo de storage ou migrar os arquivos e o campo `path` |

## 5. Permissões — inconsistência a esclarecer

- As views do módulo usam `has_access(['revisoes.create'])` e `has_access(['revisoes.delete'])`.
- No banco de referência, a tabela `permissions` tem **apenas** 9 slugs genéricos
  (`index`, `create`, `edit`, `delete`, `inactive`, `inactivate`, `restore`, `show`, `publish`)
  e **nenhuma** linha para `revisoes.*`.
- `has_access()` (`v6/app/Library/Helper/appHelpers.php:22-45`) só retorna `true` para
  `super-admin` quando o slug não existe na tabela.
- Consequência prática: **usuários não super-admin não veem os botões** de criar/excluir
  avaliação, embora possam acessar as telas (o `check.route.permission` compara o nome da
  rota, e `revisoes.index` também não existe como permissão).

**Pergunta em aberto para o negócio/infra**: como as permissões por módulo são
administradas? O padrão `slug-do-modulo.acao` é usado em todo o CMS
(`has_access(['ombudsman.delete'])`, `has_access(['noticias.create'])`, `has_access(['licitacoes.create'])` e equivalentes), mas a tabela
`permissions` do ambiente de referência não carrega esses slugs.

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

1. **Quem deve responder a avaliação?** Na v6 o respondente é o próprio operador do CMS
   (a tela pede data e revisor). O modelo de `send_ratings` (com `revisor` e `protocol`)
   sugere um respondente externo, mas não há link público para responder.
2. **A "frequência" de atualização** (categoria "Série Histórica" no seed da Câmara) sugere
   aplicação **mensal** da avaliação; o módulo não agenda nada (não há cron/queue para
   lembrar ou reaplicar).
3. **Retificação**: deve uma retificação manter o mesmo protocolo (como hoje) ou gerar um
   novo com vínculo explícito? Hoje o protocolo é herdado e o "vínculo" fica em `send_id`.
4. **`version`**: a intenção era versionar o questionário (revisões de um mesmo conjunto de
   perguntas)? Se sim, o modelo de dados atual não suporta (uma avaliação = um conjunto).
5. **Escala de satisfação**: o seed cria a categoria "Satisfação" (Insatisfeito → Muito
   satisfeito) e as perguntas da Câmara só usam "Comum" (Sim/Não/Parcial). A escala está
   pronta para uso?
6. **Pontuação de "Parcial"**: o `options.value` prevê 0.5, mas a pontuação ignora. Deve
   "Parcial" valer metade do peso?
7. **O relatório deve ser público?** Hoje o PDF é distribuído por e-mail, mas não há
   exposição no portal. Aclassified área de transparência institucional pode querer publicar.
8. **Universo da avaliação**: a avaliação da Câmara usa um conjunto fixo de perguntas
   (seed do TCE/PB, ~80 perguntas). Um município com outro recorte geográfico usaria
   outro conjunto — como isso é administrado?

## 7. Itens não verificados

- A **rotina de envio do e-mail** (`prefecture.emails.revisoes.email_user`) não foi
  renderizada em teste; o anexo usa `Storage::path($send->path)`, que depende do symlink
  `storage/app/public` → `public/storage` existir no ambiente.
- Não há **teste automatizado** do módulo (apenas o widget de dashboard), portanto
  comportamentos como a geração do PDF e o envio do e-mail foram verificados apenas por
  leitura de código.
- O comportamento do `check.route.permission` para as rotas `revisoes.*` **fora** do grupo
  `check.logged` (`revisoes/relatorios`, `listagem/revisoes/graficos/{id}`) não tem
  verificação de rota: `Route::getCurrentRoute()->getName()` retorna `null` para a rota sem
  nome (`listagem/revisoes/graficos/{id}`), o que pode gerar aviso em `explode('.', null)`.
