# Módulo Configurações (`Infraestrutura/Configuracoes`)

> Contrato de leitura do módulo (ADR-004/ADR-014) — mantenha sincronizado com o código.

## Propósito

Editar no CMS as **configurações tipadas por seção** do Core (ADR-046). Nesta fase são editáveis
`institution` (Geral), `theme` (Aparência), `cms` (Painel) e `support` (Contato do fornecedor,
P3.S6); as demais seções do schema (`seo`, `system`) são declaradas pelo Core e não têm rota
pública nesta fatia.

`theme` **não** é editável como JSON: o formulário tem um controle por token do `ThemeSchema`
(`color_primary`, `color_accent`, `color_link`, `radius`, `font_content`), valida com
`ThemeSchema::accept()` e grava `theme.portal` preservando `theme.cms`/`theme.widgets`.

`cms` também **não** é JSON editável: `ConfiguracoesShellEditor` expõe um controle por campo —
`title`, `logo`, `color_primary`, `color_accent`, checkbox por região (`header`/`footer`/`sidebar`),
`select` de `sidebar_position` (`left|right|orbital`) e de `sidebar_width` (escala fechada de
`config/cms.php`). A validação usa `CmsShellSchema` (a **mesma** verdade do `CmsShellResolver`) e
grava a seção inteira pela porta `Settings`; campo de identidade vazio **herda** a camada de baixo
(`config`/tema do tenant).

`logo` aceita caminho same-origin ou URL `https://`; **não** resolve `media_id` do catálogo nesta
fatia (um UUID é recusado pelo editor e ignorado pelo resolver — nunca vira `src` inválido). Cor só
`oklch()`: sem conversor de cor no PHP e sem JS inline, hex/rgb recebem 422 em vez de conversão.

- **Grupo:** `Infraestrutura` (organizacional — não entra no token, ADR-014).
- **Área:** CMS.
- **Ativação:** dado em `core_module_discovery_settings`; `php artisan core:module:activate Configuracoes [--disable]` (ADR-034).

## Estrutura

```
src/modules/Infraestrutura/Configuracoes/
├── ModuleProvider.php
├── MODULE.md
├── Logic/
│   ├── ConfiguracoesSections.php
│   ├── ConfiguracoesThemeEditor.php
│   ├── ConfiguracoesShellEditor.php
│   ├── Controllers/Cms/{Index,Edit,Update}Controller.php
│   └── Requests/UpdateConfiguracoesRequest.php
├── Routes/cms.php
├── Layout/Views/cms/{index,form,configuracoes-theme,configuracoes-shell}.blade.php
├── Lang/{pt_BR,en,es,fr}/
└── Test/{ConfiguracoesCmsTest,ConfiguracoesThemeTest,ConfiguracoesShellTest}.php
```

## Token canônico

**`configuracoes`** — rotas `configuracoes.cms.*`, classes `Configuracoes*`, views `configuracoes::cms.*`
e chaves de tradução no namespace `configuracoes`. Verificação: `tools/name-check.sh --grep configuracoes`.

## Dependências

- **Core:** `Core\Contracts\Settings`, `Core\Settings\SettingsSchema`, `Core\Access\Permission`,
  `Core\Settings\SettingsValueInvalid`.
- **Layout:** `Layout\Engine\ThemeSchema` (regras/tokens), `Layout\Engine\ThemeResolver`
  (tokens salvos/efetivos) e `Layout\Engine\ThemeTokens` — o módulo não escreve CSS.
  `Layout\Cms\Shell\CmsShellSchema`/`CmsShellResolver` dão a forma fechada e os valores efetivos do
  shell (identidade, regiões, posição, largura) — o módulo não duplica regex de cor/URL.
- **Services:** nenhum.
- **Outros módulos:** nenhum import.
- **Tabela:** `core.core_settings` (propriedade do Core; o módulo não escreve SQL direto).

## Rotas

| Nome | Método | URI | Controller | Acesso |
|---|---|---|---|---|
| `configuracoes.cms.index` | GET | `/configuracoes` | `Cms\ConfiguracoesIndexController` | `configuracoes.ver` |
| `configuracoes.cms.edit` | GET | `/configuracoes/{section}` | `Cms\ConfiguracoesEditController` | `configuracoes.ver` ou `configuracoes.editar` |
| `configuracoes.cms.update` | PUT | `/configuracoes/{section}` | `Cms\ConfiguracoesUpdateController` | `configuracoes.editar` |

A rota aceita somente as seções de `ConfiguracoesSections::EDITABLE` (`institution`, `theme`,
`cms` e `support`); qualquer outra seção responde 404. A autorização de escrita também acontece no
`FormRequest`, **antes** da validação. Valor inválido de token do tema ou de campo do shell
responde **422** com o rótulo do campo e não grava nada (sem escrita parcial); um valor válido
invalida `settings.theme`/`settings.cms`, então o próximo `GET /theme.css` muda o `ETag` e o
próximo shell renderizado já usa a nova estrutura.

O `cms` grava a seção inteira (todos os campos do schema estão no formulário): região vira
`{região: {enabled: bool}}`, posição/largura vêm de listas fechadas e campo de identidade vazio é
omitido (herda `config/cms.php`/tema do tenant). Região desligada remove o landmark; a ordem do DOM
acompanha a posição (foco = ordem visual, ADR-036).

## Permissões

`configuracoes.{ver|editar}` declaradas em `ModuleProvider::config()` e aplicadas com
`Permission::authorize()`/`allows()` (deny-by-default, ADR-033).

## Eventos e observabilidade

- **Emite indiretamente:** a escrita do Core publica `core.settings.updated` (auditoria + log) e o
  span Core `core.settings.write` (ADR-024/ADR-046). O módulo não escreve na trilha por conta própria.
- **Não consome** eventos nesta fase.

## Layout

- `cms/index`, `cms/form`, `cms/configuracoes-theme` e `cms/configuracoes-shell` usam **um único**
  `<x-layout::cms>`; o módulo preenche apenas o conteúdo.
- Componentes do shell: `fieldset`, `form-errors`, `data-table`, `form-group` e `button`.
- A pré-visualização do tema carrega o `/theme.css` já publicado (same-origin) via `@push('styles')`
  e desenha a amostra com as classes do design system (`.theme-preview` em `shell.css`); sem
  `<style>`/`style=` inline e sem cache-busting aleatório.
- O editor do shell não tem campo JSON: identidade em `form-group`, regiões em checkbox com
  `<fieldset>/<legend>` (título, `aria-describedby` para ajuda/erro), posição/largura em `select`.
  Nenhum `<style>`/`style=`/`<script>` inline.

## Comandos

```bash
php artisan core:module:activate Configuracoes
php artisan core:module:activate Configuracoes --disable
php artisan core:settings:show institution
```

## Convenções

- [x] `declare(strict_types=1)`; DI; sem traits/abstract/generics.
- [x] Nenhuma chave fora do schema; nenhum endpoint de chave avulsa.
- [x] 4 locales no `Lang/` com paridade.
- [x] Sem JS obrigatório; erros associados ao controle e resumo `role="alert"`.
