# Layout — header, nav e footer

| Campo | Valor |
|---|---|
| **status** | aprofundada — tasks `CMS-003-01/02` feitas. **Revisão 2 fechada** — tasks `CMS-003-03` |
| **rota pública** | todas (shell do site; quando integrar: props shared) |
| **tela CMS** | Sistema → Layout (`/admin/layout`) |
| **permissão** | já existe: `sistema.layout.editar` |
| **referência primo** | **não há** ManageLayout no OmniCo (nav quase estática; só marcas no dropdown). Aqui o mega menu / footer já nasceram CMS-first (`navigation.ts`) |
| **fonte do seeder** | `navigation.ts`, `SiteFooter.tsx`, `layout/assets.ts`, `MOBILE_NAV_ICONS` |

## Premissas (sem pergunta extra)

Se algo estiver errado, corrigir nesta spec **antes** das tasks.

| Tema | Decisão |
|---|---|
| Store | Reusar tabela `settings`. Esta spec grava `group=layout`. JSON nas keys compostas (nav, colunas, canais). Strings nas keys simples (logo). |
| Tela | Uma `ManageLayoutPage` — **não** misturar com Configurações (`002`, permissão diferente) |
| Permissão | `sistema.layout.editar`. Root sempre entra. Operador **não** (módulo Sistema fora do perfil). **Não** criar chave nova |
| Campos | Só o que o front **já mostra**. Lição do primo / `002`: nada de campo morto |
| Front | **Não** ligar `HandleInertiaRequests` / `SiteHeader` / `SiteFooter` ao banco nesta entrega |
| Coleções | `0..N` (nav, colunas, itens do mega, links rápidos, menu, transparência, canais, lojas). O componente define o layout; o CMS não congela em 5 itens |

Não é desta spec: institucional do rodapé (endereço, CNPJ, copyright), redes, SEO (`002`). Conteúdo interno das landings (`010+`). Busca (`/busca` — UI, não editorial).

---

## Divergência DS × front (seguir o código)

O `design system.md` §7 e o comentário no topo de `navigation.ts` dizem que **Para você** e **Para empresas** abrem mega menu. A implementação atual **não** faz isso:

```102:107:resources/js/components/layout/navigation.ts
    // "Para empresas" é link direto (sem mega menu) para a página Omni Empresas.
    { label: 'Para empresas', href: '/omni-empresas' },
    { label: 'A Omni', href: '/a-omni' },
    { label: 'Atendimento', href: '/atendimento' },
    { label: 'Agentes Omni', href: '/agentes-omni' },
```

**Seeder = o que o site mostra hoje** (código da `master` / `ajustes-front`): mega menu em *Para você*, *A Omni* e *Atendimento*. *Para empresas* continua link `/omni-empresas`, sem mega. Não inventar colunas de empresas. O `design system.md` §7 (só *Para você* e *Para empresas*) fica congelado — não é fonte desta revisão.

O editor **pode** ligar um mega menu em qualquer item depois (o form permite `megamenu` opcional).

---

## Revisão 2 — nav e rodapé do `ajustes-front`

**Status: fechada.** Fonte: `origin/master` (`NAV_ITEMS`, `MENU_PRINCIPAL`, `CANAIS`). Política: seeder = o que o site mostra hoje. Sem key nova — só campos dentro das JSON já existentes.

### R2.1 Megamenu em A Omni e Atendimento

O contrato da §4.2 **já** suporta (`megamenu` opcional, colunas/cards `0..N`). É seeder + o form já existente. Sem schema novo.

| Item | `href` | Coluna | Cards (title → href · ícone · hover) |
|---|---|---|---|
| A Omni | ausente (só abre mega) | A Omni | A Omni → `/a-omni` · `a-omni.svg` · `para-voce.webp`; Instituto Omni → `/instituto-omni` · `instituto-omni.svg` · `programa-elas.webp` |
| Atendimento | ausente | Atendimento | Atendimento → `/atendimento` · `atendimento.svg` · `carro.webp`; FAQ → `/faq` · `faq.svg` · `emprestimo.webp`; Segurança → `/seguranca` · `seguranca.svg` · `renova.webp` |

Destaque: A Omni = `para-voce.webp`; Atendimento = `carro.webp` (os arquivos que a `master` reusou). O editor troca depois.

Ícones mobile dos dois itens **permanecem** (`navbar/a-omni.svg`, `navbar/atendimento.svg`).

Copy dos cards (descrição):

- A Omni — *Conheça nossa história e valores.*
- Instituto Omni — *Nosso compromisso com a sociedade.*
- Atendimento — *Fale com a gente quando precisar.*
- FAQ — *Respostas para as dúvidas mais comuns.*
- Segurança — *Saiba como proteger seus dados.*

### R2.2 Rodapé com submenu (accordion)

`layout_footer_menu` ganha `children` `0..N` de `{ label, href }` por item. Sem filhos = link (como hoje). Listas do header e do rodapé são **independentes** — o rodapé não deriva do nav. “Para você” do footer tem itens que o mega não tem (Crediário, Omni Educa, Negocie, Validador).

Seed (labels **literais** da `master`):

| Item | `href` | Filhos |
|---|---|---|
| Área do cliente | `#area-cliente` | — |
| A Omni | ausente | A Omni → `/a-omni`; Instituto Omni → `/instituto-omni` |
| Para você | ausente | Financiamento de Carro, de Moto, de Caminhão, Crediário Omni (`#`), Negocie com a Omni, Empréstimo com garantia, Programa ELAS, Renova Omni, Omni Educa (`#`), Validador de boletos Omni |
| Para sua empresa | `/omni-empresas` | — |
| Seja um parceiro | `#` | — |
| Portabilidade Omni | `#` | — |

### R2.3 Canais: WhatsApp, `tel:` e link lateral

Cada linha de `layout_footer_channels[].items` ganha campos opcionais (ausente = comportamento antigo: `<p>`):

| Campo | Tipo | Uso |
|---|---|---|
| `href` | string | `tel:…` ou URL. Já existia; o seed passa a preencher |
| `whatsappId` | string | Gatilho do modal QR. IDs do front hoje: `4004-3500`, `3003-2119`, `0800-701-0412`. Copy/imagem do modal = [`005`](005-whatsapp-qr.md) |
| `showArrow` | bool | Seta de ação (Fale conosco, Chat on-line, Negocie On-line) |
| `sideLink` | `{ label, href, caption?, showArrow? }` | Link ao lado da linha (Negocie On-line ao lado do `0800 701 0471`) |

Seed = `CANAIS` da `master` (copy nova da Ouvidoria/SAC inclusive).

### R2.4 Área do cliente

`layout_client_area` **sobrevive** como gatilho: `label` + `href` `#area-cliente`. O `ClientAreaProvider` só intercepta esse hash. Conteúdo do modal = [`004`](004-area-do-cliente.md).

### R2.5 Integração

[`integracao/003-layout.md`](../integracao/003-layout.md): `SiteFooter` lê `children` e os campos de canal. Some o mapa local `WHATSAPP_QR_BY_VALUE`.

---

## 1. O que o editor vê

1. Menu **Sistema → Layout** (ícone `heroicon-o-bars-3`, sort **4**). Logs de Acesso passam a sort **5** (hoje 4).
2. Página com abas e botão **Salvar alterações** (mesmo Blade das outras Manage*: form + submit).
3. Sem item em **Páginas**. Sem widgets no Dashboard.

Quem não tem a permissão: `canAccess` false e o item some da nav (`shouldRegisterNavigation = canAccess`).

---

## 2. Recorte contra globais (`002`) e contra as páginas (`010+` / `024`)

### 2.1 O que é layout (hoje)

| Superfície | Onde |
|---|---|
| Logo Omni (mesmo SVG no header, mobile e footer) | `layoutAssets.logo.omni` |
| Itens da navbar desktop + accordion mobile | `NAV_ITEMS` |
| Mega menu (só “Para você”): colunas, cards, imagem destaque, selo | `NAV_ITEMS[0].megamenu` |
| Ícones do accordion mobile (mapa label → SVG) | `MOBILE_NAV_ICONS` |
| Botão Área do cliente | `CLIENT_AREA` |
| Links rápidos (só menu mobile aberto) | `QUICK_LINKS` |
| Coluna de links do footer | `MENU_PRINCIPAL` |
| Bloco “Baixe nosso app” (badges, **sem** `href`) | `SiteFooter` + `layoutAssets.stores` |
| Canais (título, horário, linhas valor/legenda) | `CANAIS` |
| Transparência Omni | `TRANSPARENCIA` |

Já no `002` (não repetir): redes, endereço, CNPJ, copyright.

### 2.2 Lojas (decisão que o `002` deixou em aberto)

As imagens das lojas **existem** no footer; **não** há `href`. Entram nesta spec:

- heading + 2 badges (ReplacementImage, SVG ok)
- URL **opcional** por loja, seed **vazio** (igual redes no `002`)

Contrato **quando** o front integrar: URL vazia → `<img>` sem link (como hoje). URL preenchida → `<a>`. Não é desta spec o bloco “Baixe o App” das landings (`010`, `024`) — se no aprofundamento forem os mesmos links, ler `layout_footer_app`.

### 2.3 Atendimento (`020`)

Telefones/horários do footer vivem **aqui**. A página Atendimento, quando aprofundar, **não** cria uma segunda verdade — reutiliza os canais (ou aponta para esta key).

---

## 3. Modelo de dados

Reusar `settings` (`key` unique, `value` text nullable, `group`). Layout usa `group=layout`.

JSON grande cabe em `text`. `Setting::get` / `set` continuam string; a page/seeder fazem `json_encode` / `json_decode`. Helper de leitura (ex. `Setting::getJson`) na implementação, **sem** mudar o comportamento das keys `global_*`.

Não criar resource CRUD de linhas soltas. Não criar tabelas `nav_items` / `footer_links` nesta spec.

### 3.1 URLs de mídia (seed vs upload)

O seed grava os paths **públicos de hoje** (`/images/logo/omni.svg`, `/images/megamenu/carro.webp`, SVGs de ícone). Não copiar binário para `storage` no seeder.

Upload novo vai para disco `public`, pasta `layout/…` (path relativo, como a OG).

Helper único (estender `ReplacementImage::publicUrl` ou `LayoutAsset::url`):

| Path gravado | URL no painel |
|---|---|
| começa com `/` ou `http` | usar como está |
| relativo (`layout/xyz.webp`) | `/storage/{path}` |

FileUpload **nunca** é hidratado com `/images/...` (o disco `public` do Filament não acha esse arquivo). Sempre o par ReplacementImage: preview da publicada + dropzone vazio. Upload vazio **não** zera o path (`ClearsReplacementUploads` na raiz; nos Repeaters, skip de upload vazio no `save` da page).

Ícone/logo: aceitar SVG além de JPEG/PNG/WebP.

---

## 4. Chaves `group=layout`

Prefixo `layout_`.

### 4.1 Logo

| Key | Tipo | Seed |
|---|---|---|
| `layout_logo` | ReplacementImage (SVG ok) | `/images/logo/omni.svg` |

Um arquivo para header desktop, top bar mobile e footer (o invert/brightness do footer é CSS, não outro asset).

### 4.2 Navegação — `layout_nav` (JSON)

Lista `0..N` de itens. Cada item:

| Campo | Obrigatório | Notas |
|---|---|---|
| `label` | sim | Texto da navbar / accordion |
| `href` | não | Link direto. Vazio/ausente se o item só abre mega menu |
| `icon` | sim no mobile | SVG do accordion (`MOBILE_NAV_ICONS`). ReplacementImage no item |
| `megamenu` | não | Objeto ou `null`. Ausente/`null` = item é link (`href`) |

`megamenu`:

| Campo | Notas |
|---|---|
| `featuredImage` | Destaque padrão, 320×320. ReplacementImage |
| `columns` | `0..N` |

Coluna: `category` + `items` `0..N`.

Item do mega:

| Campo | Seed / notas |
|---|---|
| `icon` | SVG 24px, ReplacementImage |
| `title` | |
| `description` | |
| `href` | rota pública (`/financiamento-carro`, …) |
| `image` | destaque no hover; cai no `featuredImage` se vazio |
| `badge` | opcional. Só o Renova tem `"Novo"` hoje. Desktop não mostra; mobile sim — o CMS guarda; o front escolhe |

**Seed de `layout_nav`** (igual `NAV_ITEMS` + ícones mobile):

1. **Para você** — sem `href`; `icon` `/images/icons/navbar/para-voce.svg`; mega menu:
   - `featuredImage`: `/images/megamenu/carro.webp`
   - Coluna **Financiamentos**
     - Financiamento de carro — `/financiamento-carro` — ícone `…/megamenu/carro.svg` — imagem `…/megamenu/carro.webp` — *Escolha seu carro e siga em frente.*
     - Financiamento de caminhão — `/financiamento-caminhao` — `caminhao.svg` / `caminhao.webp` — *Mais força para o seu trabalho crescer.*
     - Financiamento de moto — `/financiamento-moto` — `moto.svg` / `moto.webp` — *Sua próxima moto pode estar mais perto.*
   - Coluna **Crédito**
     - Programa Elas — `/programa-elas` — `programa-elas.svg` / `programa-elas.webp` — *Crédito pensado para os planos delas.*
     - Empréstimo com Garantia — `/emprestimo-garantia` — `emprestimo-garantia.svg` / `emprestimo.webp` — *Seu veículo pode abrir novos caminhos.*
     - Renova Omni — `/renova-omni` — `renova.svg` / `renova.webp` — *Seu caminhão novo de novo.* — **badge `Novo`**
2. **Para empresas** — `href` `/omni-empresas`; `icon` `…/navbar/para-empresas.svg`; **sem** mega menu
3. **A Omni** — sem `href`; `icon` `…/navbar/a-omni.svg`; mega (R2.1): destaque `para-voce.webp`; coluna A Omni (A Omni + Instituto Omni)
4. **Atendimento** — sem `href`; `icon` `…/navbar/atendimento.svg`; mega (R2.1): destaque `carro.webp`; coluna Atendimento (Atendimento + FAQ + Segurança)
5. **Agentes Omni** — `/agentes-omni` — `…/navbar/agentes-omni.svg`

### 4.3 Área do cliente — `layout_client_area` (JSON)

| Campo | Seed |
|---|---|
| `label` | `Área do cliente` |
| `href` | `#area-cliente` |

O botão **sempre** aparece. `#area-cliente` abre o modal (chrome do `ClientAreaProvider`). Conteúdo do modal = [`004`](004-area-do-cliente.md). **Não** esvaziar no seed (diferente das redes do `002`).

### 4.4 Links rápidos — `layout_quick_links` (JSON)

Só o menu mobile aberto. `0..N`. Campos: `icon` (SVG), `label`, `href`.

Seed (igual `QUICK_LINKS`):

| label | href | icon |
|---|---|---|
| Emitir boleto | `#` | `/images/icons/navbar/emitir-boleto.svg` |
| Renegociar dívidas | `/negocia-omni` | `…/negociar-dividas.svg` |
| Validar boleto | `/validador-boletos` | `…/validar-boleto.svg` |
| Acessar o app | `/superapp-omni` | `…/acessar-o-app.svg` |
| Encontrar Agente | `/agentes-omni` | `…/encontrar-agente.svg` |

### 4.5 Menu do footer — `layout_footer_menu` (JSON)

`0..N` de `{ label, href?, children? }`. `children` = `0..N` de `{ label, href }`. Sem filhos = link. Seed = R2.2 (listas independentes do header).

Não unificar “Para você” do footer com o mega do header. `#` de Crediário / Omni Educa / parceiro / portabilidade ficam como a `master` deixou.

### 4.6 App — `layout_footer_app` (JSON)

| Campo | Seed |
|---|---|
| `heading` | `Baixe nosso app` |
| `stores` | `0..N` |

Cada loja: `image` (ReplacementImage, SVG), `alt`, `url` (opcional).

Seed:

| alt | image | url |
|---|---|---|
| Google Play | `/images/stores/google-play.svg` | `''` |
| App Store | `/images/stores/app-store.svg` | `''` |

### 4.7 Canais — `layout_footer_channels` (JSON)

`0..N` canais. Cada um: `title`, `hours`, `items` `0..N` de `{ value, caption, href?, whatsappId?, showArrow?, sideLink? }` (R2.3).

Seed igual `CANAIS` da `master`:

1. **Central de Atendimento** — *Segunda-feira a sábado das 8h às 20h.*
   - `4004 3500` — Capitais e regiões metropolitanas. · `whatsappId` `4004-3500`
   - `0800 701 3500` — Demais regiões. · `href` `tel:08007013500`
2. **Central de Cobrança e Negociação** — *Segunda-feira a sábado das 8h às 20h.*
   - `3003 2119` — Capitais e regiões metropolitanas. · `whatsappId` `3003-2119`
   - `0800 701 0471` — Demais regiões. · `href` `tel:08007010471` · `sideLink` Negocie On-line → `https://negociacao.omni.com.br/` (caption *Acesse Portal de Negociação.*, `showArrow`)
3. **Ouvidoria** — *Segunda-feira a sexta-feira das 9h às 18h.*
   - `0800 701 0412` — Capitais e demais regiões. · `whatsappId` `0800-701-0412`
   - `Fale conosco` — Registre sua solicitação. · `href` `#` · `showArrow`
4. **SAC** — *Todos os dias das 8h às 20h. Autoatendimento 24h por dia.*
   - `0800 727 0885` — Capitais e demais regiões. · `href` `tel:08007270885`
   - `Chat on-line` — Atendimento também para pessoas com deficiência auditiva e de fala. · `href` `#` · `showArrow`

### 4.8 Transparência — `layout_footer_transparency` (JSON)

`0..N` de `{ label, href }`. Seed igual `TRANSPARENCIA` (todos `href` `#`):

Cadastro positivo · Demonstrações financeiras · Gerenciamento de risco · Informações ao público · Termos de uso · Privacidade Omni.

Não abrir HTML de política (`002` já tirou isso).

---

## 5. Abas do form

`statePath('data')`. `mount()`: keys `group=layout` + defaults iguais ao seeder + `withoutReplacementUploads` nos FileUploads de raiz (logo, lojas).

1. **Navegação** — logo, Área do cliente, repeater de itens da nav (mega menu aninhado: colunas → itens). Repeaters colapsáveis; `itemLabel` = label / category / title.
2. **Links rápidos** — repeater dos cards do menu mobile.
3. **Footer** — menu (filhos opcionais por item), bloco app (heading + repeater de lojas), canais (repeater aninhado: `href`, `whatsappId`, seta, link lateral), transparência.

`save()`: pular `null` e upload de substituição vazio; gravar JSON / paths com `Setting::set(..., 'layout')`; `clearReplacementUploads()`; `ActivityLogger::log('layout.updated', …, details: ['keys' => $keys])`; notification “Layout salvo!”.

Busca da navbar (placeholder, lupa, rota `/busca`) **não** entra — não é conteúdo editorial.

---

## 6. Permissão e log

Já no catálogo (`001` §5.1). Esta entrega **enforce**:

```text
canAccess  ⇔  root  OU  hasPermission('sistema.layout.editar')
```

Administrador já tem todas; Operador não tem Sistema.

Log: `layout.updated`.

---

## 7. Seeder

`LayoutSettingsSeeder`: `Setting::set` de **todas** as keys da §4, `group=layout`, copy **literal** das constantes atuais. Idempotente.

`DatabaseSeeder` chama esse seeder junto dos outros de conteúdo (**antes** do `if (production) return`), como o `GlobalSettingsSeeder`.

Não inventar URL de loja nem mega menu de empresas. `tel:`, `whatsappId` e `sideLink` entram **só** onde a `master` já tem (R2.3).

---

## 8. Integração front — **não** nesta fase

Proibido nesta entrega:

- `HandleInertiaRequests` share `layoutNav` / `layoutFooter`
- `navigation.ts` / `SiteFooter.tsx` / `SiteHeader` lerem o banco
- Mudar o React para 0..N além do que já faz (`map` nas constantes)

O shell segue hardcoded. Ligar shared props só quando o time pedir.

Contrato **futuro** (não implementar): um DTO próximo dos types de `navigation.ts` + footer; helper de URL pública para mídia; `#` e `href` vazio nos **links de texto** continuam renderizando o item (são CTAs visíveis); URL vazia **só** nas lojas (e nas redes do `002`) omite o wrap `<a>`.

---

## 9. Testes

PHPUnit (`tests/Feature/Cms/`), `RefreshDatabase`, `withoutVite`.

Mínimo:

- Seeder: `layout_nav` tem “Para você” com mega (Financiamento de carro + badge `Novo` no Renova), “A Omni” e “Atendimento” **com** mega (Instituto Omni / FAQ), “Para empresas” com `href` `/omni-empresas` **sem** `megamenu`; `layout_logo` = `/images/logo/omni.svg`; footer “Para você” tem filhos (Crediário Omni); `layout_client_area.href` = `#area-cliente`; canal `4004 3500` tem `whatsappId` `4004-3500`; lojas com `url` `''`; transparência contém “Privacidade Omni”.
- Root: `ManageLayoutPage::canAccess()` true; GET `/admin/layout` 200.
- `usuario` com perfil sem `sistema.layout.editar`: `canAccess` false.
- `usuario` com a chave: `canAccess` true.
- Livewire save altera um scalar (ex. heading do app ou label da Área do cliente) e gera `activity_logs.action = layout.updated`.
- Logo/loja já gravada: `save` com FileUpload vazio **não** apaga o path.

Não testar Inertia/shared props (ainda não existem).

---

## 10. Fora de escopo

- Endereço, CNPJ, copyright, redes, SEO (`002`).
- Placeholder/comportamento da busca.
- Ícones de chrome (lupa, chevron da navbar, seta do mega) — não são conteúdo.
- HTML de política de privacidade, cookies, canal de denúncias (`002`).
- CTAs de loja nas landings Home / SuperApp (`010` / `024`).
- Página Atendimento (`020`) — só o aviso de não duplicar canais.
- `SettingResource` CRUD genérico.
- Share Inertia.

---

## 11. Tasks

Não criar task dentro desta pasta. Implementação: [`docs/tasks/000-index.md`](../../tasks/000-index.md) (`CMS-003-01`/`02` feitas; `CMS-003-03` = esta revisão).