# Configurações da Instituição — Rotas e Controllers

> Todas as rotas do CMS vivem em `v6/routes/basepro.php`, dentro do grupo
> `Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)` (linhas 35-513).

---

## 1. Rotas do módulo

| Método | URI | Nome da rota | Controller@ação | Middleware | Arquivo:linha |
|---|---|---|---|---|---|
| GET | `/cms/configuracoes/editar/configuracoes` | `edit.configuracoes.by.category` | `Prefecture\ConfigurationController@editByCategory` | `check.logged` | `v6/routes/basepro.php:241` |
| POST | `/cms/update/configuration` | `update.configurations.by.category` | `Prefecture\ConfigurationController@updateByCategory` | **nenhum** (grupo `csrf`) | `v6/routes/basepro.php:246` |
| GET | `/cms/configuracoes` | `configuracoes.index` | `Prefecture\ConfigurationController@index` | `check.logged` | `v6/routes/basepro.php:175` (resource) |
| GET | `/cms/configuracoes/create` | `configuracoes.create` | `Prefecture\ConfigurationController@create` | `check.logged` | `v6/routes/basepro.php:175` (resource) |
| POST | `/cms/configuracoes` | `configuracoes.store` | `Prefecture\ConfigurationController@store` | `check.logged` | `v6/routes/basepro.php:175` (resource) |
| GET | `/cms/configuracoes/{id}` | `configuracoes.show` | `Prefecture\ConfigurationController@show` | `check.logged` | `v6/routes/basepro.php:175` (resource) |
| GET | `/cms/configuracoes/{id}/edit` | `configuracoes.edit` | `Prefecture\ConfigurationController@edit` | `check.logged` | `v6/routes/basepro.php:175` (resource) |
| PUT | `/cms/configuracoes/{id}` | `configuracoes.update` | `Prefecture\ConfigurationController@update` | `check.logged` | `v6/routes/basepro.php:175` (resource) |
| DELETE | `/cms/configuracoes/{id}` | `configuracoes.destroy` | `Prefecture\ConfigurationController@destroy` | `check.logged` | `v6/routes/basepro.php:175` (resource) |
| POST | `/cms/ajax/remove-arquivo-configuracao` | `remove.arquivo.configuracao` | `Prefecture\ConfigurationController@removeArquivo` | grupo ajax | `v6/routes/basepro.php:139` |
| GET | `/cms/datatable/configuracoes` | `datatable.configuracoes` | `Prefecture\DatatableController@get_configuracoes` | — | `v6/routes/basepro.php:451` |

Rotas de apoio usadas pela tela:

| Método | URI | Nome | Controller@ação | Arquivo:linha |
|---|---|---|---|---|
| GET | `/cms/modulos` | `modulos.index` | `Admin\ModulosController@index` | `v6/routes/basepro.php:37` |
| POST | `/cms/modulos` | `modulos.update` | `Admin\ModulosController@update` | `v6/routes/basepro.php:38` |

## 2. Rotas públicas (Portal) afetadas por este módulo

| Método | URI | Nome | Controller@ação | Efeito das chaves `site` |
|---|---|---|---|---|
| GET | `/selos` | `selos.index` | `Portal\SelosController@index` | Renderiza `selos_institucionais` |
| GET | `/` | `home` | `Portal\PortalController@index` | `noticias`, `ultimas_noticias`, `menu_interativo`, `links_rapidos` |
| GET | `/getColors` | `get-colors` | `Portal\HelperController@getColors` | Cores do tema |
| GET | `/getPrivacyBar` | `get-privacy-bar` | `Portal\HelperController@getPrivacyBar` | Barra LGPD |

Rotas em `v6/routes/web.php` (linhas 5, 207, 208).

O grupo `Route::middleware(['check.site.manutention'])` de `v6/routes/web.php`
envolve o portal inteiro: é o middleware que lê `pagina_em_manutencao`.

## 3. `ConfigurationController` — mapa de métodos

Arquivo: `v6/app/Http/Controllers/Prefecture/ConfigurationController.php` (856 linhas).

| Método | Linha aprox. | Usado por este módulo |
|---|---|---|
| `__construct(Configuration $configurationLog)` | 50 | Sim (registro de log) |
| `index()` | 61 | Não (categoria `outros`) |
| `create()` | 83 | Não |
| `store(ConfigurationRequest)` | 108 | Não |
| `edit(int $id)` | 152 | Não |
| `update(ConfigurationRequest, int $id)` | 179 | Não |
| `updateByCategory(ConfigurationRequest)` | 233 | **Sim** — endpoint único de gravação |
| `editByCategory(string $slug)` | 437 | **Sim** — renderização da tela |
| `configurationLog($msg)` | 533 | Sim (log de acesso) |
| `destroy($id)` | 551 | Não |
| `removeArquivo(Request)` | 566 | Sim (apagar logo/favicon) |
| `updateSelosInstitucionais(Request)` | 576 | **Sem rota** (código morto) |
| `createSelosInstitucionais(Request)` | 604 | **Sem rota** (código morto) |
| `deleteSelosInstitucionais()` | 646 | **Sem rota** (código morto) |
| `migrarCaminhosSelosAntigos()` (privado) | 679 | Sim (chamado no `updateByCategory`) |
| `gaKeys()` (protegido) | 716 | Sim |
| `dashboardKeys()` (protegido) | 729 | Sim |
| `extractGaData(Request, array)` (protegido) | 747 | **Sim** — bloco GA |
| `extractDashboardData(Request, array)` (protegido) | 819 | **Sim** — aba Dashboard |

## 4. Trechos críticos

### 4.1 Guarda de acesso na leitura

```php
public function editByCategory(string $slug)
{
    $reference = $acess = $slug;

    if ($slug == 'configuracoes')
        $reference = "site";
    if ($slug == 'temas')
        $acess = 'tema-do-portal';

    $configurations = $this->getConfiguration($reference);

    $category = CategoryConfiguration::where('slug', $reference)->first();
    $states = DB::table('states')->get();

    if (!has_access(["$acess.edit"])) {
        flash('Você não tem permissão para acessar este conteúdo')->warning();
        return redirect()->route('dashboard.index');
    }
```

Para o slug `configuracoes`, `$reference = 'site'` (categoria) e
`$acess = 'configuracoes'` (permissão `configuracoes.edit`).

### 4.2 Guarda de acesso na gravação

```php
public function updateByCategory(ConfigurationRequest $request): RedirectResponse
{
    if (!has_access(['configuracoes.edit'])) {
        flash('Você não tem permissão para salvar')->warning();
        return redirect()->route('dashboard.index');
    }
```

A gravação usa **sempre** `configuracoes.edit`, para qualquer categoria — o que
inclui `temas`, `outros` e `barra-privacidade`.

### 4.3 Geração do `config.json`

```php
if ($category->slug == 'site') {
    $request->app_env = $request->app_env == '1' ? 'local' : 'prod';
    $request->app_debug = $request->app_debug == '1' ? 'true' : 'false';

    $array = array(
        "0" => array(
            "mail_driver" => $request->mail_driver,
            "mail_host" => $request->mail_host,
            "mail_port" => $request->mail_port,
            "mail_username" => $request->mail_username,
            "mail_password" => $request->mail_password,
            "mail_encryption" => $request->mail_encryption,
            "mail_from_address" => $request->mail_from_address,
            "mail_from_name" => $request->mail_from_name
        ),
        "1" => array(
            "recaptcha_public_key" => $request->recaptcha_public_key,
            "recaptcha_private_key" => $request->recaptcha_private_key
        ),
        "2" => array(
            "app_env" => $request->app_env,
            "app_debug" => $request->app_debug,
        ),
    );

    $json = json_encode(array('config' => $array));

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

Formato gerado (exemplo de estrutura, com valores ilustrativos):

```json
{
  "config": [
    {"mail_driver":"smtp","mail_host":"mail.exemplo.gov.br","mail_port":"465","mail_username":"usuario","mail_password":"segredo","mail_encryption":"ssl","mail_from_address":"contato@exemplo.gov.br","mail_from_name":"Prefeitura"},
    {"recaptcha_public_key":"chave-publica","recaptcha_private_key":"chave-privada"},
    {"app_env":"prod","app_debug":"false"}
  ]
}
```

Consumidores (todos em `v6/config/`):

| Arquivo | Linha | Uso |
|---|---|---|
| `v6/config/app.php` | 6 | `$app_config = $data->config[2];` (`app_env`, `app_debug`) |
| `v6/config/mail.php` | 3 | `$mail_config = $data->config[0];` |
| `v6/config/recaptcha.php` | 3 | `$recaptcha_config = $data->config[1];` |

### 4.4 Loop de gravação

```php
foreach ($dataForm as $key => $value) {
    $config = Configuration::where('key', $key)->first();
    if (!$config) continue;
    if ($request->hasFile($key)) {
        $file = $request->file($key);
        $name = $this->getFileName($file, "logo");
        $fileOld = $config->value;
        if (!empty($fileOld)) {
            $file_old_in_storage = str_contains($fileOld, 'configuracoes');
            if ($file_old_in_storage == true) {
                Storage::delete($fileOld);
            }
        }
        $directory = storage_path('app/public/configuracoes');
        if (!File::exists($directory)) {
            File::makeDirectory($directory, 0755, true, true);
        }
        $image = Image::make($file->getRealPath());
        $image->save(storage_path('app/public/configuracoes/' . $name), 100);
        $image->save($directory . '/' . $name, 100);
        $config->value = 'configuracoes/' . $name;
    } else {
        $config->value = $value;
    }
    $config->save();
    activity()->performedOn($config)
        ->causedBy(Sentinel::getUser())
        ->withProperty('Detalhe', 'Foi atualizada uma configuração: Id:' . $config->id . ',Chave:' . $config->key . ',Valor:' . $config->value . ',Tipo:' . $config->type . ',Título:' . $config->title . ',Editada por:' . Sentinel::getUser()->name)
        ->log('Edição de configuração');
    Cache::pull('structureType');
    if (strpos($key, 'portal_transparencia_') === 0) {
        Cache::pull(PortalTransparenciaService::CACHE_KEY);
    }
}
```

Observações críticas:

- `Configuration::where('key',$key)->first()` grava **apenas a primeira**
  ocorrência de uma chave duplicada (não há índice único).
- Chaves sem registro correspondente são **silenciosamente ignoradas**
  (`if (!$config) continue;`).
- `Cache::pull('structureType')` roda para **cada** chave gravada, mesmo as que
  não têm relação com `structure_type`.

## 5. Form Request — `ConfigurationRequest`

Arquivo: `v6/app/Http/Requests/ConfigurationRequest.php`.

- `authorize()` retorna `true` — **não há autorização no Form Request**; a
  autorização é feita no controller.
- Na rota `update.configurations.by.category`, as regras são geradas
  dinamicamente:

```php
$dataForm = $request->except('_token');
$size = count($dataForm);
$keys = array_keys($dataForm);

$rules = [];
for ( $i = 0; $i < $size; $i++ ) {
    $config = Configuration::where('key', $keys[$i])->first();
    if (!empty($config)) {
        if ($config->notnull == true) {
            $required = 'required';
            $max = ($config->max_input != '') ? '|max:'.$config->max_input."|" : '';
            $min = ($config->max_input != '') ? '|min:'.$config->min_input.'' : '';
        } else {
            $required = ''; $max = ''; $min = '';
        }
    }
    $rules += [ $keys[$i] => $required.$max.$min];
}
```

- **Uma query por campo enviado** (N+1 clássico).
- A checagem da regra de `min` está condicionada a `max_input != ''` (bug):
  chaves com `max_input` vazio e `min_input` preenchido não recebem `min`.
- As 6 chaves de Dashboard/GA recebem **regra vazia** (`''`), e a validação real
  acontece no controller (`extractGaData` / `extractDashboardData`).
- Na rota `configuracoes.update` (CRUD unitário) há regras fixas para
  `codigo_tempo` (`required|numeric`).

## 6. Permissões exigidas

`has_access()` — `v6/app/Library/Helper/appHelpers.php:22`:

```php
function has_access($access = null)
{
    if (!Sentinel::check()) {
        return false;
    }

    if (!isset($access)) {
        return true;
    }

    if (Sentinel::inRole('super-admin')) {
        return true;
    }

    if (is_array($access)) {
        return Sentinel::check()->hasAnyAccess($access);
    }

    return Sentinel::check()->hasAccess([$access]);
}
```

| Ação | Permissão | Onde é checada |
|---|---|---|
| Ver a tela | `configuracoes.edit` | `editByCategory()` |
| Ver o item no menu | `configuracoes.index` **e** módulo ativo | `v6/resources/views/vendor/adminlte/page.blade.php:228` |
| Salvar | `configuracoes.edit` | `updateByCategory()` |
| Ver/editar abas Mail | `mail_recaptcha` | view |
| Ver abas SEO/Dashboard | `dashboard-config.index` | view |
| Gravar GA/Dashboard | `dashboard-config.edit` | `extractGaData()`, `extractDashboardData()` |

O middleware `v6/app/Http/Middleware/CheckRoutePermission.php`
(`check.route.permission`) traduz verbos: `edit` → `update`, `create` → `store`,
`delete` → `destroy`.

## 7. API Resources / formato de saída

- `updateByCategory` responde com **redirect** (HTML), não JSON.
- `removeArquivo` não devolve corpo (apenas salva `value = null`).
- `updateSelosInstitucionais`, `createSelosInstitucionais` e
  `deleteSelosInstitucionais` devolvem JSON (por exemplo
  `{"success":true,"message":"Selos criados com sucesso."}` ou
  `{"error":"Sem permissão"}`), mas **nenhuma rota os expõe** — ver
  [08-GAPS-E-DIVIDAS.md](08-GAPS-E-DIVIDAS.md).
- O datatable (`datatable.configuracoes`) devolve JSON no formato Yajra
  DataTables, alimentado pela view SQL `configuracoes`.
