# LGPD (Barra de Privacidade) — Regras e Portabilidade

> **Versão analisada:** `v6`
> **Slug do módulo:** `lgpd`

---

## 1. Regras de negócio

| # | Regra | Onde está implementada |
|---|---|---|
| R1 | A barra só aparece se o módulo `lgpd` estiver **ativo** (`modulos.status = 1`). | `v6/resources/views/portal/layouts/barra-privacidade/barra-privacidade.blade.php:2-3` |
| R2 | A barra só aparece se `barra-privacidade-exibir` for verdadeiro. | mesma view, `:4-5` |
| R3 | A barra só aparece se a chave `barra-privacidade` existir (mesmo com valor vazio, o `isset` é verdadeiro). | mesma view, `:6` |
| R4 | O texto da barra é HTML renderizado sem escape (`{!! !!}`). | mesma view, `:14` |
| R5 | O botão de aceite grava o cookie `acceptCookies=true` com validade em **dias** = `barra-privacidade-expiracao` (fallback 7). | `v6/public/assets/js/custom/portal/barra-privacidade.js` |
| R6 | O aceite dispara o evento de janela `cookieAlertAccept` e remove a classe `show` após 1000 ms. | idem |
| R7 | O prazo de expiração é lido por um endpoint público (`GET /getPrivacyBar`) no momento do clique. | `v6/app/Http/Controllers/Portal/HelperController.php` |
| R8 | A cor do texto do botão é lida do model (categoria da LGPD) e aplicada em `.botao-privacidade-color`; a borda usa `--cor-1` (categoria `site`). | `v6/resources/views/portal/layouts/barra-privacidade/head.blade.php:7` |
| R9 | A leitura da tela do CMS exige `barra-privacidade.edit`; a gravação exige `configuracoes.edit`. | `v6/app/Http/Controllers/Prefecture/ConfigurationController.php` |
| R10 | O item de menu "LGPD" exige `barra-privacidade.index` **e** `$modulos['lgpd']`. | `v6/resources/views/vendor/adminlte/page.blade.php:244` |
| R11 | Salvar redireciona de volta para `edit.configuracoes.by.category` com o slug da categoria. | `ConfigurationController@updateByCategory` |

## 2. Estado da configuração (transições)

O módulo não tem máquina de estados própria; o estado é a combinação:

| `modulos.status` | `barra-privacidade-exibir` | Resultado no portal |
|---|---|---|
| `0` (padrão da migration) | 0 ou 1 | Barra ausente |
| `1` | `0` | Barra ausente |
| `1` | `1` | Barra renderizada; exibição condicionada ao cookie |

Transição típica de ativação completa (duas telas do CMS):

1. `POST /cms/modulos` com `lgpd.status = 1` (tela `/cms/modulos`,
   `Admin\ModulosController@update`, que faz `Cache::pull('modulos')`).
2. `POST /cms/update/configuration` com `barra-privacidade-exibir = 1`,
   `barra-privacidade = <texto>`, `barra-privacidade-expiracao = <dias>`,
   `barra-privacidade-botao = <cor>`.

> O `Cache::pull('modulos')` invalida o cache do `ModulosServiceProvider`; já o
> `ConfigHeaderServiceProvider` **não tem cache** (consulta ao banco a cada
> requisição), logo a mudança de valor da faixa é refletida imediatamente.

## 3. Acoplamentos

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

| Depende de | Onde | Tipo |
|---|---|---|
| Cores `cor_1..cor_4` (categoria `site`) | `head.blade.php:2-5` | **forte** — sem `cor_1` a view quebra (`first()->value` sobre null). |
| `$modulos` (array com `status` por slug) | `ModulosServiceProvider` | forte — sem o provider, `$modulos` é undefined e o `isset` falha (barra oculta). |
| `$configurations` (3 chaves) | `ConfigHeaderServiceProvider` | forte — `isset($configurations['barra-privacidade-exibir'])` sem o provider oculta a barra silenciosamente. |
| Recurso `resources.slug = 'barra-privacidade'` | `ResourcesTableSeeder.php:477` | médio — base das permissões do CMS. |
| `Configuration` model + `ConfigurationRequest` | CMS | forte. |

### 3.2 Dependentes (quem depende do módulo)

| Depende de | Onde |
|---|---|
| `Portal\HelperController@getPrivacyBar` | `v6/public/assets/js/custom/portal/barra-privacidade.js` |
| Todos os layouts do portal (modelo1..6) | includes de head/scripts/rodapé |
| `Admin\ModulosController` (cache `modulos`) | `v6/app/Providers/ModulosServiceProvider.php` |

### 3.3 Sobreposição com os outros módulos de configuração

O módulo `lgpd` **não tem** controller, model, tabela ou rota própria. Ele
compartilha, literalmente:

| Elemento compartilhado | LGPD | `../configuracoes-da-instituicao/` | `../temas-e-cores/` | `../outras-configuracoes/` |
|---|---|---|---|---|
| Tabela | `configurations` | idem | idem | idem |
| Model | `App\Prefecture\Configuration` | idem | idem | idem |
| Controller | `Prefecture\ConfigurationController` | idem | idem | idem |
| Endpoint de gravação | `update.configurations.by.category` | idem | idem | idem |
| Form Request | `ConfigurationRequest` | idem | idem | idem |
| View de listagem | `prefecture.configurations.barra-privacidade.edit` | `prefecture.configurations.configuracoes.edit` | `prefecture.configurations.temas.edit` | `prefecture.configurations.index` |
| Categoria | `barra-privacidade` | `site` | `temas` | `outros` |
| Recurso | `barra-privacidade` | `configuracoes` | `tema-do-portal` / `template-do-portal` | `outros` |
| Módulo (`modulos.slug`) | `lgpd` (status 0) | `configuracoes-da-instituicao` (status 1) | `temas-e-cores` (status 1) | `outras-configuracoes` (status 1) |
| Slug de rota | `barra-privacidade` | `configuracoes` | `temas` | `outros` |
| Permissão de leitura | `barra-privacidade.edit` | `configuracoes.edit` | `tema-do-portal.edit` | `outros.edit` |
| Permissão de gravação | `configuracoes.edit` | `configuracoes.edit` | `configuracoes.edit` | `configuracoes.edit` |

Ver `08-GAPS-E-DIVIDAS.md` para a análise completa da sobreposição.

## 4. Armadilhas (armadilhas técnicas found no legado)

| # | Armadilha | Impacto no módulo LGPD |
|---|---|---|
| A1 | `configurations.key` **sem índice único**; `updateByCategory` usa `Configuration::where('key',$key)->first()` | Se a chave for duplicada (não há unicidade), só a primeira linha é gravada. |
| A2 | N+1 no Form Request: `ConfigurationRequest::rules()` e `::messages()` rodam uma query por campo enviado | 4 queries extras por submit da LGPD. |
| A3 | `ConfigurationRequest` monta as regras lendo `configurations` do banco **antes** da gravação; se a chave não existir, `$required/$max/$min` são reaproveitados do campo anterior (variáveis não reinicializadas no loop) | Regras podem ser herdadas de outro campo. |
| A4 | `category_configurations_id` não é validado contra as chaves enviadas | Um POST forjado grava chaves de outra categoria com o redirecionamento da LGPD. |
| A5 | Permissão assimétrica: leitura usa `<recurso>.edit`, gravação usa sempre `configuracoes.edit` | Quem tem `configuracoes.edit` grava a LGPD **sem** ter `barra-privacidade.edit`. |
| A6 | `echo "JSON file created successfully..."` no controller quando a categoria é `site` | Não afeta a LGPD (categoria ≠ `site`), mas no container com `output_buffering = 0` emite cabeçalhos antes do redirect. |
| A7 | `head.blade.php` usa `first()->value` sem guarda | Se `cor_1` (categoria `site`) não existir, erro fatal em **todas** as páginas do portal. |
| A8 | `ConfigHeaderServiceProvider` carrega uma lista **literal** de chaves | `barra-privacidade-botao` não está na lista → lê do model; se alguém criar uma 5ª chave da barra, ela não chegará ao portal. |
| A9 | `ConfigurationsComposer` existe e foi feito exatamente para as 3 chaves da barra, mas **não está registrado** em nenhum provider | Código morto; a leitura é feita pelo `ConfigHeaderServiceProvider`. |
| A10 | `modulos.status = 0` na migration | A barra nunca aparece em instalação nova sem ação manual. |
| A11 | `Cache::rememberForever('modulos', fn () => Modulos::all()->pluck('status','slug'))` | Estado do módulo cacheado para sempre; só o `Admin\ModulosController` invalida (`Cache::pull`). Alterações diretas no banco não surtem efeito. |
| A12 | CSS `assets/css/portal/privacybar/style.css` referenciado e inexistente | 404 por página renderizada com a barra. |
| A13 | Cookie gravado por JS sem `Secure`/`SameSite` | Sem `SameSite`, o cookie acompanha requisições cross-site (CSRF de consentimento trivial). |
| A14 | `getCookie`/`setCookie` copiados do w3schools dentro do IIFE | Código duplicado; `myFetch` não trata erro → `bar_config.value` pode lançar `TypeError`. |
| A15 | `acceptCookies.onclick` sem verificação de existência do botão | Protegido apenas pelo `if (!cookieAlert) return;` anterior. |
| A16 | `max_input = 999` / `min_input = 1` em campos de cor e de prazo | Herdados do template genérico; sem efeito prático (as regras são ignoradas porque `notnull = false`), mas o `maxlength="999"` no input de dias limita a 3 dígitos. |
| A17 | View SQL `configuracoes` sem filtro por categoria | O datatable do CMS lista as 4 chaves da LGPD junto de todas as outras (87 configurações ativas). |
| A18 | `{!! !!}` no texto da barra | HTML bruto do Summernote renderizado no portal (XSS Stored por operador). |

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

| Arquivo | Situação |
|---|---|
| `v6/resources/views/prefecture/configurations/barra-privacidade/edit.blade.php` | **idêntico** à v5 (diff vazio). |
| `v6/resources/views/prefecture/configurations/barra-privacidade/form.blade.php` | **idêntico** à v5. |
| `v6/public/assets/js/custom/portal/barra-privacidade.js` | **idêntico** à v5. |
| `v6/resources/views/portal/layouts/barra-privacidade/scripts.blade.php` | **idêntico** à v5. |
| `v6/resources/views/portal/layouts/barra-privacidade/head.blade.php` | **divergente**: a v6 acrescenta o bloco `.barradeprivacidade` (o CSS inline saiu da view da barra para o head). |
| `v6/resources/views/portal/layouts/barra-privacidade/barra-privacidade.blade.php` | **divergente**: a v6 adiciona a checagem `$modulos['lgpd']`, remove o `<style>` inline e adiciona o `@section('other-headers')` com o link do CSS inexistente. |
| `v6/app/Http/Model/Prefecture/Configuration.php` | **idêntico** à v5. |

## 6. Onde a v6 divergiu da v5 (e por quê, quando descobrível)

| Divergência | Detalhe | Motivo provável |
|---|---|---|
| Chave de módulo | A v6 introduz `modulos` e condiciona a renderização a `modulos.status`. | Unificação da feature-toggle dos módulos do CMS (tema "módulos" de 2024-11). |
| Remoção do `PrivacyBarSeeder` | `v5/database/seeds/PrivacyBarSeeder.php` **existe na v5 e foi removido na v6**; as 4 chaves passaram para `v6/database/seeds/ConfigurationsTableSeeder.php`. | Consolidação dos seeds de configuração em um único seeder com `updateOrCreate`. |
| Valor padrão do texto | Na v5 o texto padrão era `'Texto Barra de privacidade'` (descrição) e o valor era vazio; na v6 o `DefaultSettingSeeder` aplica o texto institucional completo. | Ajuste de conteúdo. |
| Ordem das chaves | v5: 1..4 (exibir=1, texto=2, expiração=3, botão=4); v6: 64..67 (texto=64, expiração=65, exibir=66, botão=67). | Reordenação global das configurações. |
| Composição do array `$configurations` | v5 devolvia array indexado; v6 monta array por chave (`$configurations[$value->key]`). | Permite `isset($configurations['chave'])`. |
| CSS inline | Migrou da view da barra para `head.blade.php` na v6. | Organizar estilos no head. |
| Link de CSS externo | Adicionado na v6, apontando para arquivo inexistente. | Provável refactor incompleto (o arquivo não foi versionado). |
| `ConfigurationsComposer` | Classe nova na v6, **não registrada**. | Intenção de desacoplar a leitura das chaves da barra do `ConfigHeaderServiceProvider`. |
| Nova checagem de módulo no menu do CMS | `&& $modulos['lgpd']` no item de menu. | Consistência com os demais módulos. |

## 7. Portabilidade (o que não pode mudar na reescrita)

| Item | Regra de equivalência funcional |
|---|---|
| Nome da chave de configuração | `barra-privacidade`, `barra-privacidade-exibir`, `barra-privacidade-expiracao`, `barra-privacidade-botao` — usados em seeds, no `config.json` de outras instalações e no JS. |
| Nome do cookie | `acceptCookies` — o portal já tem cookies gravados nos navegadores dos cidadãos; mudar o nome reinicia o pedido de aceite. |
| Semântica do valor de expiração | Número de **dias** (inteiro). |
| Slug da rota do CMS | `barra-privacidade` (aparece em links e em auditorias). |
| Slug do módulo | `lgpd` (chave do cache `modulos` e do item de menu). |
| Evento `cookieAlertAccept` | Disparado no `window`; outros scripts podem depender dele. |
| Endpoint `GET /getPrivacyBar` | Consumido pelo JS já em produção. |
| Comportamento do módulo inativo | Por padrão, `status = 0` na instalação — mas na v7 isso deve ser **decidido conscientemente** (a v6 entrega um portal sem barra). |
