# LGPD (Barra de Privacidade) — Views e UX

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

---

## 1. Inventário de views

### 1.1 Portal (público)

| Arquivo | Função |
|---|---|
| `v6/resources/views/portal/layouts/barra-privacidade/barra-privacidade.blade.php` | Markup da faixa + `<link>` do CSS (inexistente). |
| `v6/resources/views/portal/layouts/barra-privacidade/head.blade.php` | CSS inline (variáveis de cor + `.barradeprivacidade`) e leitura de `barra-privacidade-botao`. |
| `v6/resources/views/portal/layouts/barra-privacidade/scripts.blade.php` | Tag `<script>` que carrega o JS da barra. |

### 1.2 CMS (operador)

| Arquivo | Função |
|---|---|
| `v6/resources/views/prefecture/configurations/barra-privacidade/edit.blade.php` | Tela "LGPD": extends `admin.layouts.app`, inclui o form, botão de registro e link "Voltar". |
| `v6/resources/views/prefecture/configurations/barra-privacidade/form.blade.php` | Formulário genérico (loop sobre `$configurations`) + Summernote + colorpicker. |

## 2. Inclusões nos templates do portal

| Template | head | scripts | barra (rodapé) |
|---|---|---|---|
| `v6/resources/views/portal/layouts/modelo1/layout/layout.blade.php:10,34` | sim | sim | `v6/resources/views/portal/layouts/modelo1/layout/footer.blade.php:16` |
| `v6/resources/views/portal/layouts/modelo2/layout/layout.blade.php:10,34` | sim | sim | `v6/resources/views/portal/layouts/modelo2/layout/footer.blade.php:17` |
| `v6/resources/views/portal/layouts/modelo3/layout/layout.blade.php:8,33` | sim | sim | `v6/resources/views/portal/layouts/modelo3/layout/footer.blade.php:19` |
| `v6/resources/views/portal/layouts/modelo4/layout/layout.blade.php:19,33` | sim | sim | `v6/resources/views/portal/layouts/modelo4/layout/footer.blade.php:25` |
| `v6/resources/views/portal/layouts/modelo5/layout/layout.blade.php:9,43` | sim | sim | `v6/resources/views/portal/layouts/modelo5/layout/footer/footer.blade.php:97` |
| `v6/resources/views/portal/layouts/modelo6/layout/layout.blade.php:9,48` | sim | sim | `v6/resources/views/portal/layouts/modelo6/layout/footer.blade.php:113` |

Ou seja: a barra é oferecida em **todos** os modelos de portal e em todas as
páginas, pois head/scripts/rodapé são parciais do layout.

## 3. Markup da faixa (view real)

`v6/resources/views/portal/layouts/barra-privacidade/barra-privacidade.blade.php`:

```blade
@if(
    isset($modulos['lgpd']) &&
    $modulos['lgpd'] &&
    isset($configurations['barra-privacidade-exibir']) &&
    $configurations['barra-privacidade-exibir'] == true &&
    isset($configurations['barra-privacidade'])
)
    <div class="barradeprivacidade">
        <div class="container">
            <div class="row justify-content-center">
                <div class="col-10 py-4">
                    <div class="row align-items-center">
                        <div class="col-12 col-md-10 my-2">
                            {!! $configurations['barra-privacidade'] !!}
                        </div>
                        <div class="col-12 col-md-2">
                            <button class="btn btn-success acceptcookies bg-color-1 botao-privacidade-color" style="cursor: pointer; font-weight: 600;">Ok, entendi!</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    @section('other-headers')
        <link href="{{ asset('assets/css/portal/privacybar/style.css') }}" rel="stylesheet" type="text/css">
    @endsection
@endif
```

Condições de exibição (todas necessárias):

1. `isset($modulos['lgpd'])` — a chave existe no array de módulos.
2. `$modulos['lgpd']` — o módulo está **ativo** (`modulos.status = 1`).
3. `isset($configurations['barra-privacidade-exibir'])`.
4. `$configurations['barra-privacidade-exibir'] == true`.
5. `isset($configurations['barra-privacidade'])`.

> O texto é impresso com `{!! !!}`: HTML bruto vindo do Summernote, sem escape.
> Um operador com permissão de edição de configurações pode injetar scripts
> no portal (risco de XSS Stored, limitado a operadores autenticados).

## 4. Estilos (`head.blade.php`)

`v6/resources/views/portal/layouts/barra-privacidade/head.blade.php` faz
`@inject('config', 'App\Prefecture\Configuration')` e lê 4 cores + a cor do
botão direto do model:

```blade
@inject('config', 'App\Prefecture\Configuration')
@php
    $cor_1 = $config->where('key', 'cor_1')->first()->value;
    $cor_2 = $config->where('key', 'cor_2')->first()->value;
    $cor_3 = $config->where('key', 'cor_3')->first()->value;
    $cor_4 = $config->where('key', 'cor_4')->first()->value;
    $cor_t_botao_b_privacidade = $config->where('key', 'barra-privacidade-botao')->first();
@endphp
```

Trechos relevantes do CSS inline:

```css
.barradeprivacidade {
    position: fixed;
    width: 100%;
    left: 0;
    bottom: 0;
    background-color: rgba(255,255,255,0.9);
    box-shadow: 0 0 20px rgba(0,0,0,0.2);
    z-index: 10;
    transform: translateY(100%);
    transition: all 500ms ease-out;
}

.barradeprivacidade.show {
    opacity: 1;
    visibility: visible;
    transform: translateY(0%);
    transition-delay: 1000ms;
}

.botao-privacidade-color {
    border-color: var(--cor-1) !important;
    color: {{isset($cor_t_botao_b_privacidade->value) ? $cor_t_botao_b_privacidade->value : ""}};
}
```

Notas:

- As 5 queries por página (`cor_1..cor_4`, `barra-privacidade-botao`) rodam a
  **cada renderização** do `head` (sem cache).
- `cor_1..cor_4` vêm da categoria `site` (módulo
  `../configuracoes-da-instituicao/`); `barra-privacidade-botao` vem da
  categoria `barra-privacidade` e **não** é carregada pelo
  `ConfigHeaderServiceProvider`.
- O `first()->value` sem guarda: se `cor_1` não existir, erro fatal.

### 4.1 CSS referenciado e inexistente

A view aponta para `assets/css/portal/privacybar/style.css`. Esse arquivo
**não existe** em `v6/public/assets/css/portal/` nem em
`v5/public/assets/css/portal/` (conferido por listagem de diretório e por
`find` por `*privacybar*`: nenhum resultado). O pedido **404** em toda página
que renderiza a barra; o estilo funcional vem do `<style>` inline.

## 5. JavaScript da barra

`v6/public/assets/js/custom/portal/barra-privacidade.js` (idêntico na v5):

```js
(function () {
    "use strict";
    var cookieAlert = document.querySelector(".barradeprivacidade");
    var acceptCookies = document.querySelector(".acceptcookies");

    if (!cookieAlert) return;
    cookieAlert.offsetHeight;

    // Mostrar o alerta se não conseguirmos encontrar o cookie "aceitar Cookies"
    if (!getCookie("acceptCookies")) cookieAlert.classList.add("show");

    // Ao clicar no botão concordar, voce aceita por 7 dias
    acceptCookies.onclick = async function () {
        let bar_config = await myFetch('/getPrivacyBar');
        setCookie("acceptCookies", true,
            bar_config.value ? bar_config.value : 7
        );

        setTimeout(function () {
            cookieAlert.classList.remove("show");
        }, 1000);

        // despachar o evento de aceitação
        window.dispatchEvent(new Event("cookieAlertAccept"))
    };

    // Funções de cookies da w3schools
    function setCookie(cname, cvalue, exdays) {
        var d = new Date();
        d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
        var expires = "expires=" + d.toUTCString();
        document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
    }

    // funcao getCookie omitida no trecho
})();
```

### 5.1 Endpoints, eventos e cookies

| Item | Valor |
|---|---|
| Endpoint chamado | `GET /getPrivacyBar` (via `myFetch()`, `v6/public/assets/js/custom/utilities/functions.js:64`) |
| Cookie gravado | `acceptCookies=true`, com `expires` calculado e `path=/` |
| Atributos ausentes | `Secure`, `SameSite`, `HttpOnly` (impossível via JS), `domain` |
| Evento disparado | `cookieAlertAccept` no `window` |
| Fallback de prazo | 7 dias quando a resposta vier sem `value` |
| Efeito colateral | `myFetch` faz `fetch` sem `credentials`; a resposta é JSON cru |

### 5.2 Acessibilidade e robustez do JS

- Se o navegador tiver JS desabilitado, o `<noscript>` do `head.blade.php`
  avisa ("Seu navegador não suporta javascript!") e a faixa permanece oculta
  (sem a classe `show`).
- `acceptCookies.onclick` é atribuído sem verificar se o botão existe — mas o
  `if (!cookieAlert) return;` anterior protege o caso em que a barra não é
  renderizada (módulo inativo / configuração desligada).
- O `myFetch` engole erros (retorna `undefined`), e então
  `bar_config.value` lançaria `TypeError` — o cookie **não** seria gravado e o
  citizen veria a barra voltar ao recarregar.

## 6. Tela do CMS

### 6.1 `edit.blade.php` (idêntico na v5)

```blade
@extends('admin.layouts.app')

@section('content')

    <div class="box box-primary">
        <div class="box-header with-border">
            <h3 class="box-title">LGPD</h3>
        </div>
        <div class="row">
            @include('prefecture.configurations.barra-privacidade.form')
        </div>
        <div class="box-footer">
            <div class="row">

                <div class="col-sm-6 col-xs-6 d-inline-block m-0 ">
                    <button class="btn btn-primary btn-flat" id="btn-submit" >
                        {{ trans('adminlte::adminlte.register') }}
                    </button>
                </div>

                <div class=" col-sm-6 col-xs-6 d-inline-block pt-2 text-right align-middle">
                    <a class="align-middle" href="{{ URL::previous() }}">Voltar</a>
                </div>
            </div>
        </div>
        </form>
    </div>
@endsection
```

O botão "Registrar" fica **fora** do `<form>` (o `</form>` aparece depois do
`box-footer`); o submit depende de JS ou do enter no formulário. A view não
declara `<form>` — ele vem do include do `form.blade.php`.

### 6.2 `form.blade.php` (idêntico na v5)

Estrutura: `<form action="{{route('update.configurations.by.category')}}"
method="post" enctype="multipart/form-data">`, `{!! csrf_field() !!}`, bloco de
erros, campo hidden `category_configurations_id` e o laço:

```blade
<input type="hidden" name="category_configurations_id" value="{{$category->id}}">
@foreach($configurations as $configuration)
```

Ramificação por tipo:

| Tipo | Renderização |
|---|---|
| `richtext` | `<textarea class="summernote" name="{{$configuration->key}}">` |
| `checkbox` | hidden `0` + `<input type="checkbox" value="1">` |
| `text` (com key `barra-privacidade-botao`) | colorpicker (`#cp2.colorpicker-component`) |
| demais `text` | `<input type="text" onkeyup="somenteNumeros(this);">` |

Trecho do colorpicker:

```blade
@elseif($configuration->key != 'fornecedor')
    @if( $configuration->key == "barra-privacidade-botao" )
        <div id="cp2" class="input-group colorpicker-component">
            <input type="text" value="{{$configuration->value }}" name="{{$configuration->key}}" class="form-control" />
            <span class="input-group-addon"><i></i></span>
        </div>
    @else
        <input class=" config-input  form-control " onkeyup="somenteNumeros(this);" placeholder="{{$configuration->placeholder}}" maxlength="{{$configuration->max_input}}" minlength="{{$configuration->min_input}}" @if($configuration->notnull == true) required @endif type="{{$configuration->type}}"  name="{{$configuration->key}}"   value="{{$configuration->value }}" >
    @endif
```

> O campo `barra-privacidade-expiracao` (tipo `text`) cai no ramo "demais
> text" e recebe `onkeyup="somenteNumeros(this);"` — o usuário só consegue
> digitar dígitos e ponto. O `maxlength="999"` (herdado de `max_input`) limita a
> 3 dígitos, o que **impede** digitar prazos maiores que 999 dias (coerente) e
> **impede** valores como `30.5`.

### 6.3 Assets carregados pelo CMS

| Asset | Onde |
|---|---|
| `v6/public/assets/plugins/summernote/summernote.min.js` | `form.blade.php` |
| `v6/public/assets/plugins/summernote/summernote-pt-BR.js` | `form.blade.php` |
| `v6/public/assets/js/custom/utilities/richtext/summernote.js` | `form.blade.php` |
| `v6/public/assets/plugins/colorpicker-2.5.2/dist/css/bootstrap-colorpicker.min.css` | `edit.blade.php` (`@section('css')`) |
| `v6/public/assets/plugins/colorpicker-2.5.2/dist/js/bootstrap-colorpicker.js` | `edit.blade.php` (`@section('other-scripts')`) |

Inicialização do colorpicker:

```js
$(function() {
    $('.colorpicker-component').colorpicker();
});
```

## 7. O que o usuário final vê

| Cenário | Resultado |
|---|---|
| Módulo `lgpd` inativo (`status = 0`, padrão da migration) | Nenhuma faixa no portal. Nenhum item "LGPD" no menu do CMS. |
| Módulo ativo, `barra-privacidade-exibir = 0` | Nenhuma faixa. |
| Módulo ativo, exibição ligada, sem cookie | Faixa fixa no rodapé, texto institucional, botão verde "Ok, entendi!". |
| Módulo ativo, exibição ligada, com cookie válido | Faixa presente no HTML, mas sem a classe `show` → permanece fora da tela (`translateY(100%)`). |
| Cookie expirado | A faixa volta a aparecer na próxima visita. |

## 8. Exportações

Inexistentes. O módulo não gera planilha, PDF ou CSV.

## 9. Acessibilidade e usabilidade — observações

- O botão "Ok, entendi!" não tem `aria-label` nem texto alternativo além do
  próprio rótulo; o texto da faixa não está associado semanticamente ao botão.
- A faixa usa `z-index: 10` e `position: fixed`: pode sobrepor o rodapé e
  outros elementos fixos.
- Não há link "Política de Privacidade" dentro da faixa — o texto é HTML livre
  digitado pelo operador, e o link pode ou não existir.
