# Configurações da Instituição — Gaps, Dívidas e Comparativo v5 → v6

---

## 1. Sobreposição com o módulo `outras-configuracoes`

Esta é a **principal armadilha estrutural** do módulo e precisa ser dita sem
roupagem: a sobreposição é **literal**.

| Aspecto | `configuracoes-da-instituicao` | `outras-configuracoes` |
|---|---|---|
| Tabela | `configurations` | `configurations` (**a mesma**) |
| Model | `App\Prefecture\Configuration` | idem |
| Controller | `Prefecture\ConfigurationController` | idem |
| Controller@ação | `editByCategory` / `updateByCategory` | idem |
| Endpoint de gravação | `POST /cms/update/configuration` (`update.configurations.by.category`) | **o mesmo endpoint** |
| Categoria | `site` (id 1) — 57 configurações ativas | `outros` (id 4) — 0 configurações ativas |
| Recurso (`resources.slug`) | `configuracoes` (id 9, `modulo_id` = 1) | `outros` (id 40, `modulo_id` = 4) |
| Slug de rota | `configuracoes` | `outros` |
| Módulo (`modulos.slug`) | `configuracoes-da-instituicao` (id 1) | `outras-configuracoes` (id 4) |
| Permissão de leitura | `configuracoes.edit` | `outros.edit` |
| Permissão de gravação | `configuracoes.edit` | `configuracoes.edit` (a mesma!) |
| View | `prefecture.configurations.configuracoes.edit` | `prefecture.configurations.index` (via `index()`) |

Consequências práticas:

- **Não existe isolamento de dados**: o `POST update.configuration` grava
  qualquer chave cuja categoria esteja no `category_configurations_id` enviado.
  Um usuário com `configuracoes.edit` pode escrever na categoria `outros` (ou
  `temas`, ou `barra-privacidade`) apesar de a leitura exigir
  `<recurso>.edit` do módulo correspondente.
- **A distinção é apenas de categoria + recurso + módulo** — não de código, de
  endpoint, de tabela ou de controller.
- A tela de `outros` delega para `index()`, que carrega a categoria `outros`
  e renderiza `prefecture.configurations.index`; hoje, com 0 configurações
  ativas nessa categoria, ela aparece vazia.
- A modularização em `modulos`/`resources` foi criada **acima** do código
  legado, sem refatoração do controller: por isso a separação é apenas
  declarativa.

> Referência: o dossiê do módulo irmão está em
> [`../outras-configuracoes/`](../outras-configuracoes/). Ele documenta a
> mesma tabela e o mesmo controller a partir da categoria `outros`.

## 2. Achados verificados aplicáveis a este módulo

### A1 — Composer `$modulos` ausente no CMS (menu “Configurações” some)

- **Onde:** `v6/app/Providers/ModulosServiceProvider.php` registra composers
  apenas para `portal.layouts.modelo1..6.home.index`,
  `portal.layouts.modelo1..6.layout.sidebar.layout`,
  `portal.layouts.modelo1..6.layout.sidebar.orgaosNews`,
  `prefecture.contents.noticias.form`, `admin.layouts.dashboard` e
  `admin.layouts.dashboard-legacy`.
- **Efeito:** `admin.layouts.app` e `adminlte::page` **não** recebem
  `modulos`. Verificado empiricamente no container: os eventos
  `composing: admin.layouts.app` e `composing: adminlte::page` chegam sem a
  variável; ao registrar manualmente um composer em `admin.layouts.app`, o
  `adminlte::page` passa a receber `modulos`.
- **Consequência para este módulo:** em todas as telas do CMS **exceto** o
  dashboard, `$modulos['configuracoes-da-instituicao']` é indefinido e o item
  de menu “Configurações”
  (`v6/resources/views/vendor/adminlte/page.blade.php:228-232`) **não é
  exibido**. O módulo só é alcançável por URL direta.

### A2 — Permissão assimétrica entre leitura e gravação

- **Leitura:** `editByCategory()` exige `has_access(["$acess.edit"])`, que para
  este módulo é `configuracoes.edit`.
- **Gravação:** `updateByCategory()` exige `has_access(['configuracoes.edit'])`,
  **independentemente da categoria**.
- **Efeito:** o mesmo endpoint grava categorias de outros módulos, e o menu
  exige `configuracoes.index` (uma terceira permissão). Um operador com
  `configuracoes.index` + `configuracoes.edit` vê o menu e consegue salvar;
  um operador com apenas `configuracoes.index` vê o menu, entra na tela e
  recebe “Você não tem permissão para acessar este conteúdo”.

### A3 — `echo` dentro do controller antes do redirecionamento

```php
if (file_put_contents(base_path() . "/config.json", $json)) {
    echo "JSON file created successfully...";
} else {
    echo "Oops! Error creating json file...";
}
```

- **Onde:** `updateByCategory()`, linhas ~263-265.
- **Efeito:** no container em produção o `output_buffering` é `0`; o texto é
  emitido **antes** do redirect, com os cabeçalhos já enviados. O navegador
  exibe o texto e o redirect não é aplicado como esperado.

### A4 — `config.json` é obrigatório para o boot da aplicação

- **Consumidores:** `v6/config/app.php` (linha 6, `$data->config[2]`),
  `v6/config/mail.php` (linha 3, `$data->config[0]`),
  `v6/config/recaptcha.php` (linha 3, `$data->config[1]`).
- **Risco:** arquivo ausente, malformado ou com menos de 3 blocos derruba a
  aplicação **antes** de qualquer controller. Quem o mantém é esta tela.
- **Observação:** o arquivo é gerado **fora** do versionamento e depende do
  banco; um `config.json` vazio em uma instalação nova deixa a aplicação
  inoperante.

### A5 — `configurations.key` sem índice único

- **Efeito:** `Configuration::where('key',$key)->first()` grava **apenas a
  primeira** ocorrência de uma chave duplicada. Não há como o operador
  perceber que existem duas configurações com a mesma chave.
- **Consequência:** chaves duplicadas (por erro de seed ou por carga manual)
  ficam invisíveis e podem divergir do valor exibido na tela.

### A6 — N+1 no Form Request

- **Onde:** `v6/app/Http/Requests/ConfigurationRequest.php`, `rules()` e
  `messages()`.
- **Efeito:** uma query ao banco **por campo enviado**. Como a tela envia
  praticamente todas as chaves da categoria, cada save dispara dezenas de
  consultas antes de qualquer escrita.

### A7 — Datatable lista todas as categorias

- **Onde:** `v6/app/Http/Controllers/Prefecture/DatatableController.php`
  (linha 384), `get_configuracoes()`.
- **Efeito:** lê a view SQL `configuracoes`, que não é filtrável por
  categoria, e devolve **todas** as configurações de **todos** os módulos
  (87 ativas no banco atual), incluindo e-mails, senhas de SMTP e chaves de
  API em claro no JSON.

### A8 — `configurations` não tem FK para `modulos`

- **Efeito:** a ligação módulo ↔ chave é **indireta**:
  `modulos` → `resources.modulo_id` → `resources.slug` → rota/categoria →
  `category_configurations.slug` → `configurations.category_configurations_id`.
- **Consequência:** apagar ou renomear um `resource` quebra a associação sem
  erro; não há integridade referencial que proteja o caminho.

## 3. Funcionalidades incompletas ou mortas

| # | Item | Evidência |
|---|---|---|
| M1 | `updateSelosInstitucionais()`, `createSelosInstitucionais()` e `deleteSelosInstitucionais()` **não têm rota** | Busca por esses nomes em `v6/routes/` e em todo o código fora do controller retorna zero ocorrências. |
| M2 | Sem rota para criá-la, `selos_institucionais` **nunca é criada**; o banco confirma: a chave não existe | Consulta direta em `configurations` retorna 0 linhas para `selos_institucionais` e `selos_institucionais_url`. |
| M3 | Consequência de M1/M2: a aba “Selos” renderiza e o POST é aceito, mas **nada é gravado** (o controller só atualiza se a configuração já existir) | `updateByCategory()`: `if ($configSelos) { ... }` sem `else`. |
| M4 | `v6/app/Http/Views/Composers/ConfigurationsComposer.php` **não é registrado** em nenhum provider | Busca em `v6/app/Providers/` retorna vazio. Código morto. |
| M5 | Chaves `information_request_recaptcha` e `diario_oficial_segregacao_funcoes` são esperadas pela aba Opções mas **não existem** em `configurations` | Consulta ao banco retorna 0 linhas; a cláusula `||` do filtro nunca casa. |
| M6 | `licitacoes_url`, `email_denuncia` e `email_ouvidoria` são semeadas e editáveis, mas **nenhum consumidor** as lê na v6 | Busca por esses literais em `v6/app` e `v6/resources` não retorna uso fora da tabela `configurations`. |
| M7 | `select` de `structure_type` só oferece `prefeitura` e `camara`, enquanto o módulo `modulos` prevê outros perfis | `v6/resources/views/prefecture/configurations/configuracoes/loop_config.blade.php` vs `v6/resources/views/admin/modulos/form.blade.php`. |
| M8 | `email_suporte` é exibido na listagem de chaves mas é **excluído** do formulário por `loop_config.blade.php` — a tela mostra a chave (no seed) sem permitir edição | `loop_config.blade.php`, primeira linha do `if`. |
| M9 | A view SQL `configuracoes` expõe `value` como `row_number()` e não como o valor da configuração — quem consome a view não tem acesso ao conteúdo real | `v6/database/migrations/2023_04_17_144615_create_view_to_configuracoes.php`. |
| M10 | Regra `min` do Form Request é condicionada a `max_input != ''` | `ConfigurationRequest::rules()`: `if ($config->max_input != '')` decide **também** `$min`. |

## 4. Itens que existem na v5 e foram removidos na v6 (e o inverso)

| Item | v5 | v6 |
|---|---|---|
| `v5/database/seeds/PrivacyBarSeeder.php` | presente | **removido** — as 4 chaves da barra de privacidade foram incorporadas a `v6/database/seeds/ConfigurationsTableSeeder.php` |
| Validação “É obrigatório selecionar uma posição para todos os selos” | presente | **removida** — na v6, selos sem `posicao` são silenciosamente descartados (`continue`) |
| Validação de `quantidade_noticias_ultimas_noticias` (número par entre 2 e 20) | presente | **removida** |
| Armazenamento dos selos | `public/img/selos/` | disco `public` em `selos/`, com `migrarCaminhosSelosAntigos()` convertendo `storage/...` e `img/selos/...` |
| Tabela `modulos` | inexistente | `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` |
| `resources.modulo_id`, `menu_orders.modulo_id` | inexistente | migrations de nov/2024 |
| Tela `/cms/modulos` | inexistente | `Admin\ModulosController` + `admin/modulos/form.blade.php` |
| Bloco Dashboard / Google Analytics | inexistente | `v6/app/Services/Dashboard/ConfigurationRepository.php`, `v6/app/Services/Dashboard/WidgetRegistry.php` |
| `config.json` gerado pelo CMS | inexistente | gerado em `updateByCategory()` e consumido por `config/app.php`, `config/mail.php`, `config/recaptcha.php` |
| Endpoints JSON de selos | inexistente | `updateSelosInstitucionais`, `createSelosInstitucionais`, `deleteSelosInstitucionais` (sem rota) |
| Validação do template `layout` | aceito qualquer valor | precisa ser `modelo1..modelo6` |
| `app/Http/Model/Prefecture/Configuration.php` | — | **idêntico** entre v5 e v6 |
| `app/Http/Controllers/Prefecture/ConfigurationController.php` | versão anterior | reescrito (GA, Dashboard, selos, `config.json`) |
| `app/Http/Requests/ConfigurationRequest.php` | versão anterior | reescrito (regras dinâmicas por chave) |
| `database/seeds/ConfigurationsTableSeeder.php` | 711 linhas | 1125 linhas |
| `resources/views/prefecture/configurations/configuracoes/{edit,form,loop_config}.blade.php` | versão anterior | divergentes (abas Dashboard/Selos/Opções) |
| `resources/views/prefecture/configurations/temas/*` | — | **idênticos** à v5 |
| `resources/views/prefecture/configurations/barra-privacidade/*` | — | **idênticos** à v5 |

## 5. Riscos de dados

| # | Risco | Mitigação na v7 |
|---|---|---|
| D1 | `config.json` fora do versionamento e gerado por UI | Eliminar o arquivo; `.env` + tabela + cache |
| D2 | Senhas de SMTP e chave privada do Recaptcha gravadas **em claro** na tabela e devolvidas no JSON do datatable | Colunas `encrypted`; remover do datatable |
| D3 | `gemini_api_key` em claro | Coluna `encrypted` |
| D4 | `google_analytics_sa_json` já usa `Crypt::encrypt` (melhor caso) | Manter cast `encrypted` |
| D5 | `configurations.key` sem unicidade | `unique` em `key` |
| D6 | `order` como taxonomia de UI e sem unicidade | Coluna `group` |
| D7 | Caminhos de arquivos antigos (`../assets/images/...`, `img/selos/...`) | Migração de assets + validação de `Storage::exists` |
| D8 | `selos_institucionais` em JSON dentro de `value` | Tabela `institutional_badges` |
| D9 | Coluna `notnull` com `NULL` em 7 chaves | Definir `notnull` explícito por chave |

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

1. O perfil `structure_type` deve aceitar outros valores além de `prefeitura`
   e `camara`? Quais e quais efeitos cada um tem?
2. `licitacoes_url`, `email_denuncia` e `email_ouvidoria` devem ter
   consumidores ou podem ser removidas do formulário?
3. Os selos institucionais devem ser gravados (a feature está pronta e sem
   rota) ou desativados? Em que posição do portal devem aparecer?
4. `information_request_recaptcha` e `diario_oficial_segregacao_funcoes` devem
   ser criadas ou removidas do filtro da aba Opções?
5. A aba Mail deve continuar atrelada à permissão `mail_recaptcha`? O nome da
   permissão sugere “ver e-mail + recaptcha”, o que mistura dois domínios.
6. `email_suporte` deve ser editável pelo CMS? Hoje é fixa e não aparece no
   formulário.
7. Qual o valor esperado para `quantidade_noticias_ultimas_noticias` que a v5
   validava? A chave não existe na v6 e a validação foi removida.
8. Deve existir uma categoria “Institucional” própria (em vez de `site`), já que
   `site` concentra identidade, e-mail, Recaptcha, dashboard e transparência?

## 7. Checklist de correção (v7)

- [ ] Substituir `configurations` genérica por tabelas de domínio + `settings`
      com `key` única.
- [ ] Remover `config.json` do boot; migrar para cache de runtime.
- [ ] Introduzir Policy `UpdateInstitutionSettings` e eliminar a assimetria
      de permissões (A2).
- [ ] Remover o `echo` do controller (A3).
- [ ] Registrar `$modulos` em **todas** as views do CMS via composer global
      (A1).
- [ ] Remover do datatable as configurações sensíveis (A7).
- [ ] Decidir o destino dos endpoints JSON de selos (M1) ou removê-los.
- [ ] Remover `ConfigurationsComposer` (M4) ou registrá-lo.
- [ ] Eliminar o N+1 do Form Request (A6) com um request por aba.
- [ ] Tornar explícita a autorização por categoria no endpoint de gravação.
