---
id: DATA-SEEDS-BASICPAGES
tipo: dados
onda: 5
status: concluido
unidades: [DATA-SEEDS-BASICPAGES]
fontes:
  - V2/Seeds/BasicPages/
depende_de: [CORE-BOOT, CORE-DATA, SVC-GALLERY, SVC-MEDIA, MOD-LAYOUTS-BASICPAGE]
consumidores: [MOD-LAYOUTS-BASICPAGE, MOD-LAYOUTS-HOMEPAGE, MOD-PUBLICACOES-AGENDA, MOD-PUBLICACOES-NOTICIAS, MOD-INSTITUCIONAL-HISTORIA]
criticidade: media
dono: agente-dados-iniciais
---

# Seeds de BasicPages

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Grava páginas HTML prontas em `v2_basic_pages`, cinco marcos em `v2_historia_milestones`, configurações de portal e a payload `landing_sections` da homepage, além de subir imagens para a Galeria. |
| Entrada | `php artisan db:seed` (via `DatabaseSeeder::callSeeds()`), `php artisan db:seed --class=V2\Seeds\BasicPages\...` ou chamada programática; não há request HTTP. |
| Saída | Linhas em `v2_basic_pages`, `v2_historia_milestones`, `v2_portal_configs`, `v2_layout_base_configs`, `v2_gallery_media` e `v2_basic_page_assets`; arquivos WebP/PNG no disco de mídia. |
| Efeitos colaterais | `Log::warning` por SVG sem variantes; warnings de imagem ausente; limpeza best-effort do page cache Redis; nenhum e-mail, nenhuma auditoria (`ActivityLogger`) e nenhuma sincronização de índice por estas escritas. |
| Particularidade central | Cada execução **sobrescreve** o conteúdo editado das páginas e dos marcos e **reprocessa todas as imagens**, criando novas linhas em `v2_gallery_media`; a idempotência é só por chave natural, nunca por hash de conteúdo. |
| Como se integra | `DatabaseSeeder::callSeeds()` instancia `V2\Seeds\BasicPages\*`; o trait `ProcessesGalleryImages` usa `GalleryService::seedFromPath()` e `BasicPageAssetManagerService::createFromGallery()`. |
| Se removido, quebra | As páginas `/convenios` (rota runtime), a linha SEO de `homepage`, as seções `landing_sections`, as 5 linhas da linha do tempo, as logos/links/contato dos layouts e as imagens da Galeria usadas por `DemoContentSeeder`. |
| Superfície pública | Classes `AgendaSeeder`, `AssociadosSeeder`, `ConveniosSeeder`, `FaleConoscoSeeder`, `HistoriaMilestonesSeeder`, `NoticiasSeeder`, `ParceirosSeeder` (`ParceirosInstitucionaisSeeder.php`), `SucesuHomepageSeeder`; trait `ProcessesGalleryImages`. |

## 1. Efeito

### 1.1 O que existe depois da execução

| Seeder | Chave natural | Tabelas escritas | Registros | Ordem em `callSeeds()` |
|---|---|---|---|---|
| `SucesuHomepageSeeder` | `v2_portal_configs.config_key`; `v2_layout_base_configs.key`; `v2_basic_pages.slug='homepage'` | `v2_portal_configs`, `v2_layout_base_configs`, `v2_gallery_media`, `v2_basic_page_assets`, `v2_basic_pages` | 8 configs; 5 payloads de layout; 8 seções; 1 página atualizada; 26 uploads de imagem; 17 assets | 1 |
| `ParceirosSeeder` | `v2_basic_pages.slug='parceiros-institucionais'` | `v2_basic_pages`, `v2_gallery_media` | 1 página com 6 parceiros; 7 uploads (breadcrumb + 6 logos) | 3 |
| `NoticiasSeeder` | `v2_basic_pages.slug='noticias'` | `v2_basic_pages`, `v2_gallery_media` | 1 página com 9 cards; 10 uploads | 4 |
| `ConveniosSeeder` | `v2_basic_pages.slug='convenios'` | `v2_basic_pages`, `v2_gallery_media` | 1 página com 9 categorias; 9 uploads | 5 |
| `FaleConoscoSeeder` | `v2_basic_pages.slug='fale-conosco'` | `v2_basic_pages`, `v2_gallery_media` | 1 página; 1 upload | 6 |
| `AgendaSeeder` | `v2_basic_pages.slug='agenda'` | `v2_basic_pages`, `v2_gallery_media` | 1 página com 4 eventos; 5 uploads | 7 |
| `HistoriaMilestonesSeeder` | `v2_historia_milestones.title` | `v2_historia_milestones`, `v2_gallery_media` | 5 marcos; 5 uploads | 9 |
| `AssociadosSeeder` | `v2_basic_pages.slug='associados'` | `v2_basic_pages`, `v2_gallery_media` | 1 página com 9 categorias; 9 uploads | **não chamado** |
| `ProcessesGalleryImages` (trait) | — | `v2_gallery_media`, `v2_basic_page_assets` | Uma linha por chamada de `processImage()`/`registerBasicPageAsset()` | 0 (utilitário) |

Todas as páginas gravadas por estes seeders têm `content_type='htmlmixed'` e `is_runtime=true`, exceto a homepage, que é rebaixada a `is_runtime=false` e `content=null` (`V2/Seeds/BasicPages/SucesuHomepageSeeder.php:397-410`). O `metadata` carrega `seo` (title, description, keywords e `json_ld`) e, nas páginas runtime, um bloco `layout` com os nove flags de layout (`V2/Seeds/BasicPages/AgendaSeeder.php:70-87`).

O efeito visual do bloco `layout` é desligar o chrome do portal nessas páginas: `getLayoutConfig()` parte de `showHeader/showMainHeader/showMainFooter/showFooter = true` e o `metadata.layout` salvo sobrescreve os quatro para `false` (`V2/Modules/Layouts/BasicPage/Models/BasicPage.php:169-191`). Como as páginas já trazem breadcrumb, menu não é renderizado duas vezes.

### 1.2 Contrato observável

| Entrada | Resultado |
|---|---|
| `db:seed --class=V2\Seeds\BasicPages\AgendaSeeder` em base vazia | Cria a página `agenda`, 4 cards de evento e 5 mídias; log `✓ Agenda — página criada com sucesso.` (`V2/Seeds/BasicPages/AgendaSeeder.php:95-101`). |
| Mesma execução em base já semeada | `UPDATE` da linha por `slug`, preservando `id`/`created_at` e substituindo `title`, `content`, `is_runtime` e `metadata` (`V2/Seeds/BasicPages/AgendaSeeder.php:88-94`). |
| `db:seed --class=V2\Seeds\BasicPages\HistoriaMilestonesSeeder` duas vezes | 5 marcos no total: o segundo run encontra cada um por `title` e atualiza (`V2/Seeds/BasicPages/HistoriaMilestonesSeeder.php:155-172`). |
| Imagem de origem ausente | `processImage()` emite `  ! Imagem não encontrada: <caminho>` e devolve `null`; a página é gravada sem `<picture>` (`V2/Seeds/BasicPages/ProcessesGalleryImages.php:32-35`). |
| SVG de ícone | O upload é feito, mas nenhuma variante WebP é gerada; registra `Log::warning("[processImage] Nenhuma variante WebP gerada para ...")` e segue (`V2/Seeds/BasicPages/ProcessesGalleryImages.php:42-50`). |
| Falha no meio de um seeder transacional | `DB::rollBack()` e rethrow; a mensagem é `Erro no <Seeder>: <mensagem>` (`V2/Seeds/BasicPages/AgendaSeeder.php:96-101`). |

### 1.3 O que NÃO acontece

- **Não** cria rotas: as páginas dependem do catch-all `GET /{slug}` (`V2/Core/Routes/fallback.php:19-21`), que só é consultado se nenhum módulo já registrou o slug. Por isso `associados`, `agenda`, `noticias`, `fale-conosco` e `parceiros-institucionais` são sombreados pelas rotas de módulo (`V2/Modules/Conexoes/Associados/Routes/web.php:14-18`, `V2/Modules/Publicacoes/Agenda/Routes/web.php:7-10`, `V2/Modules/Publicacoes/Noticias/Routes/web.php:15-18`, `V2/Modules/Institucional/FaleConosco/Routes/web.php:7-10`, `V2/Modules/Conexoes/ParceirosInstitucionais/Routes/web.php:7-10`). Só `convenios` não tem rota dedicada.
- **Não** cria registros de notícias, eventos, parceiros ou associados: `NoticiasSeeder`, `AgendaSeeder`, `ParceirosSeeder` e `AssociadosSeeder` gravam apenas HTML em `v2_basic_pages`. Os registros reais vêm de `DemoContentSeeder` ou do CMS.
- **Não** dispara eventos Eloquent nas páginas: as escritas usam `DB::table('v2_basic_pages')` (`V2/Seeds/BasicPages/NoticiasSeeder.php:101-105`), portanto `BasicPage::saved`/`syncToSearch()` não roda. O índice público só é preenchido no próximo boot de console e apenas para páginas **ausentes** do índice (`V2/Core/Providers/V2ServiceProvider.php:146-165`); atualizações de título/conteúdo ficam sem reindexação.
- **Não** apaga nem trunca: não há `delete`, `truncate` ou `forceDelete` nesta unidade. Marcos removidos do array continuam no banco.
- **Não** envia e-mail, não grava auditoria e não invalida cache de navegação além do DELETE de `page:*` no Redis da homepage.

## 2. Abordagem

### 2.1 Estratégia

Cada seeder monta o HTML em memória, sobe as imagens de referência pela Galeria (para gerar WebP e variantes responsivas) e faz um upsert manual por chave natural. A homepage é o caso especial: além das páginas, grava configurações do portal, payloads de layout e as seções da landing page, sem transação única.

```mermaid
flowchart LR
  A[assets de REFERENCE/ ou assets_sucesu/] --> B[processImage]
  B --> C[GalleryService::seedFromPath]
  C --> D[v2_gallery_media + WebP]
  B --> E[processImage retorna file_path/variants/id]
  E --> F[HTML com pictureSnippet]
  F --> G[v2_basic_pages]
  E --> H[registerBasicPageAsset]
  H --> I[v2_basic_page_assets]
  G --> J[fallback GET /{slug} ou SEO da homepage]
```

### 2.2 Ordem global e dependências

| # | Etapa | Depende de | Por quê |
|---|---|---|---|
| 1 | `NacionalUnidadeRegionalSeeder` (fora desta unidade) | migrations | Cria a unidade `NAC` usada por `DemoContentSeeder` (`database/seeds/NacionalUnidadeRegionalSeeder.php:9-22`). |
| 2 | `SucesuHomepageSeeder` | migrations, Galeria | Precisa rodar **antes** de `StructuralPortalSeeder`, porque grava `titulo_site = 'SUCESU Nacional'`, que é o gate do conteúdo SLE (`V2/Seeds/StructuralPortalSeeder.php:60-67`). |
| 3 | `SobreNosDefaultSectionsSeeder` (outra unidade) | Galeria | Seções da página Sobre Nós. |
| 4-8 | `ParceirosSeeder` → `NoticiasSeeder` → `ConveniosSeeder` → `FaleConoscoSeeder` → `AgendaSeeder` | Galeria | Independentes entre si; a ordem é apenas a do `callSeeds()`. |
| 9 | `StructuralPortalSeeder` → assets → header → footer → configs | `titulo_site`, Galeria, Storage | Header/footer recebem o array de assets retornado por `SleAssetSeeder` (`V2/Seeds/StructuralPortalSeeder.php:76-95`). |
| 10 | `HistoriaMilestonesSeeder` | Galeria | Independente. |
| 11 | `DemoContentSeeder` (não-produção) | Galeria já populada, `NAC`, regionais | Resolve capas/logos por `description` em `v2_gallery_media` (`V2/Seeds/Content/DemoContentSeeder.php:1640-1652`). |

O `DatabaseSeeder` executa essa sequência literalmente em `callSeeds()` (`database/seeds/DatabaseSeeder.php:90-107`). A ordem "páginas → seções → assets → configs" aparece de forma parcial: a homepage grava **configs e seções antes** dos assets SLE; o bloco SLE segue "gov defaults → assets → header → footer → configs" (`V2/Seeds/StructuralPortalSeeder.php:31-103`).

### 2.3 Mecânica passo a passo

1. `run()` abre `DB::beginTransaction()` (exceto homepage) e captura `now()` (`V2/Seeds/BasicPages/AssociadosSeeder.php:13-17`).
2. `processImage()` resolve o caminho em `base_path('REFERENCE/...')`; se não existir e a raiz for `REFERENCE`, tenta `public_path('assets/sucesu/img/...')` (`V2/Seeds/BasicPages/ProcessesGalleryImages.php:21-30`).
3. O arquivo vira `UploadedFile` em modo test e entra em `GalleryService::seedFromPath()` → `MediaInputer::processUpload()` → `v2_gallery_media` (`V2/Services/Gallery/Services/GalleryService.php:53-75`; `V2/Services/Gallery/Services/GalleryService.php:110-132`).
4. `pictureSnippet()` renderiza `v2-service-media::components.picture` **no servidor** e devolve o HTML final, que é concatenado ao conteúdo (`V2/Seeds/BasicPages/ProcessesGalleryImages.php:62-87`).
5. O HTML é gravado por `DB::table('v2_basic_pages')->update()` ou `insert()` conforme `slug` exista (`V2/Seeds/BasicPages/ConveniosSeeder.php:127-133`).
6. `HistoriaMilestonesSeeder` grava via model `Milestone`, um por `title`, com `description` em JSON Tiptap e `sort_order` 1..5 (`V2/Seeds/BasicPages/HistoriaMilestonesSeeder.php:155-172`).
7. A homepage grava 8 chaves em `v2_portal_configs`, 5 payloads em `v2_layout_base_configs`, 8 seções em `landing_sections`, atualiza a linha `homepage` e limpa `page:*` do Redis (`V2/Seeds/BasicPages/SucesuHomepageSeeder.php:33-410`).

### 2.4 Trechos de código pivô

```php
// V2/Seeds/BasicPages/ProcessesGalleryImages.php:19-35 — resolução da imagem de seed
protected function processImage($refRelativePath, $altText, $module = 'sle', $customFilename = null, $sourceRoot = 'REFERENCE')
{
    $refRelativePath = ltrim($refRelativePath, '/');
    $localPath = base_path($sourceRoot . '/' . $refRelativePath);

    // Compatibilidade legada: seeders que usam REFERENCE/ também caem no tema público.
    if ((!file_exists($localPath) || !is_readable($localPath)) && $sourceRoot === 'REFERENCE') {
        $publicPath = public_path('assets/sucesu/img/' . $refRelativePath);
        if (file_exists($publicPath) && is_readable($publicPath)) {
            $localPath = $publicPath;
        }
    }

    if (!file_exists($localPath) || !is_readable($localPath)) {
        $this->command && $this->command->warn("  ! Imagem não encontrada: {$localPath}");
        return null;
    }
```

Mostra por que o seed depende de `REFERENCE/` ou do tema público e por que imagem faltante não aborta a página.

```php
// V2/Seeds/BasicPages/AgendaSeeder.php:88-94 — upsert por slug, sem hash de conteúdo
if ($existing) {
    DB::table('v2_basic_pages')->where('slug', $slug)->update($data);
} else {
    $data['id'] = v2_uuid();
    $data['created_at'] = $now;
    DB::table('v2_basic_pages')->insert($data);
}
```

A chave é o `slug` único; o `id` só é gerado na criação. Toda edição manual de `content` ou `metadata` é perdida no próximo run.

```php
// V2/Seeds/BasicPages/AssociadosSeeder.php:127-133 — mesmo padrão em helper privado
if ($existing) {
    DB::table('v2_basic_pages')->where('slug', $slug)->update($data);
} else {
    $data['id'] = v2_uuid();
    $data['created_at'] = $now;
    DB::table('v2_basic_pages')->insert($data);
}
```

`AssociadosSeeder` e `ConveniosSeeder` compartilham o mesmo desenho de `savePage()` (`V2/Seeds/BasicPages/ConveniosSeeder.php:91-134`), mas a página de associados não é chamada pelo `DatabaseSeeder`.

```php
// V2/Seeds/BasicPages/FaleConoscoSeeder.php:50-56 — formulário sem endpoint próprio
$html .= '<form action="#" id="contact-form" method="POST" class="contact-form-items"><div class="row g-4">';
$html .= '<div class="col-lg-6"><div class="form-clt"><input type="text" name="name" placeholder="Seu Nome" class="form-control"></div></div>';
$html .= '<div class="col-lg-6"><div class="form-clt"><input type="text" name="email" placeholder="Seu E-mail" class="form-control"></div></div>';
$html .= '<div class="col-lg-6"><div class="form-clt"><input type="text" name="phone" placeholder="Telefone" class="form-control"></div></div>';
$html .= '<div class="col-lg-6"><div class="form-clt"><input type="text" name="subject" placeholder="Assunto" class="form-control"></div></div>';
$html .= '<div class="col-lg-12"><div class="form-clt"><textarea name="message" placeholder="Digite sua mensagem" class="form-control" rows="5"></textarea></div></div>';
$html .= '<div class="col-lg-12"><div class="form-clt mb-2"><div class="g-recaptcha" data-sitekey="' . (config('services.recaptcha.site_key', '6Lc_AAAAAAA1W_A')) . '"></div></div></div>';
```

`action="#"` faz o POST voltar para a própria URL e o formulário não inclui `@csrf`; o HTML semeado não define endpoint nem token próprios. O envio real é `POST /fale-conosco` do módulo (`V2/Modules/Institucional/FaleConosco/Routes/web.php:12-16`), e um POST sem `_token` seria barrado pelo middleware `web` (HTTP 419). A sitekey tem default literal `6Lc_AAAAAAA1W_A`.

```php
// V2/Seeds/BasicPages/HistoriaMilestonesSeeder.php:166-173 — idempotência por título
if ($existing) {
    $existing->update($payload);
} else {
    $payload['id'] = v2_uuid();
    $payload['created_at'] = $now;
    Milestone::create($payload);
}
$count++;
```

Não há índice único em `title` (`V2/Modules/Institucional/Historia/Migrations/2026_07_20_000001_create_v2_historia_milestones_table.php:18-26`): duas execuções concorrentes podem duplicar marcos, e a busca `first()` atualiza apenas o primeiro.

```php
// V2/Seeds/BasicPages/SucesuHomepageSeeder.php:44-63 — configs do portal por chave
foreach ($portalConfigs as $cfg) {
    $exists = DB::table('v2_portal_configs')
        ->where('config_key', $cfg['config_key'])
        ->whereNull('deleted_at')
        ->exists();
    if ($exists) {
        DB::table('v2_portal_configs')
            ->where('config_key', $cfg['config_key'])
            ->update(['config_value' => $cfg['config_value'], 'updated_at' => $now]);
    } else {
        DB::table('v2_portal_configs')->insert([
            'id'           => v2_uuid(),
            'config_key'   => $cfg['config_key'],
            'config_value' => $cfg['config_value'],
            'config_title' => $cfg['config_title'],
            'config_group' => $cfg['config_group'],
            'created_at'   => $now,
            'updated_at'   => $now,
        ]);
    }
```

No update só `config_value` e `updated_at` mudam: `config_title`/`config_group` editados no CMS permanecem. Config soft-deletada é ignorada no `exists()` e o `insert` seguinte colide com a unique de `config_key`, porque o índice não é parcial (`V2/Modules/Support/SupportConfigs/Migrations/2026_06_30_000005_create_v2_portal_configs_table.php:40-52`).

```php
// V2/Seeds/BasicPages/SucesuHomepageSeeder.php:388-394 — payload de seções por firstOrNew
$config = \V2\Modules\Support\StructuralLandingEditor\Models\LayoutBaseConfig::firstOrNew([
    'key' => 'landing_sections',
]);
$config->payload = $landingSections;
$config->save();

$this->command->info('v2_layout_base_configs key=landing_sections populado com 8 seções SUCESU no formato LSE.');
```

O payload inteiro é substituído por 8 seções com `id` novos a cada execução (`V2/Seeds/BasicPages/SucesuHomepageSeeder.php:242-385`). Qualquer seção editada ou reordenada no editor estrutural é descartada.

```php
// V2/Seeds/BasicPages/SucesuHomepageSeeder.php:412-421 — invalidação de cache best-effort
try {
    $redis = Redis::connection();
    $pattern = config('cache.prefix', 'laravel') . ':page:*';
    $keys = $redis->keys($pattern);
    if (!empty($keys)) {
        $redis->del($keys);
    }
} catch (\Exception $e) {
    // Cache clearing is best-effort
}
```

O seeder não abre transação de propósito: o comentário registra que uma transação fica `idle in transaction` durante o processamento das imagens e trava o CMS (`V2/Seeds/BasicPages/SucesuHomepageSeeder.php:25-29`). O `KEYS` é bloqueante e o padrão só casa com o prefixo de `config/cache.php` (`page:sha1` em `V2/Core/Middleware/PageCacheMiddleware.php:90`).

### 2.5 Custos e limites

- Cada `processImage()` faz leitura de disco, otimização (WebP 360/720/1200/1920 quando raster) e um `INSERT` em `v2_gallery_media`; a homepage sozinha executa 26 chamadas. O conjunto chamado pelo `DatabaseSeeder` chega a 63 uploads por execução (72 se `AssociadosSeeder` for chamado à parte).
- `pictureSnippet()` renderiza uma view por imagem; não há cache de HTML entre páginas, então uma imagem repetida (ícones 08/09/10 em Associados/Convênios) é reprocessada e re-renderizada a cada ocorrência.
- Não há paginação, fila ou throttle: tudo roda síncrono no comando.
- `DB::table` evita eventos de model, o que barateia a escrita, mas também impede reindexação de busca.

## 3. Particularidades

### 3.1 Idempotência comprovada

| Seeder | Mecanismo | Prova | Resultado |
|---|---|---|---|
| Agenda / FaleConosco / Notícias / Parceiros | `SELECT ... where('slug')->first()` + `update` ou `insert` | `V2/Seeds/BasicPages/AgendaSeeder.php:59,88-94`; `V2/Seeds/BasicPages/NoticiasSeeder.php:71,101-105` | Página não duplica; conteúdo é sobrescrito. |
| Associados / Convênios | `savePage()` com o mesmo padrão | `V2/Seeds/BasicPages/AssociadosSeeder.php:93,127-133`; `V2/Seeds/BasicPages/ConveniosSeeder.php:93,127-133` | Página não duplica; conteúdo é sobrescrito. |
| História | `Milestone::where('title')->first()` | `V2/Seeds/BasicPages/HistoriaMilestonesSeeder.php:155-172` | Não duplica em execução serial; sem unique no banco. |
| Homepage: configs | `exists()` por `config_key` + `update`/`insert` | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:44-60` | Valor não duplica; demais campos preservados. |
| Homepage: layout configs | `exists()` por `key` + `update`/`insert` | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:436-444` | Payload substituído. |
| Homepage: seções | `firstOrNew(['key' => 'landing_sections'])` | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:388-392` | Uma linha; payload regenerado. |
| Homepage: página | `update` sem `insert` | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:397-410` | Só atualiza se a linha existir; nunca cria. |
| Imagens / assets | Nenhum: `GalleryService::upload()` sempre insere | `V2/Services/Gallery/Services/GalleryService.php:65-75`; `V2/Modules/Layouts/BasicPage/Services/BasicPageAssetManagerService.php:102-122` | Novas mídias e novos assets a cada run. |

### 3.2 Escritas que sobrescrevem conteúdo editado

| Alvo | O que é perdido | Ponto |
|---|---|---|
| `v2_basic_pages` (agenda, convenios, fale-conosco, noticias, parceiros-institucionais, associados) | `title`, `content`, `content_type`, `is_runtime`, `metadata` inteiro (SEO + layout) | `V2/Seeds/BasicPages/ConveniosSeeder.php:94-125` |
| `v2_historia_milestones` (5 títulos fixos) | `period`, `description`, `image_id`, `sort_order`, `is_active` | `V2/Seeds/BasicPages/HistoriaMilestonesSeeder.php:156-167` |
| `v2_portal_configs` (8 chaves) | `config_value` editado no CMS é reescrito | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:49-52` |
| `v2_layout_base_configs` (`logos`, `links_header`, `links_footer`, `contato`, `social_media`, `landing_sections`) | Payload inteiro | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:72-139,388-392` |
| `v2_basic_pages` (`homepage`) | `content` vira `null`; `metadata` perde tudo exceto `seo`; `is_runtime=false` | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:397-410` |

Não há `truncate`/`delete`; a limpeza é apenas por sobrescrita. Registros que deixam de existir no array (por exemplo, um marco renomeado) permanecem órfãos.

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

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| `RN-DATA-SEEDS-BASICPAGES-01` | Toda página runtime usa `is_runtime=true` e layout com header/footer desligados. | A rota catch-all resolve o HTML e não injeta o chrome do portal. | `V2/Seeds/BasicPages/AgendaSeeder.php:62-87`; `V2/Modules/Layouts/BasicPage/Models/BasicPage.php:169-191` |
| `RN-DATA-SEEDS-BASICPAGES-02` | A homepage só é atualizada, nunca criada, e perde `content`/`layout`. | Em base sem a linha `homepage`, o seeder termina sem erro e sem homepage. | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:397-410` |
| `RN-DATA-SEEDS-BASICPAGES-03` | Configs do portal só têm `config_value` atualizado. | Título/grupo editados permanecem; valor volta ao default do seed. | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:49-52` |
| `RN-DATA-SEEDS-BASICPAGES-04` | O rodapé/cabeçalho da homepage vêm de payloads, não das páginas BasicPage. | Alterar `v2_basic_pages` não muda menu/contato; é preciso mudar `v2_layout_base_configs`. | `V2/Seeds/BasicPages/SucesuHomepageSeeder.php:86-139` |
| `RN-DATA-SEEDS-BASICPAGES-05` | Imagem repetida no mesmo run gera múltiplas mídias com o mesmo `file_path`. | A última gravação sobrescreve o arquivo; o banco acumula referências. | `V2/Services/Gallery/Services/GalleryService.php:65-75` |

### 3.4 Casos-limite e armadilhas

- **Linha `homepage` ausente:** a migração cria as 5 páginas fixas (`V2/Modules/Layouts/BasicPage/Migrations/2026_06_29_092300_create_basic_pages_table.php:36-55`), então o caso só ocorre se a linha for apagada. O `update` silencioso não falha.
- **Config soft-deletada:** `exists()` filtra `deleted_at IS NULL`; o `insert` seguinte viola a unique de `config_key`. O erro aborta a homepage, que não tem transação.
- **Seeder de associados órfão:** `AssociadosSeeder` existe e é funcional, mas não aparece em `callSeeds()` (`database/seeds/DatabaseSeeder.php:92-100`). Em uma instalação limpa, `/associados` é servido pelo módulo e a página nunca é criada.
- **Nome de classe divergente do arquivo:** o arquivo `ParceirosInstitucionaisSeeder.php` declara `class ParceirosSeeder` (`V2/Seeds/BasicPages/ParceirosInstitucionaisSeeder.php:9-10`) e é chamado por esse nome (`database/seeds/DatabaseSeeder.php:94`). Funciona apenas porque `V2/Seeds` está no `classmap` (`composer.json:27-32`). Um `require` direto pelo nome do arquivo não encontra a classe.
- **Slug sombreado:** `associados`, `agenda`, `noticias`, `fale-conosco` e `parceiros-institucionais` têm rotas de módulo registradas antes do catch-all; o HTML semeado não é o que o público vê. Serve como fallback e para o editor CMS.
- **Cache Redis:** se o driver não for Redis, a exceção é engolida; se o prefixo real do store diferir de `config('cache.prefix')`, nenhuma chave é apagada — a homepage pode continuar em cache.
- **Imagens de origem:** as páginas usam `REFERENCE/` com fallback para `public/assets/sucesu/img`; a homepage usa exclusivamente `assets_sucesu/` na raiz (`V2/Seeds/BasicPages/SucesuHomepageSeeder.php:19-21`). Em deploy sem `REFERENCE/`, ícones e fotos das páginas falham silenciosamente, mas a homepage continua.
- **SVG:** não gera variantes WebP; o warning é esperado e não indica falha.

### 3.5 Divergências e dívidas

- O comentário da homepage afirma que o seeder é "idempotente (upserts por chave)" (`V2/Seeds/BasicPages/SucesuHomepageSeeder.php:28-29`), mas as imagens e os assets não são: cada chamada de `processImage()` insere nova mídia.
- O `use Illuminate\Support\Facades\Storage;` aparece nos seis seeders que gravam HTML, mas `Storage` não é usado no arquivo (`V2/Seeds/BasicPages/NoticiasSeeder.php:6`; `V2/Seeds/BasicPages/ConveniosSeeder.php:6`).
- `NoticiasSeeder` monta cards com links `href="#"` e datas fora de ordem cronológica (`12 JUL`, `12 MAR`, `13 MAR`, `25 JUN`...) (`V2/Seeds/BasicPages/NoticiasSeeder.php:19-32,65-67`).
- `AssociadosSeeder` e `ConveniosSeeder` têm conteúdo quase idêntico (9 categorias, mesmos ícones, mesmas descrições) e ambos apontam todos os links para a própria página (`V2/Seeds/BasicPages/AssociadosSeeder.php:44-67`; `V2/Seeds/BasicPages/ConveniosSeeder.php:44-67`). O comentário diz que cada categoria "linka para os parceiros correspondentes", o que não acontece.
- `HistoriaMilestonesSeeder` grava `description` como string JSON, não como array (`V2/Seeds/BasicPages/HistoriaMilestonesSeeder.php:30-37`); o model `Milestone` só converte `is_active` para boolean (`V2/Modules/Institucional/Historia/Models/Milestone.php:48-51`), então o consumidor precisa decodificar o JSON por conta própria.

## 4. Integrabilidade

### 4.1 Superfície pública

| Símbolo | Tipo | Assinatura | Precondição | Consumidores |
|---|---|---|---|---|
| `run()` | método | `public function run()` | Container Laravel inicializado, tabelas migradas | `DatabaseSeeder`, `artisan db:seed` |
| `processImage()` | método protegido (trait) | `processImage($refRelativePath, $altText, $module='sle', $customFilename=null, $sourceRoot='REFERENCE')` | Arquivo legível ou fallback público | Todos os seeders da unidade; `SobreNosDefaultSectionsSeeder` |
| `pictureSnippet()` | método protegido | `pictureSnippet(array $galleryData, $alt='', $class='img-fluid rounded', $lazy=true, $style='')` | Array com `file_path`/`variants` | Seeders de página |
| `registerBasicPageAsset()` | método protegido | `registerBasicPageAsset($galleryData, $altText=null)` | Galeria com `id` válido | `SucesuHomepageSeeder` |
| `storageUrl()` | método protegido | `storageUrl(array $galleryData)` | `StorageService` disponível | Nenhum uso interno localizado |
| `SucesuHomepageSeeder::homeImg()` | método protegido | `homeImg(string $ref, string $alt, ?string $module='sle', ?string $customFilename=null)` | `assets_sucesu/` presente | Uso interno |

### 4.2 Receitas de uso

```bash
# 1. Semear tudo (inclui SLE e demo fora de produção)
php artisan db:seed

# 2. Semear somente as páginas básicas, na ordem do DatabaseSeeder
php artisan db:seed --class='V2\Seeds\BasicPages\SucesuHomepageSeeder'
php artisan db:seed --class='V2\Seeds\BasicPages\ParceirosSeeder'
php artisan db:seed --class='V2\Seeds\BasicPages\NoticiasSeeder'
php artisan db:seed --class='V2\Seeds\BasicPages\ConveniosSeeder'
php artisan db:seed --class='V2\Seeds\BasicPages\FaleConoscoSeeder'
php artisan db:seed --class='V2\Seeds\BasicPages\AgendaSeeder'
php artisan db:seed --class='V2\Seeds\BasicPages\HistoriaMilestonesSeeder'
```

```php
// 3. Recriar uma página específica sem tocar nas demais
Artisan::call('db:seed', ['--class' => \V2\Seeds\BasicPages\ConveniosSeeder::class]);
// Resultado: 1 linha em v2_basic_pages (slug=convenios) e 9 novas mídias em v2_gallery_media.
```

```php
// 4. Resolver a página runtime no portal
// GET /convenios -> BasicPagePortalController@runtime -> v2_basic_pages where slug=convenios and is_runtime=1
// V2/Modules/Layouts/BasicPage/Controllers/BasicPagePortalController.php:149-151
```

### 4.3 Anti-uso

- **Editar a página semeada pelo CMS e rodar o seeder de novo.** O `update` substitui `content` e `metadata`; a edição é perdida sem aviso (`V2/Seeds/BasicPages/AgendaSeeder.php:88-94`).
- **Rodar `SucesuHomepageSeeder` esperando que ele crie a página `homepage` em base limpa.** Ele só faz `update`; a criação depende da migração (`V2/Seeds/BasicPages/SucesuHomepageSeeder.php:397-410`).
- **Apagar uma config do portal e reexecutar.** O `exists()` filtra soft-deleted e o `insert` colide com a unique (`V2/Seeds/BasicPages/SucesuHomepageSeeder.php:45-60`).
- **Confiar em `Slug` bloqueado por outro módulo para entregar o HTML semeado.** O catch-all é o último recurso (`V2/Core/Routes/fallback.php:19-21`).

### 4.4 Consumidores e risco

- `MOD-LAYOUTS-BASICPAGE`: lê `v2_basic_pages` e o `metadata.layout`; qualquer mudança de formato dos nove flags altera o chrome das páginas.
- `MOD-LAYOUTS-HOMEPAGE`: consome `landing_sections` e as configs do portal; o seed sobrescreve ambos.
- `MOD-PUBLICACOES-AGENDA`, `MOD-PUBLICACOES-NOTICIAS`, `MOD-INSTITUCIONAL-HISTORIA`, `MOD-CONEXOES-ASSOCIADOS`, `MOD-CONEXOES-PARCEIROSINSTITUCIONAIS`: usam as imagens da Galeria semeadas aqui; se a Galeria for limpa, capas/logos de `DemoContentSeeder` ficam nulas.
- `SVC-SEARCH`: páginas semeadas por `DB::table` não passam por `syncToSearch()`; o índice é preenchido apenas no próximo boot de console e só para páginas ausentes (`V2/Core/Providers/V2ServiceProvider.php:146-165`).

## 5. Lacunas

- `LG-DATA-SEEDS-BASICPAGES-01`: não foi possível confirmar se o cache de página em produção usa o mesmo prefixo de `config/cache.php`; o padrão `laravel:page:*` pode não casar com o prefixo físico do Redis. Onde procurar: configuração de deploy (`config/cache.php`, `REDIS_PREFIX`) e `PageCacheMiddleware`.
- `LG-DATA-SEEDS-BASICPAGES-02`: não há teste cobrindo `AssociadosSeeder`, `ConveniosSeeder` ou `FaleConoscoSeeder`; o comportamento em reexecução é inferido do código. Onde procurar: `tests/Feature/V2/BasicPages/`.
- `LG-DATA-SEEDS-BASICPAGES-03`: o conteúdo de `JSON-LD` é gravado como array dentro de `metadata`; não foi localizado o ponto que o serializa para o `<script type="application/ld+json">`. Onde procurar: `SVC-SEO` e layouts do portal.
- `LG-DATA-SEEDS-BASICPAGES-04`: não foi possível medir o volume real de `v2_gallery_media` após N execuções sem uma base populada; a duplicação é inferida de `GalleryService::upload()`.

## 6. Evidências

| Arquivo | Faixas lidas |
|---|---|
| `V2/Seeds/BasicPages/AgendaSeeder.php` | 1-104 |
| `V2/Seeds/BasicPages/AssociadosSeeder.php` | 1-135 |
| `V2/Seeds/BasicPages/ConveniosSeeder.php` | 1-135 |
| `V2/Seeds/BasicPages/FaleConoscoSeeder.php` | 1-110 |
| `V2/Seeds/BasicPages/HistoriaMilestonesSeeder.php` | 1-184 |
| `V2/Seeds/BasicPages/NoticiasSeeder.php` | 1-137 |
| `V2/Seeds/BasicPages/ParceirosInstitucionaisSeeder.php` | 1-122 |
| `V2/Seeds/BasicPages/ProcessesGalleryImages.php` | 1-113 |
| `V2/Seeds/BasicPages/SucesuHomepageSeeder.php` | 1-445 |
| `database/seeds/DatabaseSeeder.php` | 1-108 |
| `V2/Modules/Layouts/BasicPage/Models/BasicPage.php` | 136-210 |
| `V2/Modules/Layouts/BasicPage/Migrations/2026_06_29_092300_create_basic_pages_table.php` | 14-60 |
| `V2/Modules/Institucional/Historia/Migrations/2026_07_20_000001_create_v2_historia_milestones_table.php` | 18-35 |
| `V2/Services/Gallery/Services/GalleryService.php` | 40-132 |
| `V2/Modules/Layouts/BasicPage/Services/BasicPageAssetManagerService.php` | 80-122 |
| `V2/Core/Providers/V2ServiceProvider.php` | 135-170 |
| `V2/Core/Routes/fallback.php` | 1-23 |
| `V2/Core/Middleware/PageCacheMiddleware.php` | 85-92 |
