---
id: MOD-INSTITUCIONAL-SOBRENOS-PORTAL
tipo: modulo
onda: 5
status: concluido
unidades: [MOD-INSTITUCIONAL-SOBRENOS]
fontes:
  - V2/Modules/Institucional/SobreNos/
depende_de: [MOD-INSTITUCIONAL-SOBRENOS, UI-PORTAL, SVC-LANDINGSECTIONSEDITOR, SVC-MEDIA, SVC-CONTENTRICHEDITOR, SVC-SEO]
consumidores: [UI-PORTAL, SVC-SEO]
criticidade: media
dono: SobreNos
---

# Sobre Nos — UI Portal

> Anexo de [`spec.md`](spec.md). Descreve `/sobre-nos` (página “full width” sem
> header/footer do portal, montada por seções), a página individual `/equipe/{slug}`, os
> 301 de `/diretoria*`, a origem de cada metatag, o comportamento de mídia e o cache de
> página. O conteúdo é 100% derivado do payload + banco; **não há arquivo de imagem ou
> texto fixo no módulo além dos literais do Blade** (hero “Sobre nós”, breadcrumb, ícones
> de placeholder e classes de layout).

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Entrega ao visitante a página institucional com as seções salvas, a diretoria do biênio corrente e o perfil individual de cada membro ativo. |
| Entrada | `GET /sobre-nos`, `GET /equipe/{slug}`, `GET /diretoria`, `GET /diretoria/{slug}`; payload da config; tabelas de biênios/membros; `v2_gallery_media`. |
| Saída | HTML `v2-sobrenos::portal.index` e `v2-sobrenos::portal.member.show`; `301` nos redirecionamentos; `404` para membro inválido. |
| Efeitos colaterais | Registro de visita em `v2_seo_page_visits` (buffer de 50 no cache), cache Redis de página por 5 min, headers `Cache-Control` público. |
| Particularidade central | A página não tem header/footer do portal (`showHeader=false`, `showFooter=false`) e delega toda a estrutura visual ao componente LSE, que renderiza `div[data-section-id]` por seção; a seção de depoimento nunca aparece. |
| Como se integra | `v2-portal::app-layout` + `@section('main-section-area')`; `v2-service-landingsectionseditor::components.landing-sections-viewer`; `v2-service-media::components.picture`; `SEOService` via composer; `ContentRichEditorRendererService` para a bio. |
| Se removido, quebra | `/sobre-nos` cai no fallback BasicPage (404 se não houver página runtime); `/equipe/*` e os 301 desaparecem; o sitemap passa a apontar para URLs mortas (`/diretoria`, `/diretoria/{slug}`). |
| Superfície pública | 2 views de página, 4 views de seção usadas no portal (about, team, video, counters), 2 métodos de controller, 4 rotas. |

## 1. Efeito

### 1.1 O que o usuário consegue fazer

- **Ver a página institucional** com hero (breadcrumb + “Sobre nós”), apresentação,
  vídeo, contadores, diretoria e (se o payload tiver) qualquer outra seção padrão do LSE,
  na ordem salva (`Views/portal/index.blade.php:24-37`).
- **Navegar para a diretoria** pelo CTA “Conheça a Diretoria” (`#diretoria`) ou rolando
  até a seção team, que tem `id="diretoria"` (`Views/lse/render/about.blade.php:113-119`;
  `Views/lse/render/team.blade.php:2`).
- **Abrir o perfil de um membro** em `/equipe/{slug}` com foto, LinkedIn, e-mail, cargo,
  biênio, descrição e biografia rica; voltar para a diretoria por breadcrumb ou botão
  (`Views/portal/member/show.blade.php:55-100`).
- **Chegar por URLs antigas** `/diretoria` e `/diretoria/{slug}` e ser redirecionado (301)
  para as novas (`SobreNosPortalController.php:155-169`).

### 1.2 O que muda no sistema

| Efeito | Onde é produzido | Fonte |
|---|---|---|
| Grava a visita da página (path, UA, IP, referer, flag de bot) em buffer de cache | `SEOService::trackAccess` | `V2/Services/SEO/Services/SEOService.php:121-166` |
| Descarrega o buffer em `v2_seo_page_visits` a cada 50 acessos | `flushBuffer` | `:173-198` |
| Cacheia o HTML de `/sobre-nos` e `/equipe/*` por 5 min (Redis, chave `page:<sha1>`) | `PageCacheMiddleware` | `V2/Core/Middleware/PageCacheMiddleware.php:11-14,31-60,88-91` |
| Adiciona `Cache-Control: public, max-age=60, must-revalidate` e `Vary: Accept-Encoding` (GET/HEAD, 2xx, fora de AJAX) | `PublicCacheMiddleware` | `V2/Core/Middleware/PublicCacheMiddleware.php:45-87` |
| Emite JSON-LD `AboutPage` (2 blocos, ver §3.2) | controller + view + partial SEO | `SobreNosPortalController.php:77-83`; `Views/portal/index.blade.php:5-12`; `V2/layouts/portal/metadata/seo.blade.php:36-50` |
| Renderiza `picture` com variantes WebP quando existirem | `Svc-Media` | `Views/lse/render/about.blade.php:7-14,37-46` |

### 1.3 Contrato observável (entrada → saída)

| Entrada | Resultado |
|---|---|
| `GET /sobre-nos` com payload | `200` com um bloco por seção; ordem = ordem do array; seção testimonial omitida. |
| `GET /sobre-nos` sem payload | `200` com alerta “Conteúdo da página "Sobre Nós" ainda não configurado.” (`Views/portal/index.blade.php:29-36`). |
| `GET /sobre-nos` com seção desconhecida | Nenhum HTML para aquela seção (view inexistente é ignorada) (`landing-sections-viewer.blade.php:40-47`). |
| `GET /equipe/{slug}` ativo | `200` com `<h1>` = nome, cargo, biênio, descrição, bio, LinkedIn, e-mail. |
| `GET /equipe/{slug}` de inativo/inexistente | `404`. |
| `GET /diretoria` | `301` `Location: /sobre-nos#diretoria`. |
| `GET /diretoria/{slug}` | `301` `Location: /equipe/{slug}` (não valida existência). |
| Requisição AJAX | Não usa cache de página (`shouldCache` retorna false). |
| Redis indisponível | Página é servida normalmente (fail-open) com log warning (`PageCacheMiddleware.php:62-65`). |

### 1.4 O que este módulo NÃO faz

- Não publica listagem paginada de membros nem busca pública de diretoria; só as páginas
  citadas.
- Não aceita query string para filtrar seções, biênio ou membro.
- Não gera JSON-LD `Person` para membros (a página de perfil não define schema próprio).
- Não tem meta `robots` (nem `noindex`) emitida pelas views do módulo.
- Não define canonical por membro: o canonical é sempre `url()->current()`.
- Não envia e-mail nem expõe contato por formulário; o e-mail do membro é um `mailto:`.

## 2. Abordagem

### 2.1 Estratégia em uma página

```mermaid
flowchart LR
  R[GET /sobre-nos] --> PC{page.cache HIT?}
  PC -->|sim| H[HTML do cache]
  PC -->|não| C[Controller]
  C --> CFG[(sobre_nos_sections)]
  CFG --> F[filterDisabledSections]
  F --> I[DataIntegrator]
  I --> P[prepareSections 1]
  P --> V[View portal.index]
  V --> LV[landing-sections-viewer]
  LV --> P2[prepareSections 2]
  P2 --> W[render views dos widgets]
  W --> H2[HTML + cache]
  H2 --> PUB[PublicCache headers]
  M[GET /equipe/slug] --> BM[(board_members + gallery)]
  BM --> V2[portal.member.show]
```

### 2.2 Mecânica passo a passo

1. `Routes/web.php` mapeia as rotas públicas sob `web`, `MaintenanceMiddleware`,
   `page.cache` e `public.cache` (`Routes/web.php:3-19`; `V2ServiceProvider.php:296-302`).
2. `PageCacheMiddleware` tenta `Cache::store('redis')->get('page:'.sha1(fullUrl))`; em HIT
   devolve conteúdo/status/headers com `X-Cache-Status: HIT` (`PageCacheMiddleware.php:31-41`).
3. `SobreNosPortalController@index` lê a config, filtra `sobre-nos-testimonial`, integra a
   equipe, prepara as seções, define SEO e devolve a view (`:60-86`).
4. `portal.index` estende `v2-portal::app-layout` com header/footer desligados, áreas
   laterais desligadas e `fullWidthMain=true` (`Views/portal/index.blade.php:1-22`).
5. Na `main-section-area`, inclui o `landing-sections-viewer`, que **prepara de novo** as
   seções e renderiza cada widget dentro de `div[data-section-id]`
   (`Views/portal/index.blade.php:24-28`; `landing-sections-viewer.blade.php:11-48`).
6. Cada render view consome `$data` (= `parameters` preparados) e usa
   `v2-service-media::picture` para imagens (`Views/lse/render/*`).
7. O partial de SEO monta title/description/keywords/OG/Twitter/canonical e os JSON-LD a
   partir do `SEOService` (singleton) e do parâmetro `schemas` (`metadata/seo.blade.php:1-50`).
8. `PageCacheMiddleware` grava a resposta 2xx (exceto `Set-Cookie`) por 5 min
   (`PageCacheMiddleware.php:44-59`).
9. `GET /equipe/{slug}` segue fluxo próprio: consulta com `active()->bySlug()`, resolve
   foto/variantes, converte bio, define SEO e renderiza (`SobreNosPortalController.php:112-148`).

### 2.3 Decisões de design e por quê

- **Página sem chrome do portal** `[C]`: o design da página é uma landing; header/footer
  são desligados por flags do layout (`Views/portal/index.blade.php:15-21`).
- **Seções dirigidas por widget registry** `[C]`: o portal não conhece os tipos; delega ao
  `WidgetRegistry` e à view registrada (`landing-sections-viewer.blade.php:35-47`).
- **SEO definido no controller, não na configuração** `[C]`: os textos são literais e não
  têm tela de edição (`SobreNosPortalController.php:69-83`).
- **Bio convertida no controller, não na view** `[C]`: o controller falha de forma
  tolerante e entrega `htmlBio` pronto (`:126-133`).
- **Cache de página genérico** `[C]`: o módulo não implementa cache próprio; depende do
  middleware e da limpeza por `KEYS` nas mutações.

### 2.4 Trechos de código pivô

```php
// V2/Modules/Institucional/SobreNos/Controllers/SobreNosPortalController.php:62-67
$config = LayoutBaseConfig::where('key', 'sobre_nos_sections')->first();
$sections = $config ? $config->payload : [];

$sections = $this->filterDisabledSections($sections);
$sections = $this->integrator->integrate($sections);
$sections = $this->renderer->prepareSections($sections);
```

> Pipeline do portal. Note que `filterDisabledSections` usa `array_values(array_filter())`,
> o que reindexa o array antes do render — irrelevante para a ordem visual, que é a do
> array.

```blade
{{-- V2/Modules/Institucional/SobreNos/Views/portal/index.blade.php:1-22 --}}
@extends('v2-portal::app-layout', [
    'pageTitle'             => 'Sobre Nós — SUCESU Nacional',
    'pageDescription'       => 'Conheça a SUCESU Nacional — sociedade civil sem fins lucrativos …',
    'coverImage'            => null,
    'schemas'               => [[ '@context' => 'https://schema.org', '@type' => 'AboutPage', 'name' => 'Sobre Nós — SUCESU Nacional', 'url' => url('/sobre-nos') ]],
    'showAccessibilityBar'  => true,
    'stickyAccessibilityBar'=> false,
    'showHeader'            => false,
    'showMainHeader'        => false,
    'showMainFooter'        => false,
    'showFooter'            => false,
    'showMainLeftArea'      => false,
    'showMainRightArea'     => false,
    'fullWidthMain'         => true,
])
```

> As flags de layout são o contrato visual da página. `schemas` aqui é **adicional** ao
> schema do controller; o partial de SEO renderiza os dois (um como objeto, outro como
> array), gerando JSON-LD duplicado.

```blade
{{-- V2/Modules/Institucional/SobreNos/Views/lse/render/team.blade.php:31-40 --}}
<div class="team-content">
    <h5>{{ $m['position'] ?? '' }}</h5>
    <h3><a href="{{ !empty($m['slug']) ? url('equipe/' . $m['slug']) : '#' }}">{{ $m['name'] ?? '' }}</a></h3>
    @if(!empty($m['description']))
    <p>{{ $m['description'] }}</p>
    @endif
    <div class="social-icon d-flex align-items-center">
        <a href="{{ $m['linkedin'] ?? '#' }}" target="_blank" rel="noopener noreferrer"
           aria-label="Perfil no LinkedIn — abre em nova aba"><i class="bi bi-linkedin" aria-hidden="true"></i></a>
    </div>
</div>
```

> O link do card depende de `slug` (vem do banco); sem slug, o href é `#`. O LinkedIn
> sempre é renderizado, mesmo sem URL — vira um link para `#` com `target=_blank`.

```blade
{{-- Views/lse/render/counters.blade.php:7-22 --}}
@foreach($data['counters'] as $counter)
<div class="counter-box-items">
    @if(!empty($counter['icon']))
    <div class="icon icon2" style="width:120px !important;height:120px !important;…">
        @include('v2-service-media::components.picture', [
            'originalPath' => $counter['icon'],
            'variants'     => $counter['icon_variants'] ?? [],
            'alt'          => $counter['label'] ?? '',
            'lazy'         => true,
            'style'        => 'width:100% !important;height:100% !important;object-fit:contain !important;',
        ])
    </div>
    @endif
    <h2><span class="count">{{ $counter['value'] ?? '' }}</span>{{ $counter['suffix'] ?? '' }}</h2>
    <p>{{ $counter['label'] ?? '' }}</p>
</div>
@endforeach
```

> O valor e o sufixo são concatenados sem espaço (ex.: `45` + `+`, `100` + `mil+`). A
> classe `count` é animada por JS do tema; sem JS, o número aparece estático.

```blade
{{-- Views/portal/member/show.blade.php:22-35 --}}
@if($photoUrl)
@include('v2-service-media::components.picture', [
    'originalPath' => $member->getPhotoUrl(),
    'variants'     => $variants,
    'alt'          => $member->name,
    'class'        => 'img-fluid w-100',
    'lazy'         => false,
    'style'        => 'border-radius:8px;max-height:400px;object-fit:cover;',
])
@else
<div style="width:100%;height:300px;background:var(--color-bg-canvas, #e9ecef);…">
    <i class="bi bi-person-fill" style="font-size:5rem;color:var(--color-text-muted, #adb5bd);"></i>
</div>
@endif
```

> A página do membro não usa lazy na foto principal (LCP) e mantém proporção com
> `max-height:400px`/`object-fit:cover`. O placeholder é um ícone Bootstrap sem
> `aria-hidden` (ruído para leitor de tela).

### 2.5 Algoritmos, consultas e custos

| Página | Queries por request (cache MISS) | Observação |
|---|---|---|
| `/sobre-nos` | 1 config + 3 da equipe (biênio, membros, mídias) + 1 contagem de associados (×2) + 1+N regionais (×2) | Sem cache de dados; `picture` não consulta o banco (recebe caminho e variantes já preparados) |
| `/sobre-nos` (HIT) | 0 (HTML do Redis) | TTL 5 min |
| `/equipe/{slug}` | 1 membro + 1 mídia + 1 biênio | Bio convertida em memória |
| Qualquer | 1 acesso ao buffer SEO (get/put no cache) por request de metadata | Flush em lote de 50 |

## 3. Particularidades

### 3.1 O que só existe aqui

- **Landing institucional sem header/footer** — o único módulo do grupo Institucional que
  desliga o chrome do portal por flags de layout.
- **Seção de diretoria com âncora fixa `#diretoria`**, usada pelo CTA, pelo breadcrumb da
  página do membro e pelos 301 de `/diretoria`.
- **Dois caminhos de SEO** (controller + view) com JSON-LD duplicado.
- **Página de membro com foto sticky** (`position:sticky; top:2rem` em `.member-photo-card`)
  e conteúdo em `col-lg-8`.

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

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| RN-SOBRENOS-P01 | Toda seção renderizada recebe `div[data-section-id]` com o id do payload | IDs são a única marca estável no DOM; mudam ao renormalizar sem id | `landing-sections-viewer.blade.php:41-46` |
| RN-SOBRENOS-P02 | Seção sem view registrada é simplesmente ignorada | Não há mensagem de erro para o visitante | `:40-47` |
| RN-SOBRENOS-P03 | O hero de breadcrumb exibe **sempre** “Sobre nós” e “Home”, ignorando título da seção | O `title` do widget about não vira `<h1>` | `Views/lse/render/about.blade.php:17-25` |
| RN-SOBRENOS-P04 | O contador de destaque do about concatena `%` fixo ao valor | Seed `78` → “78%” | `Views/lse/render/about.blade.php:48-53` |
| RN-SOBRENOS-P05 | Títulos dos ícones do about são impressos com `{!! !!}` (HTML permitido) | `<br>` é intencional e documentado no form | `:88,102`; `Views/lse/forms/about.blade.php:79` |
| RN-SOBRENOS-P06 | O link “LinkedIn” da seção team é renderizado mesmo sem URL (href `#`) | Ícone clicável inútil | `Views/lse/render/team.blade.php:37-40` |
| RN-SOBRENOS-P07 | A seção team tem `id="diretoria"` fixo | Âncoras antigas continuam funcionando | `:2` |
| RN-SOBRENOS-P08 | `/sobre-nos` define title/description/keywords no controller; a view repete título/descrição como fallback | Em caso de mudança, o controller vence | `SobreNosPortalController.php:69-76`; `Views/portal/index.blade.php:2-3` |
| RN-SOBRENOS-P09 | A view adiciona um `AboutPage` sem `description`; o controller adiciona outro com `description` | Dois blocos JSON-LD com URLs iguais | `:5-12`; `SobreNosPortalController.php:77-83`; `metadata/seo.blade.php:36-50` |
| RN-SOBRENOS-P10 | A página do membro não define schema JSON-LD | Só há metatags | `SobreNosPortalController.php:137-143`; `Views/portal/member/show.blade.php:1-13` |
| RN-SOBRENOS-P11 | O canonical é sempre `url()->current()` (sem override) | `/equipe/x` e `/sobre-nos` são canônicos de si mesmos | `metadata/seo.blade.php:30-31` |
| RN-SOBRENOS-P12 | `og:image` cai para a logo do layout (`v2_layout_base_configs.key='logos'`, `logo_header`) quando não há capa | `/sobre-nos` usa a logo; o membro usa a foto | `V2/Services/SEO/Services/SEOService.php:375-397`; `SobreNosPortalController.php:141-143` |
| RN-SOBRENOS-P13 | O cache de página é por URL completa (inclui query) e ignora `Set-Cookie` | Query string cria chaves distintas | `PageCacheMiddleware.php:44-56,88-91` |
| RN-SOBRENOS-P14 | `trackAccess` ignora extensões de asset e prefixos `cms/`, `api/`, etc. | Visitas a `/sobre-nos` e `/equipe/*` são contadas | `SEOService.php:54-116` |

### 3.3 Casos-limite e comportamentos surpreendentes

- **Biênio trocado no CMS**: o portal só reflete após a limpeza do cache (best-effort); em
  falha de Redis, até 5 min de defasagem.
- **Membro desativado**: `/equipe/{slug}` passa a 404, mas se a URL estava em cache o 404
  pode demorar até 5 min.
- **Foto removida da Galeria**: o caminho em `photo` continua no HTML e gera 404 de imagem.
- **Membro sem `slug`** (registro legado): o card aponta para `#` e a página individual não
  é alcançável; o sitemap também não a lista.
- **Título do about vazio**: o render cai no default “Sobre a SUCESU”; o hero continua
  “Sobre nós”.
- **Seção about sem imagens**: o hero fica só com o gradiente sobre fundo transparente; os
  blocos de ícone só aparecem se houver `icon_1`/`icon_2` preparados.
- **Contador com valor vazio**: imprime só o sufixo (ex.: “mil+”).
- **`description` do membro vazia**: meta description cai no literal “Membro da Diretoria
  Executiva da SUCESU Nacional.”; a seção team usa a frase padrão do integrador.
- **`bio` inválida**: `toHtml` devolve string vazia; a seção “Biografia” não é renderizada.
- **Membro com e-mail**: o link `mailto:` mostra o endereço completo (potencial coleta por
  bots; não há ofuscação).
- **Página com muitas seções**: todas são preparadas 2× e renderizadas no mesmo request;
  não há paginação.

### 3.4 Armadilhas de reimplementação

- **Não** recrie o hero como conteúdo editável: ele é literal no Blade e ignora
  `parameters.title`; só a imagem vem do widget.
- **Não** altere `id="diretoria"` sem atualizar CTA, breadcrumbs e redirects.
- **Não** remova o segundo `prepareSections` sem revisar os widgets: os `prepare()` são
  idempotentes, mas o viewer depende deles para resolver imagens quando usado fora deste
  módulo (ex.: preview).
- **Não** gere `Person` JSON-LD “porque faz sentido”: o legado não gera e a paridade de SEO
  seria diferente.
- **Não** conte com `showHeader=false` para esconder o header em outras páginas: são flags
  do array de layout, avaliadas apenas nesta view.
- **Não** use o `pageDescription` da view como fonte: o controller sempre define a
  descrição antes, então o fallback nunca é usado em `/sobre-nos`.

### 3.5 Divergências documentação × código e dívidas

- Sitemap publica `/diretoria` e `/diretoria/{slug}` (301) em vez de `/sobre-nos` e
  `/equipe/{slug}` (`V2/Services/SEO/Controllers/SeoPortalController.php:295-323`).
- `Views/portal/index.blade.php:5-12` repete o schema sem `description`, duplicando o do
  controller; o partial ainda embrulha o array inteiro num único `<script>` (`metadata/seo.blade.php:46-50`).
- O `pageDescription` da view é redundante com `setDescription` do controller.
- A seção `sobre-nos-testimonial` existe no payload mas é filtrada; não há aviso no CMS.
- `Views/lse/render/about.blade.php:2` define `$bcUrl = url('/')` e não usa variável de
  configuração para o link “Home”.

## 4. Integrabilidade

### 4.1 Superfície pública

| Símbolo | Tipo | Contrato |
|---|---|---|
| `v2-sobrenos::portal.index` | view | `$sections` (array preparado) |
| `v2-sobrenos::portal.member.show` | view | `$member`, `$photoUrl`, `$variants`, `$htmlBio`, `$bienniumLabel` |
| `v2-sobrenos::lse.render.about` | view de seção | `$data` = `parameters` (com `breadcrumb_image`, `image`, `image_2`, `icon_1`, `icon_2`, `*_variants`, textos) |
| `v2-sobrenos::lse.render.team` | view de seção | `$data` = `subtitle`, `title`, `description`, `members[]`, `biennium_label` |
| `v2-sobrenos::lse.render.video` | view de seção | `$data` = `subtitle`, `title`, `youtube_url`, `watch_label`, `background_image` |
| `v2-sobrenos::lse.render.counters` | view de seção | `$data['counters'][]` = `icon`, `value`, `suffix`, `label` |
| `SobreNosPortalController@index/show` | HTTP | ver §5 |
| `BoardMember::getPhotoFullUrl/getHtmlBio` | model | usados pela view do membro |

### 4.2 Contrato de UI/JS

- **Slots do layout**: apenas `main-section-area`; a página do membro também empilha
  `late-styles` com o CSS do perfil (`Views/portal/member/show.blade.php:103-124`).
- **Componentes**: `landing-sections-viewer` (props `sections`), `picture`
  (`originalPath`, `variants`, `alt`, `class`, `lazy`, `style`), usado com `lazy=false`
  no topo (breadcrumb, vídeo, foto do membro) e `lazy=true` no restante.
- **JS**: nenhum JS próprio do módulo. Depende do JS do tema para `count` (contadores) e
  `video-popup` (lightbox do YouTube) — classes `count`, `video-popup`, `ripple`.
- **Sem JS**: contadores aparecem com o valor final; o vídeo abre o link do YouTube na
  mesma aba (sem modal).
- **DOM estável**: `#diretoria`, `div[data-section-id]`, `.team-box-items`,
  `.counter-box-items`, `.member-photo-card`, `.member-info-card`.

### 4.3 Ordem e ciclo de vida

1. `PageCacheMiddleware` antes do controller; em HIT, nem o banco é consultado.
2. Controller → view → viewer → widgets → partial SEO (composer do `SEOServiceProvider`).
3. `PublicCacheMiddleware` depois do controller, aplicando headers.
4. `PageCacheMiddleware` grava a resposta ao final.

### 4.4 Receitas de uso

```blade
{{-- Renderizar o perfil de um membro em outro contexto (dados já carregados) --}}
@include('v2-sobrenos::portal.member.show', compact('member','photoUrl','variants','htmlBio','bienniumLabel'))
```

```php
// Montar a lista de seções como o portal faz (para um endpoint de prévia)
$sections = app(\V2\Services\LandingSectionsEditor\Services\LandingSectionsEditorRendererService::class)
    ->prepareSections(
        app(\V2\Modules\Institucional\SobreNos\Services\SobreNosDataIntegrator::class)
            ->integrate($payload)
    );
```

```http
GET /diretoria/maria-silva HTTP/1.1
=> HTTP/1.1 301 Moved Permanently
   Location: /equipe/maria-silva
```

### 4.5 Anti-uso

- **Colocar HTML de layout no payload** (header/footer): o portal os desliga por flags;
  qualquer chrome inserido via seção some ou duplica.
- **Depender de `data-section-id` como chave de analytics**: o id muda quando o payload é
  renormalizado sem id (`SobreNosSectionNormalizer`).
- **Cachear a página com TTL maior sem invalidação**: as mutações limpam `page:*` via
  `KEYS`; um cache paralelo (CDN) ficaria defasado.

### 4.6 Custos e limites da integração

- HTML cacheado inteiro no Redis por 5 min; páginas com muitas imagens aumentam o valor.
- `PublicCacheMiddleware` permite cache de navegador/CDN por 60 s.
- Sem paginação/lazy loading de seções; todas são renderizadas no mesmo request.
- A visita SEO é bufferizada (até 50) para evitar um insert por pageview.

## 5. Rotas e contratos HTTP

| Tipo | Método | URI | Nome | Middleware | Controller@ação | Efeito |
|---|---|---|---|---|---|---|
| endpoint | GET | `/sobre-nos` | `v2.sobrenos.portal.index` | web; maintenance.v2; page.cache; public.cache | `SobreNosPortalController@index` | 200 com seções; cache 5 min (`Routes/web.php:6-7`) |
| endpoint | GET | `/equipe/{slug}` | `v2.sobrenos.portal.member.show` | idem | `show` | 200 ou 404 (`:10-11`) |
| redirect | GET | `/diretoria` | `v2.diretoria.portal.index.redirect` | idem | `redirectOldDiretoria` | 301 `/sobre-nos#diretoria` (`:14-15`) |
| redirect | GET | `/diretoria/{slug}` | `v2.diretoria.portal.bio.redirect` | idem | `redirectMember` | 301 `/equipe/{slug}` (`:17-18`) |

**SEO de `/sobre-nos`** (origem):

| Tag | Valor | Origem |
|---|---|---|
| `<title>` | `Sobre Nós — SUCESU Nacional` (o `siteTitle` só entra quando `seoTitle` é nulo) | `setTitle` + partial (`metadata/seo.blade.php:2`) |
| `meta description` | “Conheça a SUCESU Nacional — sociedade civil sem fins lucrativos que conecta lideranças e impulsiona o futuro da tecnologia da informação e telecomunicações no Brasil.” | `setDescription` |
| `meta keywords` | “SUCESU, sobre, história, diretoria, biênio, associados, tecnologia, TIC, institucional” | `setKeywords` |
| `og:type` | `website` | literal do partial |
| `og:url` / `twitter:url` | URL atual | literal |
| `og:image` / `twitter:image` | logo do layout (fallback) | `SEOService::getCoverImage` |
| `canonical` | URL atual | literal |
| JSON-LD | 2 × `AboutPage` (um com `description`, um sem) | controller + view |

**SEO de `/equipe/{slug}`**: title `<nome> — Diretoria SUCESU Nacional`; description =
`description` do membro ou “Membro da Diretoria Executiva da SUCESU Nacional.”; keywords
`SUCESU, diretoria, <nome>, <cargo>`; `og:image` = URL da foto quando existir; sem JSON-LD
(`SobreNosPortalController.php:137-143`).

## 6. Dados e modelo de domínio

Os dados exibidos vêm de `v2_layout_base_configs` (payload), `v2_board_members` (biênio
corrente, ativos, ordenados), `v2_bienniums` e `v2_gallery_media` (foto e variantes).
Formato e donos em [`dados.md`](dados.md) §6.

## 7. Fluxos e estados

```mermaid
sequenceDiagram
  participant B as Navegador
  participant P as PageCacheMiddleware
  participant C as SobreNosPortalController
  participant D as Banco
  B->>P: GET /sobre-nos
  alt HIT
    P-->>B: HTML (X-Cache-Status: HIT)
  else MISS
    P->>C: next()
    C->>D: config + biênio + membros + mídias + contadores
    C-->>P: view renderizada
    P->>P: put(page:sha1, 300s)
    P-->>B: HTML (X-Cache-Status: MISS)
  end
```

- `FL-SOBRENOS-P01` — primeira carga (MISS): pipeline completo + gravação de cache.
- `FL-SOBRENOS-P02` — carga cacheada (HIT): sem banco; headers preservados.
- `FL-SOBRENOS-P03` — página de membro: consulta, SEO, render (também cacheável).
- `FL-SOBRENOS-P04` — redirects legados.

## 8. UI CMS

Não se aplica; ver [`cms.md`](cms.md).

## 9. UI Portal

### 9.1 Estrutura de `/sobre-nos`

Ordem das seções = ordem do payload. Cada uma é embrulhada em
`<div data-section-id="<id>" class="lse-render-section-block">`. Renderizações esperadas
para o seed:

| Ordem | Tipo | Estrutura | Heading |
|---|---|---|---|
| 1 | `sobre-nos-about` | `.breadcrumb-wrapper` (imagem de fundo + gradiente + “Home > Sobre nós” + `<h1>Sobre nós</h1>`) e `section.about-section-2` (imagem principal + contador de destaque + imagem secundária + subtítulo + `<h2>` + ícones + descrições + CTA) | `h1` fixo + `h2` do parâmetro `title` |
| 2 | `sobre-nos-team` | `section#diretoria.team-section` com título central e grade de cards `col-lg-6` | `h2` do parâmetro `title` |
| 3 | `sobre-nos-video` | seção com imagem de fundo + subtítulo + `<h2 class="text-white">` + botão play + rótulo | `h2` |
| 4 | `sobre-nos-counters` | `section.counter-section` com 3 blocos (ícone, `<h2><span class="count">valor</span>sufixo</h2>`, label) | `h2` por contador |
| — | `sobre-nos-testimonial` | **não renderiza** | — |

### 9.2 Conteúdo e escopos

- **About**: usa apenas `parameters` preparados; imagens são IDs resolvidos em caminhos.
- **Team**: `members` vem do banco (nunca do payload); cards mostram cargo, nome (link),
  descrição e LinkedIn. Sem e-mail no card.
- **Video**: `youtube_url` é um link direto (não há embed); a classe `video-popup` depende
  do JS do tema.
- **Counters**: valores recalculados por rótulo (§3 de [`widgets-e-integracoes.md`](widgets-e-integracoes.md)).
- **Página do membro**: foto (sticky), LinkedIn, e-mail, breadcrumb “Sobre Nós > Diretoria >
  nome”, `<h1>` nome, cargo, biênio, descrição em `.lead`, “Biografia” com HTML rico e
  botão “Voltar para Diretoria”.

### 9.3 SEO

Ver §5. Não há `robots`, `og:locale` nem `twitter:site`; `og:type` é sempre `website`.

### 9.4 Mídia

- `picture` recebe caminho original + variantes WebP (mapa largura→caminho) e gera
  `<picture>` com `source` WebP; sem variantes, usa `<img>` simples.
- `alt`: nome do membro/label do contador/título do ícone; imagens decorativas recebem
  `alt=""` (shapes, ícone de aspas).
- Lazy: `false` no breadcrumb, no fundo do vídeo e na foto do membro; `true` nas demais.
- Dimensões: foto da equipe 100%×300 px; ícones 120×120 (contain); about 160 px para a
  imagem secundária; perfil `max-height:400px`.

### 9.5 Responsividade e print

- Grades Bootstrap: `col-lg-6` (equipe), `col-lg-6` (about), `col-lg-4`/`col-lg-8`
  (perfil), `col-lg-8` (contadores). Em telas pequenas tudo empilha (colapso padrão do
  Bootstrap 5).
- Não há regra `@media print` nas views do módulo; a impressão usa o CSS do tema
  (`print-header` é do layout do portal).

### 9.6 Cache

- **Página**: Redis, chave `page:<sha1(fullUrl)>`, TTL 300 s, invalidada por
  `clearPageCache()` nas mutações (`SobreNosCmsController.php:94-106`;
  `BoardMemberController.php:256-268`).
- **Navegador/CDN**: `Cache-Control: public, max-age=60, must-revalidate` (e `Vary: Accept-Encoding`) via `PublicCacheMiddleware`.
- **Visitas SEO**: buffer `seo_access_buffer` no cache (24 h de expiração) com flush a
  cada 50 entradas (`SEOService.php:153-166`).
- **Sem cache de dados** no módulo (contadores e payload são lidos a cada MISS).

### 9.7 Acessibilidade

- `aria-label` em links de ícone (LinkedIn, e-mail); breadcrumb com `nav aria-label="Breadcrumb"`
  e `aria-current="page"`.
- `<h1>` no hero do about (`Sobre nós`) e no nome do membro; são páginas distintas, então
  nunca há dois `<h1>` na mesma resposta.
- `target="_blank"` sempre com `rel="noopener noreferrer"`.
- Lacunas: ícones decorativos sem `aria-hidden` no placeholder do membro e no hero;
  `alt` de shapes vazio (correto) mas o ícone de aspas usa `alt=""` + `aria-hidden` só no
  fallback; contadores não anunciam mudança (são estáticos no HTML).

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

- Nenhuma config própria; o SEO é literal.
- Rotas públicas não exigem autenticação e **não** são afetadas por
  `module.active:SobreNos` (a flag só é aplicada ao grupo CMS).
- `MaintenanceMiddleware` pode bloquear as rotas quando o portal está em manutenção.
- `config('v2.portal.favicon_url')` e `titulo_site` vêm do layout global, não do módulo.

## 11. Observabilidade, erros e limites

- Logs: `[SobreNosPortal] Falha ao renderizar bio: …` (warning);
  `[PageCache] Redis indisponivel, servindo sem cache: …` (warning);
  `[SEO] Falha ao rastrear acesso (Buffer): …` (warning).
- Erros HTTP: `404` (slug), `301` (legados), `503` (manutenção).
- Limites: sem paginação; cache de 5 min; 60 s de cache público; `fullUrl` como chave.

## 12. Seeds e dados iniciais

O portal depende do payload semeado (`SobreNosDefaultSectionsSeeder`) e de pelo menos um
biênio corrente com membros ativos para exibir a diretoria. Sem biênio, a seção team mostra
o texto padrão. Ver [`dados.md`](dados.md) §12.

## 13. Lacunas

- `LG-SOBRENOS-P01` — Não foi possível confirmar o CSS final de `.about-*`, `.counter-*` e
  `.team-*` (tema público fora do manifest); a paridade visual exata depende do tema.
- `LG-SOBRENOS-P02` — Não há dados de tráfego para dimensionar o impacto do cache de 5 min
  nem do `KEYS` na limpeza.
- `LG-SOBRENOS-P03` — O comportamento do lightbox `video-popup` (JS do tema) não foi
  inspecionado; só a marcação é conhecida.
- `LG-SOBRENOS-P04` — Não está claro se o sitemap será corrigido para `/equipe/*`; hoje ele
  publica URLs que respondem 301.

## 14. Checklist de paridade

- [ ] `PC-SOBRENOS-P01` — `/sobre-nos` responde 200 com um bloco por seção na ordem do payload.
- [ ] `PC-SOBRENOS-P02` — Seção testimonial não gera HTML.
- [ ] `PC-SOBRENOS-P03` — Hero do about mostra “Home > Sobre nós” e `<h1>Sobre nós</h1>` (`Views/lse/render/about.blade.php:17-25`).
- [ ] `PC-SOBRENOS-P04` — Contador de destaque do about exibe `<valor>%` (`:48-53`).
- [ ] `PC-SOBRENOS-P05` — Seção team tem `id="diretoria"` e cards com cargo/nome/descrição/LinkedIn (`Views/lse/render/team.blade.php:2-40`).
- [ ] `PC-SOBRENOS-P06` — Sem membros, a seção team mostra “Em breve, conheça nossa diretoria.” (`:47-49`).
- [ ] `PC-SOBRENOS-P07` — Vídeo usa `youtube_url` como href e classe `video-popup` (`Views/lse/render/video.blade.php:21-28`).
- [ ] `PC-SOBRENOS-P08` — Contadores concatenam valor e sufixo sem espaço (`Views/lse/render/counters.blade.php:20`).
- [ ] `PC-SOBRENOS-P09` — `/equipe/{slug}` só abre membro ativo; inativo/inexistente → 404.
- [ ] `PC-SOBRENOS-P10` — Perfil exibe bio como HTML e esconde a seção se vazia (`Views/portal/member/show.blade.php:81-88`).
- [ ] `PC-SOBRENOS-P11` — Foto do perfil não é lazy; demais imagens usam lazy quando aplicável.
- [ ] `PC-SOBRENOS-P12` — `/diretoria` e `/diretoria/{slug}` respondem 301 para os novos caminhos.
- [ ] `PC-SOBRENOS-P13` — `X-Cache-Status` HIT/MISS e TTL de 5 min em `/sobre-nos`.
- [ ] `PC-SOBRENOS-P14` — Mutações no CMS limpam `page:*` (best-effort).
- [ ] `PC-SOBRENOS-P15` — Title/description/keywords e canonical conforme §5.
- [ ] `PC-SOBRENOS-P16` — JSON-LD `AboutPage` presente (com a duplicação documentada).

## 15. Evidências consultadas

| Arquivo | Faixas |
|---|---|
| `Views/portal/index.blade.php` | 1-37 |
| `Views/portal/member/show.blade.php` | 1-124 |
| `Views/lse/render/about.blade.php` | 1-126 |
| `Views/lse/render/team.blade.php` | 1-52 |
| `Views/lse/render/video.blade.php` | 1-34 |
| `Views/lse/render/counters.blade.php` | 1-29 |
| `Views/lse/render/testimonial.blade.php` | 1-92 (não renderizado) |
| `Controllers/SobreNosPortalController.php` | 33-169 |
| `Routes/web.php` | 3-19 |
| `V2/layouts/portal/{app-layout,base,metadata/seo}.blade.php` | conforme citado |
| `V2/Core/Middleware/{PageCacheMiddleware,PublicCacheMiddleware}.php` | conforme citado |
| `V2/Services/SEO/Services/SEOService.php` | 121-198, 290-400 |
| `V2/Services/LandingSectionsEditor/Views/components/landing-sections-viewer.blade.php` | 1-48 |
| `V2/Services/Media/Views/components/picture.blade.php` | 1-60 |
