---
id: MOD-LAYOUTS-HOMEPAGE-PORTAL
tipo: modulo
onda: 5
status: concluido
unidades: [MOD-LAYOUTS-HOMEPAGE]
fontes:
  - V2/Modules/Layouts/Homepage/Views/portal/
  - V2/Modules/Layouts/Homepage/Views/lse/render/
  - V2/Modules/Layouts/Homepage/Controllers/HomepagePortalController.php
  - V2/Modules/Layouts/Homepage/Routes/web.php
depende_de: [UI-PORTAL, SVC-LANDINGSECTIONSEDITOR, SVC-MEDIA, SVC-STORAGE, SVC-SEO, SVC-GALLERY, MOD-SUPPORT-STRUCTURALLANDINGEDITOR]
consumidores: [UI-PORTAL]
criticidade: alta
dono: Homepage
---

# Homepage — página pública

> `/` é uma página **composta**: o layout do portal entrega um `<main>` vazio e o viewer do LSE
> injeta um bloco por seção, na ordem do payload. Não há template próprio para o corpo; há dez
> renders de seção com contratos de mídia, heading, `aria-label` e fallback diferentes entre si.

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | O visitante vê a página inicial montada com as seções ativas do payload, com imagens responsivas da galeria, eventos futuros, notícias e logos de parceiros. |
| Entrada | `GET /` (`v2.portal.home`); nenhuma query é lida pelo controller; payload salvo; banco de Notícias/Agenda/Parceiros. |
| Saída | HTML completo (`v2-homepage::portal.index` + `v2-service-landingsectionseditor::components.landing-sections-viewer` + `lse/render/*`). |
| Efeitos colaterais | Cache Redis `page:<sha1(fullUrl)>` por 5 min; buffer de SEO em cache e `INSERT` em lote em `v2_seo_page_visits`; consultas de leitura. |
| Particularidade central | O documento tem **um `<h1>`** (primeiro slide do hero) e depende do SLE para carregar Swiper/main.js; sem esses scripts os carrosséis não inicializam. |
| Como se integra | Layout `v2-portal::app-layout`; namespace `v2-homepage::`; componentes `v2-service-media::components.picture` e `v2-service-landingsectionseditor`; rotas das seções (`v2.noticias.portal.index`, `v2.parceiros-institucionais.portal.detalhe`, `/agenda`). |
| Se removido, quebra | `/` cai no catch-all do BasicPage (ou 404); nenhuma outra página consome os renders `lse/render/*`. |
| Superfície pública | `v2-homepage::portal.index`; 10 views `lse.render.*`; 8 views legadas `portal.sections.*` (sem uso). |

## 1. Efeito

### 1.1 O que o visitante vê

- **Hero** com slides em Swiper, primeiro slide com `<h1>` e os demais com `<h2>`, CTA opcional e
  imagem de fundo responsiva (`V2/Modules/Layouts/Homepage/Views/lse/render/hero.blade.php:5-64`).
- **Serviços** em grade de 3 colunas com ícone + título + descrição (`lse/render/services.blade.php:1-31`).
- **Sobre** com texto, imagem e contadores animados (`lse/render/about.blade.php:1-61`).
- **Marcas** em carrossel de logos (`lse/render/brands.blade.php:1-29`).
- **Eventos** em 4 cards com data/local/organizador e link (`lse/render/events.blade.php:1-43`).
- **Parceiros institucionais** em carrossel; cada logo abre um modal com o detalhe do parceiro
  (`lse/render/associados.blade.php:1-177`).
- **Regionais** como bloco textual com imagem de mapa ao fundo (`lse/render/regionais.blade.php:1-57`).
- **Notícias (estáticas/dinâmicas)** em cards de 3 colunas (`lse/render/news.blade.php:1-46`).
- **Notícias (banco)** com super-destaque + grade e selos de categoria/destaque
  (`lse/render/noticias.blade.php:9-101`).
- **Agenda Executiva** com calendário de até dois meses, tabs e lista de próximos eventos
  (`lse/render/agenda.blade.php:17-193`).
- **Vazio**: `Bem-vindo ao Portal` (`Views/portal/index.blade.php:38-45`).

### 1.2 O que muda no sistema

| Efeito | Onde | Fonte |
|---|---|---|
| HTML completo cacheado por 5 min | Redis | `V2/Core/Middleware/PageCacheMiddleware.php:13-14,31-59` |
| Cabeçalho `X-Cache-Status: HIT|MISS` | resposta | `PageCacheMiddleware.php:39 e :58` |
| Nenhum cache é gravado para resposta não-2xx | middleware | `PageCacheMiddleware.php:44-59` |
| Registro de visita SEO em buffer de 24 h e flush a cada 50 | `v2_seo_page_visits` | `V2/Services/SEO/Services/SEOService.php:144-168,173-194` |
| Consultas: config + galeria + notícias/agenda/parceiros | banco | ver [`integradores.md`](integradores.md) §2.5 |
| Nenhum índice de busca é escrito pela home | — | nenhuma chamada a indexer |

### 1.3 Contrato observável

| Entrada | Resultado |
|---|---|
| `GET /` sem payload | `200` com jumbotron |
| `GET /` com payload | `200` com as seções na ordem do array |
| `GET /` segunda vez em < 5 min | `200` + `X-Cache-Status: HIT` |
| `GET /?utm_source=x` | nova entrada de cache (a chave inclui a query string) |
| `GET /` com Redis fora | `200` sem cache, log `warning` |
| `GET /` com seção desconhecida | ignora a seção |
| `GET /` com erro de banco em Notícias/Parceiros | `500` (sem `try/catch`) |

### 1.4 O que a página NÃO faz

- Não pagina nem filtra seções: todas as do payload são renderizadas.
- Não aplica `is_active`/agendamento: esses campos não existem no payload.
- Não gera breadcrumb, título de página próprio no `<body>` nem `<h1>` de página.
- Não inclui `<meta name="robots">` nem canonical customizado (canonical = URL atual).
- Não personaliza por unidade regional nem por usuário.
- Não sanitiza o conteúdo das seções (HTML salvo é impresso cru em alguns renders).

## 2. Abordagem

### 2.1 Estratégia

O corpo da home é o próprio componente do LSE. O módulo só decide **quais** seções entregar e
**qual SEO** aplicar; a composição visual é dos renders.

```mermaid
flowchart TD
  R[GET /] --> C[HomepagePortalController@index]
  C --> L[(landing_sections)]
  C --> I[integrate]
  I --> P1[prepareSections]
  P1 --> V[portal.index]
  V --> LAY[v2-portal::app-layout]
  LAY --> BASE[v2-portal::base]
  BASE --> SEO[metadata/seo + SEOService]
  BASE --> PC[portal-content: header/footer/SLE blocks]
  V --> VIEWER[landing-sections-viewer]
  VIEWER --> P2[prepareSections 2ª]
  P2 --> SEC[render por type]
  SEC --> HTML
```

### 2.2 Mecânica passo a passo

1. Rota `GET /` → `HomepagePortalController@index`
   (`V2/Modules/Layouts/Homepage/Routes/web.php:9`), sob `web`, `MaintenanceMiddleware`,
   `page.cache` e `public.cache` (`V2/Core/Providers/V2ServiceProvider.php:297-301`).
2. Controller lê `landing_sections` e chama `integrate`/`prepareSections`
   (`HomepagePortalController.php:44-48`).
3. Popula o `SEOService` (title, description, keywords, JSON-LD) (`:50-63`).
4. Devolve `v2-homepage::portal.index` com `$sections` (`:65`).
5. A view define `siteTitle`/`descricao` via `PortalConfigsService` e estende
   `v2-portal::app-layout` com `fullWidthMain=true` e header/footer desligados
   (`Views/portal/index.blade.php:1-31`).
6. `v2-portal::base` monta `<head>`; o composer do SEO já colocou `$seo`, `$seoTitle`,
   `$seoDescription`, `$seoKeywords` e `siteTitle` (`V2/Services/SEO/Providers/SEOServiceProvider.php:23-56`).
7. `portal-content` decide header/footer: **blocos SLE têm precedência** sobre as flags
   (`V2/layouts/portal/partials/portal-content.blade.php:13-29,109-120`); com `fullWidthMain=true`
   o `<main>` só faz `@yield('main-section-area')` (`:32-35`).
8. `portal.index` inclui o viewer (`Views/portal/index.blade.php:33-36`).
9. O viewer reexecuta `prepareSections` e inclui o render de cada seção
   (`landing-sections-viewer.blade.php:11-47`).
10. `PageCacheMiddleware` grava a resposta final (HTML + headers, exceto `Set-Cookie`) por 5 min
    (`PageCacheMiddleware.php:44-59`).

### 2.3 Decisões de design

| Decisão | Efeito | Marca |
|---|---|---|
| Reutilizar o layout global `v2-portal::app-layout` | herda header/footer/acessibilidade/CSS do portal | `[C]` `portal/index.blade.php:11` |
| `fullWidthMain=true` + flags de header/footer | a home tem controle total do corpo | `[C]` `:23-30`; `portal-content.blade.php:32-35` |
| SEO no controller, não na view | `SEOService` é singleton e o `metadata/seo` lê dele | `[C]` `HomepagePortalController.php:50-63`; `SEOServiceProvider.php:45-55` |
| Hero com preload da primeira imagem | melhora LCP; usa `max($variants)`, que compara os **valores** (caminhos), não as larguras | `[C]` `lse/render/hero.blade.php:20-27` |
| `picture` com variantes WebP | responsividade e formato moderno | `[C]` `lse/render/hero.blade.php:30-37`; `V2/Services/Media/Views/components/picture.blade.php:51-70` |
| CSS de cada seção inline (`<style>`) | isolamento visual sem build | `[C]` `lse/render/noticias.blade.php:103-337`; `lse/render/agenda.blade.php:195-390` |

### 2.4 Trechos de código pivô

```blade
{{-- V2/Modules/Layouts/Homepage/Views/portal/index.blade.php:11-31 — a home redefine header/footer e largura --}}
@extends('v2-portal::app-layout', [
    'pageTitle' => $siteTitle ? "$siteTitle — Página Inicial" : 'Página Inicial',
    'pageDescription' => $descricao ?? '',
    'coverImage' => null,
    'schemas' => [[
        '@context' => 'https://schema.org',
        '@type' => 'Organization',
        'name' => $siteTitle ?? 'Portal',
        'url' => url('/')
    ]],
    'stickyAccessibilityBar' => false,
    'showHeader' => false,
    'showMainHeader' => false,
    'showMainFooter' => false,
    'showFooter' => false,
    'showMainLeftArea' => false,
    'showMainRightArea' => false,
    'fullWidthMain' => true
])
```

> `showHeader=false` só vale quando o SLE **não** tem bloco `header`; se tiver, o header aparece
> (`portal-content.blade.php:13-27`). `showMainFooter/footer` são irrelevantes com `fullWidthMain`.

```php
// V2/Modules/Layouts/Homepage/Controllers/HomepagePortalController.php:50-63 — SEO fixo da home
$this->seo->setTitle('SUCESU Nacional — Sociedade de Usuários de Informática e Telecomunicações');
$this->seo->setDescription(
    'Portal oficial da SUCESU Nacional. Conectando lideranças, impulsionando o futuro da tecnologia.'
);
$this->seo->setKeywords(
    'SUCESU, tecnologia, TIC, associados, eventos, regionais, networking, inovação'
);
$this->seo->addSchema([
    '@context'   => 'https://schema.org',
    '@type'      => 'Organization',
    'name'       => 'SUCESU Nacional',
    'url'        => url('/'),
    'description' => 'Sociedade de Usuários de Informática e Telecomunicações.',
]);
```

> Nenhum dado do payload entra no SEO. Mudar o título do hero não muda o `<title>` da página.

```blade
{{-- V2/Modules/Layouts/Homepage/Views/lse/render/hero.blade.php:18-27 — preload e prioridade do primeiro slide --}}
@foreach($data['slides'] as $slide)
<div class="swiper-slide">
    @if($loop->first)
        @php
            $firstImg = $slide['image'] ?? '';
            $firstVariants = $slide['image_variants'] ?? [];
            $firstUrl = $firstVariants ? $storageService->url(max($firstVariants)) : $storageService->url($firstImg);
        @endphp
        <link rel="preload" as="image" href="{{ $firstUrl }}" fetchpriority="high">
    @endif
```

> `max($firstVariants)` compara os **valores** do mapa `largura => caminho` e devolve o caminho lexicograficamente maior — não necessariamente a maior largura (armadilha). `StorageService::url` é chamado no render,
> enquanto o restante das imagens usa `v2_storage_url` dentro do componente `picture`.

```blade
{{-- V2/Modules/Layouts/Homepage/Views/lse/render/noticias.blade.php:9-20 — cabeçalho e link "Ver todas" --}}
@if(!empty($items) || !empty($super))
<section class="section-padding {{ !empty($super) ? '' : 'pt-0' }}">
    <div class="container">
        <div class="section-title-row">
            <h2 class="section-title-custom">
                <i class="bi bi-newspaper me-2" style="color: var(--tenant-primary, #1075ad);"></i>{{ $title }}
            </h2>
            <a href="{{ route('v2.noticias.portal.index') }}" class="section-title-link">
                Ver todas <i class="bi bi-arrow-right"></i>
            </a>
        </div>
```

> A cor vem da variável CSS `--tenant-primary`; o link depende da rota de Notícias existir.

### 2.5 Custos e limites

| Ponto | Custo |
|---|---|
| Render | 2× `prepareSections` + consultas de widgets (ver [`integradores.md`](integradores.md) §2.5) |
| Imagens | 1 preload + N `picture`; variantes WebP quando disponíveis |
| CSS | `<style>` inline em `noticias`, `agenda`, `regionais`, `associados` |
| HTML | payload inteiro é re-renderizado a cada cache miss; cache de 5 min mitiga |
| SEO | buffer de visitas cresce até 50 registros por flush (`SEOService.php:157-164`) |

## 3. Particularidades

### 3.1 O que só existe aqui

- **Composição sem `<h1>` fixo**: o `<h1>` é o título do primeiro slide do hero; sem hero, a página
  não tem `<h1>` (os demais renders usam `<h2>`).
- **Dois blocos JSON-LD**: o controller adiciona `Organization` ao `SEOService` e a view passa outro
  `schemas` `Organization`; ambos são impressos (`metadata/seo.blade.php:36-49`).
- **Dependência de assets do SLE**: sem `js_foot` semeado com Swiper e `main.min.js`, os carrosséis
  não inicializam (`V2/Seeds/SleConfigsSeeder.php:160-176`).

### 3.2 Invariantes e regras não óbvias

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| `RN-HOMEPAGE-51` | A ordem das seções é a ordem do array; renderer e viewer não ordenam | `order` não muda a tela | `landing-sections-viewer.blade.php:33-48` |
| `RN-HOMEPAGE-52` | Cada seção é embrulhada em `<div data-section-id class="lse-render-section-block">` | âncora estável para o preview trocar HTML | viewer `:41-46` |
| `RN-HOMEPAGE-53` | O `<title>` é o `seoTitle` puro quando ele existe; o sufixo `| siteTitle` só aparece no fallback | título sem “SUCESU Nacional” duplicado | `V2/layouts/portal/metadata/seo.blade.php:2` |
| `RN-HOMEPAGE-54` | O JSON-LD do controller e o da view são emitidos em blocos separados | dois `Organization` no HTML | `metadata/seo.blade.php:36-49` |
| `RN-HOMEPAGE-55` | `og:image` fica vazio porque `coverImage` é `null` | compartilhamento sem imagem de capa | `portal/index.blade.php:14`; `metadata/seo.blade.php:12-21` |
| `RN-HOMEPAGE-56` | A chave de cache inclui a query string inteira | `?utm_*` multiplica entradas de cache | `PageCacheMiddleware.php:88-91` |
| `RN-HOMEPAGE-57` | Respostas não-2xx não são cacheadas | erro some no próximo request | `PageCacheMiddleware.php:44-59` |
| `RN-HOMEPAGE-58` | O portal não passa `gtag`, ao contrário do BasicPage | sem `page_context='homepage'` no dataLayer | `portal/index.blade.php:11-31` vs `BasicPage/Views/portal/homepage.blade.php:14` |
| `RN-HOMEPAGE-59` | Blocos SLE de header/footer vencem as flags da view | home pode exibir header mesmo com `showHeader=false` | `portal-content.blade.php:13-29,109-120` |
| `RN-HOMEPAGE-60` | A barra de acessibilidade aparece se `accessibility_bar_enabled` estiver ligado, independentemente de `showAccessibilityBar` | barra presente na home | `portal-content.blade.php:6-11` |
| `RN-HOMEPAGE-61` | Não há `robots`, `hreflang` nem canonical próprio | canonical = `url()->current()` | `metadata/seo.blade.php:30-31` |

### 3.3 Estrutura de seções e headings

| Seção | Elemento raiz | Heading | `aria-label` | Guarda |
|---|---|---|---|---|
| hero | `<section class="hero-section-1">` | `<h1>` 1º slide, `<h2>` demais | `Destaques` | `slides` não vazio |
| services | `<section class="service-section-1 …">` | `<h3>` por card | `Serviços` | `items` |
| about | `<section class="about-section …">` | `<h2>` (título) | `Sobre` | `$data` não vazio |
| brands | `<section class="brand-section …">` | nenhum (título é `<p>`) | `Marcas` | `items` |
| events | `<section class="service-section …">` | `<h2>` + `<h3>` por card | `Eventos` | `items` |
| associados | `<section class="brand-section …">` | `<h2>` opcional | `Parceiros Institucionais` | `items` |
| regionais | `<section class="choose-us-section …">` | `<h2>` | `Regionais` | `$data` não vazio |
| news | `<section class="news-section …">` | `<h2>` + `<h3>` por card | `Notícias` | `items` |
| noticias | `<section class="section-padding">` | `<h2>` + `<h3>`/`<h2>` do super | **sem** `aria-label` | `items` ou `super_highlighted` |
| agenda | `<section class="service-section …">` | `<h2>` | `Agenda Executiva` | `months` |

> `noticias` é a única seção sem `aria-label`; `brands` não tem heading de seção (apenas `<p>`),
> o que quebra a hierarquia de headings quando usada isolada.

### 3.4 Casos-limite e comportamentos surpreendentes

- **Hero sem imagem**: `<img src="">` dentro do `picture` (o render não testa `image`)
  (`lse/render/hero.blade.php:30-37`; `picture.blade.php:63-67`).
- **Preload do hero escolhe pelo valor, não pela largura**: `max($firstVariants)` devolve o maior
  caminho (string), não a chave de maior largura; em mapas `largura => caminho` isso pode preloadar
  uma variante pequena (`lse/render/hero.blade.php:22-26`).
- **Notícias sem `summary`**: usa `subtitle` e corta em 120 caracteres com `str_limit`
  (`lse/render/noticias.blade.php:81-85`).
- **`date` fora do formato `<dia> <mês>`**: o card mostra o dia e o resto como mês
  (`lse/render/news.blade.php:25-32`).
- **Parceiro com detalhe indisponível**: o modal exibe `Erro ao carregar detalhes do parceiro.`
  (`lse/render/associados.blade.php:152-164`).
- **Swiper ausente**: o bloco de associados tenta 20× a cada 200 ms e desiste em silêncio
  (`lse/render/associados.blade.php:82-98`).
- **Calendário sem eventos**: meses são gerados mesmo sem eventos; dias não têm link
  (`lse/render/agenda.blade.php:82-101`).
- **Super-destaque com data futura**: aparece (o widget filtra por `status`, não por data).

### 3.5 Divergências e dívidas

| Item | Evidência |
|---|---|
| `Views/portal/sections/*` é uma cópia antiga dos renders (sem `picture`, sem variáveis `$data['…']` consistentes) e não é incluída por ninguém | `Views/portal/index.blade.php:33-36`; diff entre `portal/sections/hero.blade.php` e `lse/render/hero.blade.php` |
| SEO hard-coded diverge do SEO semeado em `v2_basic_pages` | `HomepagePortalController.php:50-56` vs `SucesuHomepageSeeder.php:401-407` |
| `metadata/seo` imprime dois JSON-LD | `metadata/seo.blade.php:36-49` |
| `og:image` sempre vazio | `portal/index.blade.php:14` |
| Dependência implícita de assets do SLE | `SleConfigsSeeder.php:160-176` |

## 4. Integrabilidade

### 4.1 Superfície pública

| Símbolo | Tipo | Assinatura | Precondição | Consumidores |
|---|---|---|---|---|
| `v2-homepage::portal.index` | view | `$sections` (array) | — | `HomepagePortalController` |
| `lse.render.{type}` | views | `$section`, `$data` | tipo registrado | viewer do LSE |
| `GET /` | rota | — | `landing_sections` opcional | navegador |
| `HomepagePortalController@index` | ação | `index(): View` | — | rota |

### 4.2 Contrato de UI/JS

- **Swiper**: `.hero-slider` e `.brand-slider` são inicializados por `assets/sucesu/js/main.js`,
  carregado pelo bloco SLE `js_foot` (`V2/Seeds/SleConfigsSeeder.php:164-176`).
- **Associados**: inicialização própria com retry e modal Bootstrap
  (`lse/render/associados.blade.php:80-176`).
- **Agenda**: tabs Bootstrap (`data-bs-toggle="tab"`) e links `/agenda?date=YYYY-MM-DD`
  (`lse/render/agenda.blade.php:31-104`).
- **Hero**: animações `wow`/`data-animation` dependem de `main.min.js` + `WOW` do SLE
  (`lse/render/hero.blade.php:43-52`).
- **DOM**: cada seção embrulhada por `div[data-section-id]`; o preview do CMS substitui esse
  conteúdo via `postMessage` (`landing-sections-viewer.blade.php:41-46`).
- **Sem JS**: hero vira uma pilha de slides; marcas/parceiros viram listas; agenda mantém o
  primeiro mês visível; notícias/eventos continuam legíveis.

### 4.3 Ordem e ciclo de vida

1. Middleware → controller → SEO → view.
2. Composer do SEO roda ao renderizar `metadata*` e chama `trackAccess()` (`SEOServiceProvider.php:32-36`).
3. Layout renderiza head → body → `layout-content` (header/footer/SLE) → `main-section-area`.
4. Viewer → renders.
5. Resposta cacheada (5 min) com headers originais, exceto `Set-Cookie`.

### 4.4 Receitas de uso

**1. Adicionar uma seção à home existente (sem editor):**

```php
$config = \V2\Modules\Support\StructuralLandingEditor\Models\LayoutBaseConfig::where('key', 'landing_sections')->firstOrFail();
$sections = is_array($config->payload) ? $config->payload : [];
$sections[] = ['id' => v2_uuid(), 'type' => 'sucesu-noticias', 'order' => count($sections) + 1, 'parameters' => ['title' => 'Últimas Notícias', 'limit' => 6]];
$config->payload = $sections;
$config->save();
// invalidar cache manualmente (o model não invalida)
```

**2. Reutilizar o visual de uma seção fora da home:**

```blade
@include('v2-homepage::lse.render.hero', ['section' => $section, 'data' => $section['parameters']])
```

> Funciona, mas **não** resolve `image_id`: para isso use o viewer/renderer.

**3. Verificar cache e SEO com curl:**

```bash
curl -sI https://<host>/ | grep -E "X-Cache-Status|Cache-Control"
curl -s  https://<host>/ | grep -E "<title>|application/ld\+json"
```

### 4.5 Anti-uso

- **Copiar `Views/portal/sections/*`** para “ter um render próprio”: são versões anteriores sem
  `picture`/variantes e não recebem `prepare`.
- **Confiar em `scripts`/`@push` dentro dos renders**: eles não empilham scripts; o único JS próprio
  é o de `associados`, e o resto depende do SLE.
- **Assumir header/footer ocultos**: se o SLE tiver blocos, eles aparecem.
- **Assumir `og:image`**: é sempre vazio na home.

### 4.6 Custos e limites

- Cache de 5 min por URL completa; invalidação global só pelo CMS/save ou TTL.
- Sem ETag/Last-Modified; `public.cache` adiciona `Cache-Control` curto (ver `CORE-RUNTIME`).
- Imagens: até 1 preload + N `picture`; sem `srcset` fora do componente.
- HTML pode ser grande: cada seção carrega `<style>` próprio.

## 5. Rotas e contratos HTTP

| Método | URI | Nome | Middleware | Auth | Cache | Fonte |
|---|---|---|---|---|---|---|
| GET | `/` | `v2.portal.home` | `web`; `MaintenanceMiddleware`; `page.cache`; `public.cache` | pública | Redis 5 min + headers públicos | `Routes/web.php:9`; `V2ServiceProvider.php:297-301` |

- **Query**: ignorada pelo controller; afeta a chave de cache (`fullUrl`).
- **Resposta**: `200` HTML; `503` em manutenção (middleware); `500` em erro de widget sem catch.
- **Headers**: `X-Cache-Status`; `Cache-Control` do `PublicCacheMiddleware`.
- **Sem paginação, filtros ou parâmetros de ordenação.**

## 6. Dados e modelo de domínio

- Lê `v2_layout_base_configs.payload` e, via widgets, `v2_gallery_media`, `v2_news` (+categorias),
  `v2_eventos`, `v2_parceiros_institucionais` (ver [`dados.md`](dados.md) e
  [`integradores.md`](integradores.md)).
- Escreve apenas `v2_seo_page_visits` (buffer do `SEOService`) — efeito indireto da visita.

## 7. Fluxos e estados

- `FL-HOMEPAGE-09` — **visita com cache quente**: request → `PageCacheMiddleware` → `HIT` (nenhum
  controller/consulta) → resposta.
- `FL-HOMEPAGE-10` — **visita com cache frio**: controller → SEO → view → viewer → renders → 5 min.
- `FL-HOMEPAGE-11` — **seção vazia**: render não imprime nada; o `div[data-section-id]` do viewer
  também não é impresso porque a condição `view()->exists` é satisfeita, mas o render devolve vazio
  — na prática fica um `<div>` vazio para seções com guarda falsa.

## 8. UI CMS

Não se aplica à página pública; ver [`cms.md`](cms.md).

## 9. UI Portal

### 9.1 Dados exibidos e escopos

| Seção | Escopo/consulta | Limite |
|---|---|---|
| hero/services/about/brands/events/news/regionais | payload (`parameters`) | — |
| associados | `Parceiro::active()->ordered()` com `galleryLogo`, exclui `hidden_slugs` | nenhum; mínimo 6 por repetição |
| noticias | `News::published()->recent()`, `whereIn(category_id)` | `limit+1` (default 10) |
| agenda | `Evento::published()->active()->upcoming()->upcomingOrdered()` | 20 carregados; lista = `event_limit` (default 4) |

### 9.2 SEO

| Tag | Origem | Exemplo/observação |
|---|---|---|
| `<title>` | `seoTitle` do `SEOService`; fallback `pageTitle | siteTitle` | `SUCESU Nacional — Sociedade de Usuários de Informática e Telecomunicações` |
| `meta description` | `seoDescription` (controller) | “Portal oficial da SUCESU Nacional…” |
| `meta keywords` | `seoKeywords` (controller) | `SUCESU, tecnologia, TIC, …` |
| `og:type/url/title/description/image` | `website`, URL atual, mesmos valores; imagem vazia | `metadata/seo.blade.php:7-21` |
| `twitter:card/url/title/description/image` | `summary_large_image`, mesmos valores | `:23-28` |
| `link rel=canonical` | `url()->current()` | `:30-31` |
| JSON-LD | `Organization` do controller + `Organization` da view | dois blocos |
| `robots` / `hreflang` | não emitidos | — |

Fonte do composer: `V2/Services/SEO/Providers/SEOServiceProvider.php:23-56`;
template: `V2/layouts/portal/metadata/seo.blade.php:1-49`.

### 9.3 Mídia

- `picture` com variantes WebP (`<source media="(min-width: Wpx)">`) e `<img>` de fallback
  (`V2/Services/Media/Views/components/picture.blade.php:51-70`).
- `alt`: título do slide/item quando não há `alt_text`; ícones decorativos usam `alt=""`.
- `loading=lazy` em tudo, exceto quando o render força `eager` (primeiro slide/dots).
- Preload do primeiro slide com `fetchpriority="high"` (`lse/render/hero.blade.php:20-27`).
- Regionais usa background CSS via `v2_storage_url` e `mask-image` (`lse/render/regionais.blade.php:1-29`).

### 9.4 Responsividade, print e acessibilidade

- Grades Bootstrap: `col-xl-4/col-lg-6/col-md-6` (serviços, notícias, eventos), `col-lg-5/col-lg-7`
  (agenda), `col-xl-11/col-xl-2` (sobre).
- `associados` usa Swiper com breakpoints 0/576/992/1200 (`lse/render/associados.blade.php:116-121`).
- Print: a home não tem CSS de impressão próprio; o portal injeta `print-only` e esconde `#app`
  quando aplicável (`V2/layouts/portal/base.blade.php:105-115`).
- A11y: `aria-label` por seção (exceto noticias), `alt` nas imagens, links com `title` nos dias do
  calendário, `aria-selected` nas tabs, `visually-hidden` no modal de parceiro, `aria-modal`.

### 9.5 Cache

- Middleware: `page:<sha1(fullUrl)>`, TTL 5 min, store `redis`
  (`PageCacheMiddleware.php:11-14,88-91`).
- Não cacheia: métodos ≠ GET, AJAX, `cms*`, `api*`, `trabalhe-conosco*`, `associe-se*`,
  `fale-conosco*`, `_debugbar*`.
- Invalidação: `POST /cms/homepage/save`, `POST /cms/homepage/refresh-cache`,
  `SucesuHomepageSeeder`, e outras escritas CMS que apaguem `page:*`.

## 10. Configuração, permissões e flags

- Rota pública: sem permissão; sob `MaintenanceMiddleware`.
- **A flag `Homepage` não afeta `/`**: `module.active` só é aplicado em `Routes/cms.php`
  (`V2ServiceProvider.php:303-306`); desativar o módulo esconde o editor, não a home.
- SEO depende de `PortalConfigsService` (`titulo_site`, `descricao`, `palavras_chaves`,
  `global_meta_tokens`, `header_scripts`, `footer_scripts`) via composer
  (`SEOServiceProvider.php:38-54`).
- Assets JS/CSS do tema dependem dos blocos SLE `css_head`/`js_foot`
  (`V2/layouts/portal/base.blade.php:76-91,125-139`).

## 11. Observabilidade, erros e limites

| Situação | Efeito | Fonte |
|---|---|---|
| Cache hit | header `X-Cache-Status: HIT` | `PageCacheMiddleware.php:35-40` |
| Cache miss | header `MISS` + gravação | `:52-58` |
| Redis fora | warning e serve sem cache | `:62-65` |
| Erro em widget de banco sem catch | 500 | `SucesuNoticiasWidget.php:49`; `SucesuAssociadosWidget.php:35` |
| Falha de SEO tracking | warning `[SEO] Falha ao rastrear acesso (Buffer): …` | `SEOService.php:165-167` |
| Falha de flush | error `[SEO] Erro ao descarregar buffer de acessos no banco: …` | `SEOService.php:191-193` |

Limites: TTL 5 min; sem compressão/minify específicos (há `HtmlMinify` global — ver
`CORE-RUNTIME`); payload sem limite; sem paginação.

## 12. Seeds e dados iniciais

- `SucesuHomepageSeeder` fornece as 8 seções iniciais e as imagens via Gallery
  (`V2/Seeds/BasicPages/SucesuHomepageSeeder.php:241-394`).
- `SleConfigsSeeder` fornece Swiper/main.js/estilos do tema no `css_head`/`js_foot`
  (`V2/Seeds/SleConfigsSeeder.php:160-185`) — sem ele, carrosséis e animações `wow` não funcionam.
- `SleAssetSeeder` também referencia `swiper-bundle` (`V2/Seeds/SleAssetSeeder.php:86 e :104`).

## 13. Lacunas

| ID | Lacuna | Onde procurar | Impacto |
|---|---|---|---|
| `LG-HOMEPAGE-21` | Não há CSS de impressão específico da home | `V2/layouts/portal/print` | impressão depende do layout global |
| `LG-HOMEPAGE-22` | Não há teste de SEO ponta a ponta para `/` (apenas `SeoPortalControllerTest`) | `tests/Feature/V2/Services/SEO/` | regressão de title/JSON-LD invisível |
| `LG-HOMEPAGE-23` | O comportamento esperado de `og:image` na home não está definido | `portal/index.blade.php:14` | compartilhamento social pobre |
| `LG-HOMEPAGE-24` | Não há `aria-label` em `noticias` e heading em `brands` | renders | falha de acessibilidade |
| `LG-HOMEPAGE-25` | Não foi possível determinar se o `main.js` do tema é obrigatório em produção (depende do seed SLE) | `SleConfigsSeeder`; banco | carrosséis podem não inicializar |
| `LG-HOMEPAGE-26` | Sem política de cache para query strings arbitrárias | `PageCacheMiddleware.php:88-91` | crescimento de chaves Redis |

## 14. Checklist de paridade

- [ ] `PC-HOMEPAGE-58` — `/` renderiza as seções na ordem do payload e embrulha cada uma em `div[data-section-id]`. Fonte: `landing-sections-viewer.blade.php:33-46`.
- [ ] `PC-HOMEPAGE-59` — Com payload vazio, aparece `Bem-vindo ao Portal`. Fonte: `portal/index.blade.php:38-45`.
- [ ] `PC-HOMEPAGE-60` — `<title>` é exatamente o título do controller, sem `| siteTitle`. Fonte: `HomepagePortalController.php:50`; `metadata/seo.blade.php:2`.
- [ ] `PC-HOMEPAGE-61` — O HTML contém dois blocos `application/ld+json` `Organization`. Fonte: `HomepagePortalController.php:57-63`; `portal/index.blade.php:15-22`; `metadata/seo.blade.php:36-49`.
- [ ] `PC-HOMEPAGE-62` — `og:image` e `twitter:image` saem vazios. Fonte: `portal/index.blade.php:14`; `metadata/seo.blade.php:12-28`.
- [ ] `PC-HOMEPAGE-63` — A primeira visita retorna `MISS` e a segunda (mesma URL) `HIT` em < 5 min. Fonte: `PageCacheMiddleware.php:35-59`.
- [ ] `PC-HOMEPAGE-64` — `?utm_source=x` gera chave de cache distinta. Fonte: `PageCacheMiddleware.php:88-91`.
- [ ] `PC-HOMEPAGE-65` — Sem blocos SLE, o header não é impresso (`showHeader=false`); com bloco `header`, ele é impresso. Fonte: `portal-content.blade.php:13-27`.
- [ ] `PC-HOMEPAGE-66` — O primeiro slide tem `<h1>`, os demais `<h2>`, e há `link rel=preload` apontando para `StorageService::url(max($variants))` (maior valor de caminho, não a maior largura). Fonte: `lse/render/hero.blade.php:20-46`.
- [ ] `PC-HOMEPAGE-67` — `noticias` mostra super-destaque quando existe e a grade com até `limit+1` itens. Fonte: `lse/render/noticias.blade.php:22-99`; `SucesuNoticiasWidget.php:55-72`.
- [ ] `PC-HOMEPAGE-68` — Agenda mostra tabs por mês e links `/agenda?date=YYYY-MM-DD` apenas em dias com evento. Fonte: `lse/render/agenda.blade.php:31-101`.
- [ ] `PC-HOMEPAGE-69` — Parceiros abrem modal que busca a URL de detalhe por XHR e exibe erro amigável em falha. Fonte: `lse/render/associados.blade.php:128-166`.
- [ ] `PC-HOMEPAGE-70` — Seções sem dados não produzem HTML visível. Fonte: guards de cada render.
- [ ] `PC-HOMEPAGE-71` — Desligar a flag `Homepage` não derruba `/`. Fonte: `V2ServiceProvider.php:297-307`.
- [ ] `PC-HOMEPAGE-72` — Visitar `/` alimenta `seo_access_buffer` e, a cada 50 visitas, insere em `v2_seo_page_visits`. Fonte: `SEOService.php:144-168,173-194`.

## 15. Evidências consultadas

- `V2/Modules/Layouts/Homepage/Views/portal/index.blade.php:1-46`
- `V2/Modules/Layouts/Homepage/Views/portal/sections/*.blade.php` (8 arquivos, legado)
- `V2/Modules/Layouts/Homepage/Views/lse/render/*.blade.php` (10 arquivos)
- `V2/Modules/Layouts/Homepage/Controllers/HomepagePortalController.php:1-67`
- `V2/Modules/Layouts/Homepage/Routes/web.php:1-9`
- `V2/Core/Providers/V2ServiceProvider.php:290-335`
- `V2/Core/Middleware/PageCacheMiddleware.php:1-92`
- `V2/Services/SEO/Providers/SEOServiceProvider.php:16-69`
- `V2/Services/SEO/Services/SEOService.php:29-58,65-116,121-194,293-400`
- `V2/layouts/portal/{base,app-layout,metadata,metadata/seo,partials/portal-content}.blade.php`
- `V2/Services/Media/Views/components/picture.blade.php:1-72`
- `V2/Modules/Publicacoes/{Noticias,Agenda}/Models/*.php`; `V2/Modules/Conexoes/ParceirosInstitucionais/Models/Parceiro.php`
- `V2/Seeds/SleConfigsSeeder.php:160-190`; `V2/Seeds/SleAssetSeeder.php:80-110`
- `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:241-421`
