# Banners — Regras de Negócio e Portabilidade

## 1. Regras de negócio

| # | Regra | Onde está implementada |
|---|---|---|
| B1 | Um banner pertence à categoria `banners` (id 27) ou `super-banners` (id 33) | `category_contents` |
| B2 | Só banners com `status = true` são exibidos no portal | `v6/app/Providers/BannerServiceProvider.php` (`byStatus()`) e as queries das views |
| B3 | A ordem de exibição do banner comum é `contents.order` | view `banners` e `orderBy('order')` nas views |
| B4 | No super banner, `order` indica a **posição na tela**: 1 = topo/esquerda, 2 = base/direita | view `super_banners` |
| B5 | O rótulo da posição depende do layout ativo: `Esquerda`/`Direita` (modelo5) ou `Em cima`/`Em baixo` | view `super_banners` (SQL) e `objBanner()` (PHP) |
| B6 | Ativar um super banner desativa o outro da mesma posição | `v6/app/Http/Controllers/Prefecture/ContentController.php:675-677`, `:997-999`, `:1568-1570` |
| B7 | A reordenação é feita por drag & drop e persistida em `contents.order` | `ajustOrder` → `reorder()` (`v6/app/Http/Traits/AppTrait.php:741`) |
| B8 | A imagem do banner é o `archives` com `highlight = 1` do conteúdo | `Content::archiveHighlight()` |
| B9 | O link do banner fica em `links` (1:1 com `contents`) | `Content::linksUnique()` |
| B10 | "Abrir em nova guia" corresponde a `links.isBlank = 1` | views do portal |
| B11 | O módulo só é exibido no portal se `modulos['banners']` estiver ligado | `modelo1/layout/sidebar/banners.blade.php` |
| B12 | Tamanhos sugeridos: 267x95 (banner lateral) e 772x90 (super banner) | formulários do CMS |
| B13 | A imagem é obrigatória no cadastro (não na edição) | `v6/app/Http/Requests/ContentRequest.php` (`Route::getCurrentRoute()->getName() == "conteudo.store"`) |

## 2. Acoplamentos

### Dependências de saída

| Módulo | Acoplamento |
|---|---|
| **Conteúdos (genérico)** | O módulo inteiro é o CRUD genérico de conteúdos; compartilha `contents`, `archives`, `links`, `ContentRequest`, `ContentController` |
| **Configurações** | `configurations.key = 'layout'` é lido **dentro** da view SQL `super_banners` |
| **Módulos (registry)** | `modulos.slug = 'banners'` e `modulos['banners']` controlam a exibição no portal |
| **Menus** | `MenuServiceProvider` monta o submenu "Banners Laterais"/"Super Banners" |
| **Layouts (modelo personalizável)** | O modelo5 itera a tabela `layouts` para saber quais blocos de super banner renderizar |
| **Transmissões** | `modelo1/layout/sidebar/banners.blade.php` também consulta a categoria `transmissoes` na mesma sidebar |

### Dependências de entrada

| Consumidor | Uso |
|---|---|
| `PortalController` / layout `default` | Renderiza os banners laterais |
| `BannerServiceProvider` | Injeta `$banners` e `$superBanners` |
| `MenuServiceProvider` | Item de menu "Banners" |

## 3. Armadilhas

### A1 — Nomes de campo da tela de super banner dependem do slug semeado

`v6/resources/views/prefecture/contents/banners/super.blade.php` envia
`check_<slug>`, `url_<slug>` e `<slug>` (arquivo). O controller
`updateSuperBanner` lê `check_super_banner_1`, `check_super_banner_2`,
`url_super_banner_1`, `url_super_banner_2`, `super_banner_1`, `super_banner_2`.
Como os dois conteúdos da categoria `super-banners` têm, no seed, os slugs
`super_banner_1` e `super_banner_2`, os nomes **coincidem** e a tela funciona
com os dados atuais. O risco é a dependência implícita: renomear o slug de um
super banner faz o controller não ler mais o campo, zerar o status
(`if (!isset(...)) $banner->status = 0;`) e gravar a `link` como nula.

Agravante: a própria tela é **órfã** — nenhum controller renderiza
`prefecture.contents.banners.super` e a `index.blade.php` dos banners não
aponta para ela; a rota `update.super.banner` fica sem uso.

### A2 — Caminho de upload divergente

| Onde | Caminho |
|---|---|
| View do formulário (`super-banners/form.blade.php`) | `storage/content/super-banners/<id>/destaque/<nome>` |
| Controller `updateSuperBanner` | `storage/content/super-banner/<nome>` (singular) |

Além disso, `Storage::delete("/content/super-banner/" . $nomeOld)` usa o caminho
singular, enquanto o conteúdo gravado pelo CRUD genérico está em `super-banners/`
(plural).

### A3 — `updateSuperBanner` assume dois super banners

Se houver menos de dois conteúdos na categoria `super-banners`, `$banner1`/`$banner2`
serão `null` e o método lançará erro (`Call to a member function id() on null`).

### A4 — `returnData` de super banners usa a categoria errada

`returnData('Super Banners', 'super-banners', 'banners', ...)` popula `$banners` com os
conteúdos de `banners`. Inofensivo hoje (a view do super banner não usa `$banners`),
mas é uma armadilha para quem mexer no formulário.

### A5 — SQL com subconsulta escalar dentro da view

`super_banners` embute `(select value from configurations c where c.key = 'layout')`.
A view é re-calculada a cada consulta; trocar o layout muda o resultado sem
migração. Em troca, qualquer alteração no schema de `configurations` quebra a view.

### A6 — `row_number()` em views

`row_number() OVER (...)` torna a view dependente da ordem das colunas e impede
`CREATE OR REPLACE` se a assinatura mudar (o `CREATE OR REPLACE VIEW` exige o **mesmo**
número e tipos de colunas).

### A7 — `Route::getCurrentRoute()` dentro do Form Request

```php
if (Route::getCurrentRoute()->getName() == "conteudo.store") { ... }
```

Acopla a validação ao **nome** da rota: renomear a rota quebra a obrigatoriedade da
imagem.

### A8 — Regra de `order` com semântica dupla

O mesmo campo `contents.order` significa "posição na lista" para banners comuns e
"posição na tela" para super banners. Um `order` alto (ex.: 4) em um super banner não
tem posição definida na view: cai no `ELSE` ("Em baixo"/"Direita"), colidindo com o
`order = 2`.

### A9 — Injeção de model dentro do Blade

`@inject('banners', 'App\Prefecture\Content')` seguido de query na view
(`default/side-banners.blade.php`, `modelo1/layout/sidebar/banners.blade.php`):
a consulta roda **a cada renderização**, ignorando o cache do provider.

### A10 — Query duplicada em `modelo1/layout/sidebar/banners.blade.php`

A view consulta `banners` **e** `transmissoes` na mesma sidebar, cada uma com seu
`whereHas`, gerando N+1 por categoria.

### A11 — Nomes de arquivo com typo

- `storage/img/exemplos/supper-banner-sem-imagem.png` ("supper").
- `v6/public/assets/js/custom/contents/banners/datatable.js` ×
  `v6/public/assets/js/custom/contents/super-banners/datatable.js` — dois arquivos
  praticamente idênticos.

### A12 — `has_access` em vez de Policy

Não há `Policy`; a autorização está espalhada em views, no `DatatableController` e no
`MenuServiceProvider`.

### A13 — Sem cache

`BannerServiceProvider` roda as queries a **cada request** do portal (o composer é
executado por view). Com muitos banners, é um custo fixo por página.

### A14 — Exclusão em lote genérica

`ajax.generic.content.deleteAll` remove conteúdos (soft delete). As views SQL já
filtram `deleted_at IS NULL`, então o grid se auto-atualiza.

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

| Arquivo | v5 vs v6 |
|---|---|
| `database/migrations/2023_04_18_132413_update_banners_view_to_order.php` | **Idêntico** |
| `database/migrations/2023_04_10_132524_create_view_to_super_banners.php` | **Idêntico** |
| `resources/views/prefecture/contents/super-banners/index.blade.php` | **Idêntico** |
| `resources/views/prefecture/contents/banners/index.blade.php` | **Divergente** |
| `app/Http/Controllers/Prefecture/ContentController.php` | **Divergente** |
| `app/Http/Controllers/Prefecture/DatatableController.php` | **Divergente** |
| `app/Http/Controllers/Prefecture/CategoryContentController.php` | **Divergente** |
| `app/Providers/MenuServiceProvider.php` | **Divergente** |
| `app/Providers/BannerServiceProvider.php` | **Divergente** (v5 é mais evoluído) |

## 5. Onde a v6 divergiu da v5 (e por quê)

### 5.1 `BannerServiceProvider` — a v5 está à frente

A v5 agrupa os super banners por posição e expõe variáveis extras:

```php
// v5/app/Providers/BannerServiceProvider.php (trecho)
$groupedSuperBanners = $configSuperBanners->groupBy('order');
$legacyBanners = $configSuperBanners->take(2)->values();
$view->with('superBanners', $legacyBanners);
$view->with('superBannersPosition1', $groupedSuperBanners->get(1, collect()));
$view->with('superBannersPosition2', $groupedSuperBanners->get(2, collect()));
```

A v6 perdeu esse agrupamento e mantém apenas `superBanners`. Em contrapartida, a v6
incluiu `acronym` no select da v5 (`get(['id', 'title', 'slug', 'order', 'acronym'])`).

### 5.2 Migration de view ausente na v6

`v5/database/migrations/2025_09_30_215142_update_view_to_super_banners.php` **não
existe** na v6. A v6 compensa com o seed
`v6/database/seeds/UpdateSuperBannersViewFixEmBaixo.php`, que reescreve a view — mas
**seeds não são idempotentes por migração**: uma instalação nova da v6 precisa rodar o
seeder manualmente para ter a view correta.

### 5.3 Menu do CMS

A v6 carrega `MenuOrder::with('modulo')` e exige `modulos.status` verdadeiro; a v5
não tinha o registry de módulos.

### 5.4 `ContentController` e `CategoryContentController`

Grandes divergências de código (a v6 é o ramo mais novo do CMS, com
`display_type`, `custom_icon`, `description`, API de categorias, etc.). O que afeta
banners: a v6 adiciona a regra de ativação exclusiva do super banner também no
`setStatus`.

## 6. Regras que NÃO podem mudar na reconstrução

1. O rótulo da posição do super banner acompanha o layout ativo
   (`Esquerda`/`Direita` no modelo5; `Em cima`/`Em baixo` nos demais).
2. Só um super banner por posição pode estar ativo.
3. A ordem do banner lateral é definida pelo operador (drag & drop).
4. Banners desativados somem do portal.
5. O link abre em nova aba quando "Abrir em nova guia" está marcado.
6. O banner lateral aparece na coluna direita das páginas internas.
7. O super banner do modelo5 respeita a composição livre definida na tabela `layouts`.
