# Outras Configurações — Regras e Portabilidade

> Módulo `outras-configuracoes`. Regras observáveis, acoplamentos e armadilhas
> relevantes para a reconstrução da v7.

---

## 1. Regras de negócio não óbvias

| # | Regra | Origem |
|---|---|---|
| R1 | O menu "Outras Configurações" só aparece se `has_access(['outros.index'])` **e** o módulo `outras-configuracoes` estiver com `status = 1` | `v6/resources/views/vendor/adminlte/page.blade.php:254` |
| R2 | O clique no menu vai para `GET /cms/configuracoes/editar/outros`, que exige `has_access(['outros.edit'])` | `editByCategory()` |
| R3 | Para `outros`, **não há formulário por categoria**: `case 'outros'` devolve `$this->index()` | `editByCategory()` |
| R4 | A listagem exibe **todas** as configurações de **todas** as categorias, porque a fonte é a view SQL `configuracoes` (sem `where`) | `DatatableController@get_configuracoes()` |
| R5 | A coluna "ID" exibe o `row_number()` da view, não o `id` real da configuração | view SQL `configuracoes` |
| R6 | Criar, editar e excluir configuração exige o papel `super-admin` (`Sentinel::inRole('super-admin')`) | `create()`, `edit()` |
| R7 | A gravação pela rota `update.configurations.by.category` exige `has_access(['configuracoes.edit'])`, **independente da categoria** | `updateByCategory()` |
| R8 | A `key` é normalizada com `str_slug($key, '_')` no cadastro e na atualização | `store()`, `update()` |
| R9 | Com `status_create = true`, a `key` fica `disabled` no formulário e, por consequência, não é enviada no POST | `form.blade.php` |
| R10 | A exclusão é **soft delete** (coluna `deleted_at`); a linha some da view SQL porque ela filtra `deleted_at IS NULL` | `Configuration` (SoftDeletes) + view SQL |
| R11 | O valor padrão da categoria no formulário de cadastro é **4 (`outros`)** | `form.blade.php` |
| R12 | `value` é obrigatório exceto quando `type = 'file'` | `ConfigurationRequest::rules()` |
| R13 | Na rota por categoria, obrigatoriedade/tamanho vêm de `notnull`, `max_input` e `min_input` da própria linha em `configurations` | `ConfigurationRequest::rules()` |
| R14 | Na rota por categoria, as regras de `min` só são aplicadas quando `max_input` não está vazio | `ConfigurationRequest::rules()` (condição duplicada) |
| R15 | Arquivos de configuração vão para o diretório `configuracoes/` do disco `public`; na criação o nome é `file_{4 chars}{nomeOriginal}`, na edição `img_{YmdHi}{4 chars}.{ext}` | `store()`, `update()` |
| R16 | Na edição com upload, o arquivo anterior é apagado com `Storage::delete("/" . $fileOld)` | `update()` |
| R17 | Toda operação do módulo gera log de atividade via `activity()->performedOn(...)->log(...)` | `configurationLog()`, `store()`, `update()`, `create()`, `edit()` |
| R18 | O botão "Cadastrar Configuração" aparece na listagem mesmo para quem não é `super-admin`; o bloqueio acontece no controller, com flash e redirect | `index.blade.php` + `create()` |

## 2. Acoplamentos

### 2.1 Dependências (o módulo depende de)

| Dependência | Natureza |
|---|---|
| `configuracoes-da-instituicao` (`../configuracoes-da-instituicao/`) | Mesmo controller, mesmo model, mesma tabela, mesmo endpoint `update.configurations.by.category`. A distinção é a categoria (`site` vs `outros`) e o recurso (`configuracoes` vs `outros`) |
| `lgpd` (`../lgpd/`) | Mesma categoria "genérica" de configurações e mesma família de rotas; a barra de privacidade tem categoria própria (`barra-privacidade`, id 7) |
| `temas-e-cores` (`../temas-e-cores/`) | Mesma estrutura de "categoria de configuração com formulário próprio" |
| Tabela `modulos` | Habilita/desabilita o menu em `/cms/modulos` |
| Tabela `resources` (`slug = outros`) | Origem das permissões `outros.index` / `outros.edit` |
| View SQL `configuracoes` | Fonte única dos dados da listagem |
| `ConfigHeaderServiceProvider` | Consome chaves de `configurations` para montar o header do portal (apenas `site`) |

### 2.2 Dependentes (quem depende deste módulo)

| Dependente | Natureza |
|---|---|
| `DatatableController@get_configuracoes` | Serve a listagem; é compartilhado com qualquer tela que use a DataTable de configurações |
| `App\Providers\ConfigHeaderServiceProvider` | Lê `configurations` (categoria `site`) para injetar `$configurations` no header do portal |

## 3. Armadilhas técnicas

| # | Armadilha | Impacto | Onde |
|---|---|---|---|
| A1 | **N+1 no Form Request**: `rules()` e `messages()` executam uma query por campo enviado | Requisição com 30 campos → 30 queries | `v6/app/Http/Requests/ConfigurationRequest.php` |
| A2 | **`configurations.key` sem índice único** | `Configuration::where('key',$key)->first()` grava apenas a primeira ocorrência; chaves duplicadas ficam invisíveis | migration de `configurations` |
| A3 | **`$configurations` carregado e não usado** | O controller consulta a categoria `outros` (vazia) e a view ignora a variável; o operador vê todas as categorias | `index()` + `index.blade.php` |
| A4 | **Divergência de permissões**: leitura exige `<recurso>.edit`, gravação exige `configuracoes.edit` | Usuário com `outros.edit` lê, mas não grava; usuário com `configuracoes.edit` grava qualquer categoria | `editByCategory()` x `updateByCategory()` |
| A5 | **`echo` dentro do controller** | `updateByCategory` imprime "JSON file created successfully..." antes do redirect; com `output_buffering => 0` os cabeçalhos são enviados antes do redirecionamento | `updateByCategory()` |
| A6 | **`config.json` obrigatório no boot** | `v6/config/app.php`, `v6/config/mail.php` e `v6/config/recaptcha.php` leem `base_path()."/config.json"` com `file_get_contents`; sem o arquivo a aplicação não sobe | `v6/config/app.php` |
| A7 | **`$request->all()` no update** | Campos não previstos no formulário podem ser gravados em colunas preenchíveis (`$fillable`) | `update()` |
| A8 | **Chave `disabled` não é enviada** | O `disabled` do HTML impede o envio; a preservação da `key` depende de ela não aparecer no POST | `form.blade.php` |
| A9 | **`DB::table('states')->get()`** em `editByCategory` | Consulta executada para todas as categorias, sem uso no caso `outros` | `editByCategory()` |
| A10 | **Exclusão com URL padrão errada** | `addDelete()` usa `/cms/conteudo/${id}` como fallback; funciona apenas porque o atributo `target` é sempre gravado | `v6/public/assets/js/custom/utilities/table.js` |
| A11 | **Coluna "ID" é um `row_number()`** | A numeração exibida não corresponde ao `id` usado nas URLs de edição/exclusão | view SQL `configuracoes` |
| A12 | **Soft delete sem rota de restauração** | A coluna `deleted_at` existe, mas o módulo não expõe restauração; `struct_views()` calcula `active`/`inactivate` sem uso | `Configuration`, `struct_views()` |
| A13 | **`hasOne` para a categoria** | `categoryConfiguration()` é `hasOne` (não `belongsTo`); funciona, mas distorce o modelo | `v6/app/Http/Model/Prefecture/Configuration.php` |
| A14 | **Composer `$modulos` ausente** | Fora do dashboard, o menu do módulo não é renderizado | `v6/app/Providers/ModulosServiceProvider.php` |
| A15 | **Nome do módulo ≠ conteúdo** | "Outras Configurações" não agrupa servidor/e-mail/API keys; essas chaves estão em `site` e `dashboard` | seeds e banco |

## 4. Código duplicado entre v5 e v6

Comparação por `diff` entre `v5/` e `v6/`:

| Arquivo | Resultado |
|---|---|
| `app/Http/Model/Prefecture/Configuration.php` | **Idêntico** |
| `app/Http/Model/Prefecture/CategoryConfiguration.php` | Idêntico |
| `resources/views/prefecture/configurations/index.blade.php` | Idêntico |
| `resources/views/prefecture/configurations/form.blade.php` | Idêntico |
| `resources/views/prefecture/configurations/create.blade.php` | Idêntico |
| `resources/views/prefecture/configurations/edit.blade.php` | Idêntico |
| `public/assets/js/custom/config/configuracoes/datatable.js` | Idêntico |
| `app/Http/Controllers/Prefecture/ConfigurationController.php` | **Divergente** (536 → 856 linhas) |
| `app/Http/Requests/ConfigurationRequest.php` | Divergente |
| `database/seeds/ConfigurationsTableSeeder.php` | Divergente |
| `database/seeds/CategoryConfigurationsTableSeeder.php` | Divergente (v6 usa `updateOrCreate` por `slug`) |

> O módulo, portanto, é **funcionalmente o mesmo** em v5 e v6: nenhuma feature
> do módulo `outras-configuracoes` foi adicionada na v6. A v6 apenas adicionou
> a *tabela* `modulos` que habilita o menu.

## 5. Onde a v6 divergiu da v5 (relevante a este módulo)

| Mudança | Efeito no módulo |
|---|---|
| Tabela `modulos` + `resources.modulo_id` | Introduz a chave de habilitação `$modulos['outras-configuracoes']` no menu |
| Tela `/cms/modulos` | Permite ligar/desligar o módulo sem deploy |
| `CategoryConfigurationsTableSeeder` com `updateOrCreate` | As categorias passam a ser recriadas idempotentemente; a categoria `outros` continua vazia |
| `PrivacyBarSeeder` removido na v6 (existia na v5) | As 4 chaves da barra de privacidade foram incorporadas ao `ConfigurationsTableSeeder` da v6 (categoria `barra-privacidade`) |
| Bloco Dashboard/Google Analytics | Cria a categoria `dashboard` (id 8) com 6 chaves — fora de `outros` |
| `config.json` gerado pelo CMS | Passa a ser lido no boot por `v6/config/app.php` |
| Validação de template `modelo1..modelo6` | Adicionada em `updateByCategory()` |

Nada disso alterou o comportamento observável da listagem de "Outras
Configurações": ela continua mostrando todas as categorias.

## 6. Regras que a v7 **não pode** quebrar

1. Usuário com `outros.index` vê o menu; sem ele, não vê.
2. Usuário sem `outros.edit` é redirecionado ao dashboard ao clicar no menu.
3. A listagem continua acessível por `GET /cms/configuracoes` (rota nomeada
   `configuracoes.index`), usada por links internos.
4. Excluir configuração responde JSON 200 com a chave `success` (o front-end usa
   `result.success` / `result.erro`).
5. A exclusão é lógica: a configuração não pode reaparecer na listagem.
6. `key` continua normalizada com `str_slug(..., '_')`, porque o código do
   portal (`ConfigHeaderServiceProvider`, `functions.php`) lê chaves com
   underscore.
7. A relação `configurations.category_configurations_id → category_configurations.id`
   precisa continuar existindo: a view SQL depende do `join`.

## 7. Portabilidade — itens que exigem decisão na v7

| Item | Legado | Sugestão v7 |
|---|---|---|
| `str_slug` | Helper do Laravel 5.5 (removido no 6+) | `Str::slug($key, '_')` |
| `Sentinel` | `cartalyst/sentinel` 2.0 | `spatie/laravel-permission` ou Policies |
| `has_access()` | Função global | Policy / Gate |
| `activity()` | Facade global do activity log | Modelo `Activity` ou `spatie/laravel-activitylog` |
| View SQL `configuracoes` | View no PostgreSQL | Query builder + `JOIN` + índice por categoria |
| `configurations.key` sem índice | — | Índice único por `key` (ou unique composto `key` + `deleted_at`) |
| `hasOne` para categoria | — | `belongsTo` |
| `disabled` no input de chave | HTML puro | Campo somente leitura no front-end + guarda no backend |
| `echo` no controller | — | Nunca; usar `Log::info()` |
