# Temas e Cores — Regras de Negócio e Portabilidade

> **Slug do módulo:** `temas-e-cores` (tabela `modulos`, id 2)

## 1. Regras de negócio observáveis

| # | Regra | Onde está implementada |
|---|---|---|
| R1 | O portal só renderiza 6 templates: `modelo1`..`modelo6`. Qualquer outro valor na chave `layout` é rejeitado na gravação. | `ConfigurationController@updateByCategory` |
| R2 | A validação de R1 só dispara se `layout` for o **primeiro** campo enviado. | `if (isset($keys[0]) && $keys[0] == 'layout')` |
| R3 | As 4 cores são obrigatórias (`notnull = true`) e limitadas a 50 caracteres, com mínimo de 3. Não há validação de formato hexadecimal. | `ConfigurationRequest::rules()` + colunas `max_input`/`min_input` |
| R4 | `cor_cms` precisa ter um valor, mas o valor **não** é validado contra a lista de skins. Um valor inexistente produz um `<link>` para um CSS 404 (AdminLTE sem estilo). | `ConfigurationRequest` + `page.blade.php:5-7` |
| R5 | A skin válida é definida **apenas** pelo array `$temas_lte` no controller (`blue`, `black`, `purple`, `green`, `red`, `yellow`, `blue-light`, `black-light`, `green-light`, `red-light`, `yellow-light`). | `ConfigurationController`, `case 'temas'` |
| R6 | `background` é opcional; quando enviado, é salvo sem redimensionamento em `storage/app/public/configuracoes/` e referenciado como `configuracoes/<nome>`. | `ConfigurationController@updateByCategory` |
| R7 | `background_fixed` e `background_repeat` têm valor `'1'`/`'0'` (string) e são comparados por `=== '1'` nas views. | `modelo{1..6}/layout/layout.blade.php` |
| R8 | Se a view do template ativo não existir, o sistema grava `pagina_em_manutencao = 1` e aborta com 404. | `Admin\Controller::getTemplateActived()` |
| R9 | A quantidade de notícias da home depende do modelo (6/7/10−d/3/8−d/4). | `v6/app/Helpers/functions.php` (switch `$layout`) |
| R10 | Modelos 1, 2 e 6 compartilham o bloco de slides da home. | `PortalController.php:92` |
| R11 | No modelo 5, o grupo `tags` é gravado com `status = 0` (oculto) na reordenação; os demais elementos ficam com `status = 1`. | `LayoutController@orderLayout()` |
| R12 | No modelo 5, os valores antigos `0..3` da chave `secretarias-grid` são remapeados (`0`→`transparencia`, `1`→`secretaria`, `2`→`menus`, `3`→`transparencia`). | `LayoutController::edit()` |
| R13 | Campos `input-*` têm semântica fixa: `input-title-*` → coluna `title`, `input-columns*` → coluna `columns`, demais → coluna `style`. | `LayoutController::save_layout()` |

## 2. Acoplamentos

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

| Dependência | Natureza |
|---|---|
| `configurations` + `category_configurations` | Dados. Compartilhados com `../configuracoes-da-instituicao/`, `../lgpd/` e `../outras-configuracoes/`. |
| `ConfigurationController` | Controller compartilhado entre os 4 módulos de configuração. |
| `ConfigurationRequest` | Validação compartilhada. |
| `Configuration` (`App\Prefecture`) | Model compartilhado. |
| `getActualLayout()` / `getTemplateActived()` | Resolução do template ativo, usada por todo o portal. |
| `ContentPresenter`, `ExportsContentTrait`, `EnviosRepository`, `SwaggerController` | Consumidores das cores. |
| `ConfigHeaderServiceProvider` | Injeta `$configHeader` (com `background` e `cor_1..4`) nas views. |
| `ModulosServiceProvider` / tabela `modulos` | Controla a visibilidade do item de menu. |
| Tabela `layouts` | Structure **própria** do módulo (modelo 5). |

### Dependências em sentido inverso (quem depende deste módulo)

| Consumidor | O que consome |
|---|---|
| Portal público (todas as páginas) | `layout` (template), `cor_1..cor_4` (CSS), `background*` (`<body>`). |
| CMS (todas as telas AdminLTE) | `cor_cms` (skin + logo). |
| Exportações PDF | `cor_1`. |
| E-mails de avaliação | `cor_1`. |
| Swagger | `cor_2`. |
| PortalController / ContentPresenter / functions.php | Quantidade de notícias e classes CSS por modelo. |

## 3. Armadilhas técnicas

| Armadilha | Impacto |
|---|---|
| **N+1 no Form Request** | `ConfigurationRequest::rules()` e `::messages()` executam **uma query por campo enviado** na tabela `configurations`. |
| **`configurations.key` sem índice único** | `Configuration::where('key',$key)->first()` grava apenas a primeira ocorrência de uma chave duplicada. |
| **Chaves duplicadas gravam só a primeira** | Consequência direta do item acima: um `updateOrCreate` duplicado no seed também é silencioso. |
| **Query por render do AdminLTE** | `page.blade.php` consulta `cor_cms` 3× por página (CSS + 2 logos). |
| **Query por render do portal** | `head.blade.php` e `master.blade.php` consultam `cor_1..cor_4` (4 queries cada) em cada página. |
| **`->first()->value` sem guarda** | `head.blade.php`, `master.blade.php`, `page.blade.php` e `Admin\Controller::getTemplateActived()` quebram com erro fatal se a chave não existir. |
| **Validação de `layout` condicionada à posição** | R2 acima. |
| **Divergência de permissão leitura × gravação** | Leitura exige `tema-do-portal.edit`; gravação exige `configuracoes.edit`. |
| **Rotas do modelo 5 sem middleware** | `save.layout`, `orderLayout`, `uploadFile`, `downloadFile`, `edit.layout` e `update-module-status` não têm `check.logged` nem `has_access()` no controller. |
| **Typo no `$fillable` do model `Layout`** | `v6/app/Http/Model/Prefecture/Layout.php` declara `'kay'` (com `k`) e **omite** `title`, `style`, `columns`. Como o controller usa `update()`/`updateOrInsert`, o impacto é baixo, mas `create()`/`fill()` perderiam esses campos. |
| **`Layout` sem `protected $table`** | Eloquent infere `layouts` a partir do nome da classe — funciona, mas é implícito. |
| **Chaves `secretarias-grid` e `preview`/`tags` created por updateOrInsert** | `secretarias-grid` pode não existir (não está no banco de referência). |
| **`$configHeader['background']` com barra invertida** | As views do portal usam `url('/storage/{{ ... }}')` com barra fixa; valor persistido sem barra (`configuracoes/<nome>`). |
| **Duplo `$image->save()`** | O upload salva o arquivo em `storage/app/public/configuracoes/` **duas vezes** (mesma imagem, mesmo caminho) — `Image::save()` do Intervention não é idempotente quanto ao custo de CPU. |
| **`background` ignora o redimensionamento** | O `if ($key != 'imagem-acesso-rapido' && $key !== 'background')` deixa o plano de fundo com tamanho original —-upload de imagem muito pesada. |
| **View SQL `configuracoes` sem filtro** | O datatable do CMS lista as 9 chaves de `temas` junto com as de todas as outras categorias. |

## 4. Código duplicado / copiado

| Trecho | Observação |
|---|---|
| `getConfiguration($reference)` (`GetInformationsPrefectureTrait`) | Mesma consulta para as 4 categorias; o filtro é o `slug` da categoria. |
| `updateByCategory()` | **Um único endpoint** grava as 8 categorias; o `switch`/`if` só muda o tratamento de `site` (GA/Dashboard/`config.json`) e dos selos. |
| `v6/resources/views/prefecture/configurations/temas/form.blade.php` | Idêntico à v5. |
| `v6/resources/views/prefecture/configurations/temas/edit.blade.php` | Idêntico à v5. |
| `v6/resources/views/prefecture/configurations/temas/modal.blade.php` | Idêntico à v5. |
| Bloco `background` do `<body>` | Copiado literalmente nos 6 templates (`modelo1`..`modelo6`). |

## 5. Onde a v6 divergiu da v5

| Arquivo | Divergência |
|---|---|
| `v6/app/Http/Controllers/Prefecture/ConfigurationController.php` | O bloco `case 'temas'` (com `$temas_lte` e `$templatesPortal`) é **idêntico** entre v5 e v6. A divergência do arquivo está em outros blocos (Dashboard/GA, selos, `config.json`). |
| `v6/resources/views/prefecture/configurations/temas/select_layout.blade.php` | Divergente: a v6 reindentou o bloco e **moveu o `@if (has_access(['layouts']))` para dentro**, involve o `<div id="cp2">` e o `@endforeach` no `@if`; a v5 mantinha o `has_access` no nível do `@include`. O resultado observável é o mesmo. |
| `v6/app/Http/Controllers/Prefecture/LayoutController.php` | Divergente: a v6 adicionou `updateModuleStatus()`, o `legacyMap` de `secretarias-grid`, a leitura de `category_contents` (`transparencia-fiscal`) e de `menus`, a coluna `columns` no `get()`, o tratamento de `input-title-carousel-noticias` e `input-columnsTransparenciaFiscal*`, e `status = 0` para o grupo `tags`. |
| `v6/database/seeds/ConfigurationsTableSeeder.php` | A v6 usa `updateOrCreate` com bloco "Temas e cores" agrupado; a v5 dispersava as mesmas chaves. |

## 6. Recomendações de portabilidade (curtas)

1. Não carregar a lista de skins num array de controller — criar um enum `CmsSkin`.
2. Validar cores com regex (`/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/`) e converter para `Color`.
3. Cachear `cor_cms` e `cor_1..cor_4` (chave de cache versionada por `updated_at`).
4. Extrair a resolução do template ativo para um serviço único (`ThemeResolver`), consumido por
   `PortalController`, `ContentPresenter` e `functions.php`.
5. Renomear o arquivo CSS do modelo 5 para include o `id` do tenant/município.
6. Unificar o bloco de `<body>` dos 6 templates em um partial Blade.

## 7. Checklist de paridade funcional (v6 → v7)

| # | Comportamento a preservar | Como verificar na v7 |
|---|---|---|
| P1 | Trocar o template muda a home renderizada | Teste de feature por modelo (6 casos) |
| P2 | As 4 cores chegam ao `<head>` como `--cor-1..--cor-4` | Assert no HTML da resposta |
| P3 | `cor_cms` troca a skin e o logo do CMS | Assert no `<link>` e no `src` do logo |
| P4 | Plano de fundo fixo/repetido conforme os flags | Assert no `<style>` do `<body>` |
| P5 | `GET /getColors` devolve as 4 cores | Teste de endpoint |
| P6 | Quantidade de notícias por modelo | Assert na contagem de itens na home |
| P7 | Modelo 5: reordenar, ocultar, renomear, colunas e CSS | Testes de feature do `LayoutBlock` |
| P8 | Grupo `tags` sempre salvo com `status = 0` | Assert no banco após `orderLayout` |
