---
id: DATA-SEEDS-CONTENT
tipo: dados
onda: 5
status: concluido
unidades: [DATA-SEEDS-CONTENT]
fontes:
  - V2/Seeds/Content/
depende_de: [DATA-SEEDS-BASICPAGES, MOD-CONEXOES-UNIDADEREGIONAL, MOD-PUBLICACOES-NOTICIAS, MOD-PUBLICACOES-AGENDA, MOD-CONEXOES-ASSOCIADOS, MOD-CONEXOES-PARCEIROSINSTITUCIONAIS, MOD-PUBLICACOES-TRABALHECONOSCO, SVC-GALLERY]
consumidores: [MOD-CONEXOES-UNIDADEREGIONAL, MOD-PUBLICACOES-NOTICIAS, MOD-PUBLICACOES-AGENDA, MOD-CONEXOES-ASSOCIADOS, MOD-CONEXOES-PARCEIROSINSTITUCIONAIS, MOD-PUBLICACOES-TRABALHECONOSCO]
criticidade: baixa
dono: agente-dados-iniciais
---

# Conteúdo de demonstração

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Popula o portal com 7 unidades regionais, 26 notícias, 8 eventos, 18 associados, 8 parceiros, 6 vagas e 5 candidaturas fictícias, com datas retroativas em relação ao dia da execução. |
| Entrada | `php artisan db:seed` (não-produção) ou `php artisan db:seed --class=V2\Seeds\Content\DemoContentSeeder`. |
| Saída | Linhas em `v2_unidade_regionais`, `v2_unidade_regional_empresas`, `v2_news_categories`, `v2_news_tags`, `v2_news`, `v2_news_tag_pivot`, `v2_eventos`, `v2_associado_instituicoes`, `v2_associado_tags`, `v2_associado_tag_pivot`, `v2_parceiros_institucionais`, `v2_job_openings` e `v2_job_applications`. |
| Efeitos colaterais | Hooks de model disparam índice público de busca (`v2_public_search_index`), feed RSS (`updateOrCreate` em `v2_feed_rss_items`) e auditoria (`ActivityLogger`); categorias regionais são criadas pelo hook de `UnidadeRegional`; nenhuma imagem é enviada. |
| Particularidade central | É o único seeder do corpus com **duas** barreiras de produção e com guardas de volume por tabela (`count() > 0`) em vez de upsert: se a tabela já tem qualquer linha, a seção inteira é pulada. |
| Como se integra | Chamado por `DatabaseSeeder::callSeeds()` apenas quando `config('app.env') !== 'production'`; resolve `cover_image_id`/`logo_id` por `v2_gallery_media.description`. |
| Se removido, quebra | O portal de demonstração fica vazio; não há impacto em produção, onde o seeder já não roda. |
| Superfície pública | Classe `V2\Seeds\Content\DemoContentSeeder`; métodos protegidos `seedUnidadesRegionais`, `seedNewsTaxonomy`, `seedNoticias`, `seedEventos`, `seedAssociados`, `seedParceiros`, `seedVagas`, `seedRegionalNews`, `seedRegionalAssociados`, `report`. |

## 1. Efeito

### 1.1 Volume e entidades

| Entidade / tabela | Registros | Campos relevantes | Fonte |
|---|---|---|---|
| Unidades regionais (`v2_unidade_regionais`) | 6 novas (DF, BA, PE, PA, RS, PR) + `NAC` pré-existente | `acronym`, `name`, `slug` automático, `state`, `city`, `address`, `phone`, `email`, `site_url`, `map_coordinates`, `sort_order`, `is_active`, `metadata.resumo` | `V2/Seeds/Content/DemoContentSeeder.php:76-158` |
| Vínculo regional-empresa (`v2_unidade_regional_empresas`) | 18 | `unidade_regional_id`, `empresa_id`, `status='active'` | `V2/Seeds/Content/DemoContentSeeder.php:905-917,1574-1580` |
| Categorias (`v2_news_categories`) | 5 garantidas + `regionais` + uma `regional-<sigla>` por unidade (até 7) | `slug`, `parent_id`, `is_system`, `sort_order` | `V2/Seeds/Content/DemoContentSeeder.php:168-190`; `V2/Core/Services/RegionalIdentity/RegionalIdentityService.php:14-60` |
| Tags (`v2_news_tags`) | 14 declaradas + `Inovação` criada sob demanda = 15 | `slug`, `name` | `V2/Seeds/Content/DemoContentSeeder.php:192-207` |
| Notícias (`v2_news`) | 14 no bloco principal + 12 regionais = 26 | `status` (24 `published`, 1 `draft`, 1 `pending_approval`), `category_id`, `unidade_regional_id`, `cover_image_id`, `content` (Tiptap), `content_date`, `published_at`, `metadata.seo` | `V2/Seeds/Content/DemoContentSeeder.php:212-601,1387-1440` |
| Pivô notícia-tag (`v2_news_tag_pivot`) | 1 linha por tag de cada notícia | `news_id`, `tag_id` | `V2/Seeds/Content/DemoContentSeeder.php:581-590,1422-1431` |
| Eventos (`v2_eventos`) | 8 (7 ativos, 1 inativo) | `event_date_start/end` relativos a `now()`, `location`, `organizer`, `target_audience`, `topics_list` (JSON), `schedule` (JSON), `is_active`, `published_at`, `image_id` | `V2/Seeds/Content/DemoContentSeeder.php:607-751` |
| Associados (`v2_associado_instituicoes`) | 10 no bloco principal + 8 regionais = 18 | `name`, `url`, `discount_badge`, `description`, `sort_order`, `is_active`, `metadata.benefits`, `metadata.social_media` | `V2/Seeds/Content/DemoContentSeeder.php:753-921,1448-1590` |
| Tags de associado (`v2_associado_tags` / `v2_associado_tag_pivot`) | 9 tags distintas; 1 pivô por associado | `is_primary=true` para a primeira tag | `V2/Seeds/Content/DemoContentSeeder.php:900`; `V2/Modules/Conexoes/Associados/Models/AssociadoInstituicao.php:231-252` |
| Parceiros (`v2_parceiros_institucionais`) | 8 | `name`, `summary`, `description` (Tiptap), `url`, `sort_order`, `is_active`, `logo_id`, `metadata.social_media`, `metadata.seo` | `V2/Seeds/Content/DemoContentSeeder.php:923-1034` |
| Vagas (`v2_job_openings`) | 6 (5 ativas, 1 encerrada) | `title`, `type`, `workload`, `location`, `salary_range`, `description_detailed` (Tiptap), `is_active`, `published_at`, `closes_at` | `V2/Seeds/Content/DemoContentSeeder.php:1037-1176` |
| Candidaturas (`v2_job_applications`) | 5 | `job_opening_id`, `name`, `email`, `phone`, `linkedin_url`, `cover_letter`, `resume_path`, `status`, `notes`, `reviewed_at` | `V2/Seeds/Content/DemoContentSeeder.php:1178-1209` |

As datas não são absolutas: notícias usam `Carbon::now()->subDays(...)`, eventos usam `addDays(start_offset)`, vagas usam `subDays(10 + i*5)` e candidaturas usam `subDays(5 + i*3)` (`V2/Seeds/Content/DemoContentSeeder.php:558-562,720-721,1163-1165,1202-1204`). Duas execuções em dias diferentes produzem janelas diferentes se as tabelas forem limpas antes.

### 1.2 Contrato observável

| Entrada | Resultado |
|---|---|
| `APP_ENV=production` + `db:seed` | `DatabaseSeeder` nem chama o seeder (`database/seeds/DatabaseSeeder.php:104-107`); se chamado por `--class`, o seeder emite `DemoContentSeeder ignorado: APP_ENV=production.` e retorna (`V2/Seeds/Content/DemoContentSeeder.php:43-48`). |
| Base vazia, não-produção | Executa as 9 seções na ordem fixa e imprime o relatório de contagens ao final (`V2/Seeds/Content/DemoContentSeeder.php:52-62,1607-1638`). |
| `v2_news` com ≥1 linha | Pula as 14 notícias principais, mas **ainda executa** `seedRegionalNews()` e cria/atualiza categorias regionais (`V2/Seeds/Content/DemoContentSeeder.php:212-217,59`). |
| `v2_unidade_regionais` com ≥1 linha além de `NAC` | Não cria regionais; apenas carrega o mapa `acronym => id` das existentes (`V2/Seeds/Content/DemoContentSeeder.php:69-76`). |
| Imagem de capa/logo ausente na Galeria | `galleryId()` retorna `null`; a notícia/parceiro é gravada sem imagem, sem erro (`V2/Seeds/Content/DemoContentSeeder.php:1598-1606`). |
| Falha no meio (exceção não tratada) | Não há `DB::transaction` nem `try/catch` global; as seções anteriores permanecem gravadas (`V2/Seeds/Content/DemoContentSeeder.php:41-62`). |

### 1.3 O que NÃO acontece

- **Não** envia imagens para a Galeria: `galleryId()` só consulta `v2_gallery_media.description` (`V2/Seeds/Content/DemoContentSeeder.php:1598-1606`). As capas dependem de imagens já semeadas por `SucesuHomepageSeeder`, `NoticiasSeeder` e `ParceirosSeeder`.
- **Não** cria usuários, permissões, páginas ou configurações.
- **Não** limpa dados existentes: não há `truncate`, `delete` ou `updateOrCreate` de conteúdo; o padrão é `count() === 0` ou `firstOrCreate`/`exists`.
- **Não** é transacional: qualquer falha deixa dados parciais (ver `seedNoticias`, que grava item a item).
- **Não** usa um arquivo de configuração próprio: a única chave é `config('app.env')`.

## 2. Abordagem

### 2.1 Estratégia

O seeder é uma sequência de blocos independentes, cada um com sua própria guarda de idempotência. A taxonomia e as regionais usam `firstOrCreate`; o conteúdo usa "tabela vazia ou pula". No fim, duas passadas de "garantia regional" criam o que falta por slug/nome.

```mermaid
flowchart TD
  P{APP_ENV == production?} -- sim --> X[ignora e retorna]
  P -- não --> R[seedUnidadesRegionais]
  R --> T[seedNewsTaxonomy]
  T --> N[seedNoticias]
  N --> E[seedEventos]
  E --> A[seedAssociados]
  A --> PC[seedParceiros]
  PC --> V[seedVagas]
  V --> RN[seedRegionalNews]
  RN --> RA[seedRegionalAssociados]
  RA --> RP[report de contagens]
```

### 2.2 Ordem e dependências

| # | Método | Depende de | Por quê |
|---|---|---|---|
| 1 | `seedUnidadesRegionais` | `NacionalUnidadeRegionalSeeder` | As notícias e vínculos precisam do mapa `acronym => id`; o hook `saved` de `UnidadeRegional` cria `regional-<sigla>` (`V2/Modules/Conexoes/UnidadeRegional/Models/UnidadeRegional.php:137-146`). |
| 2 | `seedNewsTaxonomy` | 1 | `regional-pa` usada em `seedNoticias` só existe porque o hook da regional PA a criou. |
| 3 | `seedNoticias` | 2 | `$cat('regional-pa')` faz `->first()->id` sem checar `null` (`V2/Seeds/Content/DemoContentSeeder.php:219-221,543`). |
| 4 | `seedEventos` | Galeria (opcional) | `image_id` é resolvido por descrição. |
| 5 | `seedAssociados` | 1, taxonomia de associados | Cria tags e vínculo regional. |
| 6 | `seedParceiros` | Galeria (opcional) | `logo_id` por descrição. |
| 7 | `seedVagas` | — | Cria também as candidaturas, no mesmo bloco. |
| 8 | `seedRegionalNews` | 1, 2, `SVC-SEARCH`/feed | Cria categorias regionais e notícias por slug ausente. |
| 9 | `seedRegionalAssociados` | 1 | Cria associados por nome ausente e vínculo regional. |

A ordem é fixa no `run()` (`V2/Seeds/Content/DemoContentSeeder.php:52-60`). `seedVagas` depende de `JobOpening` já ter linhas para distribuir candidaturas (`V2/Seeds/Content/DemoContentSeeder.php:1179-1180`).

### 2.3 Mecânica passo a passo

1. Verifica `app.env` e retorna em produção (`V2/Seeds/Content/DemoContentSeeder.php:41-48`).
2. Instancia `MarkdownToTiptapConverter`, usado por `tt()` para transformar Markdown em JSON Tiptap; em erro devolve `{"type":"doc","content":[]}` (`V2/Seeds/Content/DemoContentSeeder.php:50,1592-1600`).
3. Cria as 6 regionais via `UnidadeRegional::firstOrCreate(['acronym' => ...])` e guarda os IDs (`V2/Seeds/Content/DemoContentSeeder.php:131-158`).
4. Garante categorias `nacional` + 4 filhas e a categoria pai `regionais` (`V2/Seeds/Content/DemoContentSeeder.php:168-190`).
5. Garante 14 tags por slug (`V2/Seeds/Content/DemoContentSeeder.php:192-207`).
6. Para cada notícia: instancia `News`, resolve categoria/regional/capa, grava, sincroniza tags e regrava com `created_at`/`updated_at` retroativos (`V2/Seeds/Content/DemoContentSeeder.php:534-598`).
7. Para cada evento: calcula início/fim como `now()->addDays(offset)->startOfDay()->addMinutes(HH*60+MM)`, grava, regrava timestamps (`V2/Seeds/Content/DemoContentSeeder.php:719-742`).
8. Associa: grava instituição, `syncTagsFromArray([$tag])` e vínculo regional (`V2/Seeds/Content/DemoContentSeeder.php:873-917`).
9. Parceiros: grava com `description` convertida e `metadata.social_media` sintético (`V2/Seeds/Content/DemoContentSeeder.php:1009-1030`).
10. Vagas/candidaturas: 6 vagas, a última inativa, e 5 candidaturas distribuídas por `i % count($openingIds)` (`V2/Seeds/Content/DemoContentSeeder.php:1148-1209`).
11. `seedRegionalNews` percorre 6 siglas × 2 títulos, pula slug existente e cria a notícia com a categoria de `RegionalIdentityService::newsCategoryFor()` (`V2/Seeds/Content/DemoContentSeeder.php:1387-1439`).
12. `seedRegionalAssociados` percorre 6 siglas e 8 nomes, pula nome existente, cria tag e vínculo (`V2/Seeds/Content/DemoContentSeeder.php:1514-1582`).
13. `report()` imprime contagens reais das 9 tabelas e a distribuição de tamanho dos resumos (`V2/Seeds/Content/DemoContentSeeder.php:1640-1687`).

### 2.4 Trechos de código pivô

```php
// V2/Seeds/Content/DemoContentSeeder.php:43-48 — barreira de produção dentro do seeder
// Proteção em camadas: mesmo invocado diretamente (--class=DemoContentSeeder),
// NUNCA popula dados de referência/análise em produção.
if (config('app.env') === 'production') {
    $this->command->warn('DemoContentSeeder ignorado: APP_ENV=production.');
    return;
}
```

Além da barreira do `DatabaseSeeder`, o próprio seeder recusa rodar em produção, o que cobre a execução manual por `--class`.

```php
// V2/Seeds/Content/DemoContentSeeder.php:212-220 — guarda de volume por tabela
protected function seedNoticias()
{
    if (News::count() > 0) {
        $this->info('  ! Notícias já existentes — pulando.');
        return;
    }

    $cat = function ($slug) {
        return NewsCategory::where('slug', $slug)->first()->id;
```

Qualquer linha existente desliga a seção inteira; não há tentativa de completar notícias faltantes. O `->first()->id` sem null-check é o ponto que exige `regional-pa` já criada.

```php
// V2/Seeds/Content/DemoContentSeeder.php:133-149 — regional idempotente por acronym
$ur = UnidadeRegional::firstOrCreate(
    ['acronym' => $r['acronym']],
    [
        'id' => v2_uuid(),
        'name' => $r['name'],
        'slug' => str_slug($r['name']),
        'description' => $r['description'],
        'state' => $r['state'],
        'city' => $r['city'],
        'address' => $r['address'],
        'phone' => $r['phone'],
        'email' => $r['email'],
        'site_url' => $r['site_url'],
        'map_coordinates' => $r['map_coordinates'],
        'sort_order' => $order++,
        'is_active' => true,
        'metadata' => ['resumo' => $r['resumo']],
```

`firstOrCreate` não atualiza os campos de uma regional existente; dados editados no CMS permanecem. O `saved` do model cria a categoria `regional-<sigla>` e indexa a regional na busca.

```php
// V2/Seeds/Content/DemoContentSeeder.php:170-187 — taxonomia garantida
$national = NewsCategory::firstOrCreate(
    ['slug' => 'nacional'],
    ['id' => v2_uuid(), 'name' => 'Nacional', 'is_system' => true, 'sort_order' => 1]
);
app(\V2\Core\Services\RegionalIdentity\RegionalIdentityService::class)->newsCategoryParent();

$childrenNational = [
    ['name' => 'Tecnologia e Mercado', 'sort' => 1],
    ['name' => 'Política e Regulação', 'sort' => 2],
    ['name' => 'Inovação e Startups', 'sort' => 3],
    ['name' => 'Educação e Carreiras', 'sort' => 4],
];
foreach ($childrenNational as $c) {
    NewsCategory::firstOrCreate(
        ['slug' => str_slug($c['name'])],
        ['id' => v2_uuid(), 'name' => $c['name'], 'parent_id' => $national->id, 'sort_order' => $c['sort']]
    );
}
```

As 5 categorias nacionais são idempotentes por slug; a categoria pai `regionais` é criada por `firstOrCreate` sem `id` explícito, deixando o UUID para o model.

```php
// V2/Seeds/Content/DemoContentSeeder.php:536-552 — criação de notícia e vínculos
$item = new News();
$item->id = v2_uuid();
$item->title = $n['title'];
$item->subtitle = $n['subtitle'] ?? null;
$item->content = $this->tt($n['content']);
$item->summary = $n['summary'] ?? null;
$item->status = $n['status'];
$item->category_id = $cat($n['category']);
$item->source = $n['source'] ?? null;
$item->responsible_name = 'Equipe SUCESU Nacional';
$item->is_highlighted = $n['highlight'] ?? false;
$item->is_super_highlighted = $n['super_highlight'] ?? false;

$ur = $n['regional'] === 'NAC'
    ? UnidadeRegional::where('acronym', 'NAC')->first()
    : UnidadeRegional::where('acronym', $n['regional'])->first();
$item->unidade_regional_id = $ur ? $ur->id : null;
```

A regional é resolvida por sigla; se `NAC` não existir, a notícia fica sem unidade. O `slug` não é informado: o hook `saving` do model gera e desambigua (`V2/Modules/Publicacoes/Noticias/Models/News.php:267-282`).

```php
// V2/Seeds/Content/DemoContentSeeder.php:579-595 — tags e timestamps retroativos
$item->save();

// associa tags
$tagIds = [];
foreach ($n['tags'] as $tagName) {
    $tag = NewsTag::firstOrCreate(
        ['slug' => str_slug($tagName)],
        ['id' => v2_uuid(), 'name' => $tagName]
    );
    $tagIds[] = $tag->id;
}
$item->tags()->sync($tagIds);

// backdate timestamps para listagem realista
$item->created_at = Carbon::now()->subDays($n['content_days_ago'])->subHours(2);
$item->updated_at = Carbon::now()->subDays($n['content_days_ago']);
$item->save();
```

Cada notícia é salva **duas vezes**: o primeiro `save()` dispara índice, feed e auditoria; o segundo altera timestamps e repete os mesmos hooks. `sync()` substitui as tags anteriores, não acumula.

```php
// V2/Seeds/Content/DemoContentSeeder.php:720-735 — eventos relativos ao dia da execução
$startDate = Carbon::now()->addDays($r['start_offset'])->startOfDay()->addMinutes($this->hm($r['start_time']));
$endDate = Carbon::now()->addDays($r['end_offset'])->startOfDay()->addMinutes($this->hm($r['end_time']));

$evento = new Evento();
$evento->id = v2_uuid();
$evento->title = $r['title'];
$evento->description = $r['description'];
$evento->event_date_start = $startDate;
$evento->event_date_end = $endDate;
$evento->location = $r['location'];
$evento->organizer = $r['organizer'];
$evento->target_audience = $r['target_audience'];
$evento->topics_list = json_encode($r['topics']);
$evento->schedule = $r['schedule'];
$evento->is_active = $r['active'];
$evento->published_at = Carbon::now()->subDays($r['published_days_ago']);
```

Os offsets vão de `-50` a `+108` dias; `schedule` é gravado como array e o cast do model decide a serialização. O evento de índice 7 nasce com `active=false` e datas no futuro.

```php
// V2/Seeds/Content/DemoContentSeeder.php:1391-1400 — garantia regional idempotente por slug
$category = app(\V2\Core\Services\RegionalIdentity\RegionalIdentityService::class)->newsCategoryFor($ur);

foreach ($newsList as $tpl) {
    if (News::where('slug', str_slug($tpl['title']))->exists()) continue;

    $item = new News();
    $item->id = v2_uuid();
    $item->title = $tpl['title'];
    $item->subtitle = $tpl['subtitle'] ?? null;
    $item->content = $this->tt($tpl['content']);
```

A checagem usa `str_slug(title)`, enquanto o model pode gerar sufixo `-1` em caso de colisão; nesse cenário o `exists()` não encontra e o run seguinte duplica.

### 2.5 Custos e limites

- 26 notícias × 2 `save()` + 18 associados + 8 eventos + 8 parceiros + 6 vagas + 5 candidaturas: cada `save()` dispara hooks de busca/feed/auditoria, então o custo real é maior que o número de linhas.
- `seedRegionalNews` faz uma consulta `exists()` por título (12 por execução) e `newsCategoryFor()` por regional; `seedRegionalAssociados` faz uma consulta por nome (8).
- `report()` conta 9 tabelas e carrega todos os `summary` para calcular palavras; é O(n) no volume de notícias, aceitável para demonstração.
- Não há transação, fila, throttle ou limite de tempo; a falha é parcial por desenho.

## 3. Particularidades

### 3.1 Idempotência comprovada

| Seção | Guarda | Prova | Comportamento em reexecução |
|---|---|---|---|
| Regionais | `UnidadeRegional::where('acronym','!=','NAC')->count()`; depois `firstOrCreate` | `V2/Seeds/Content/DemoContentSeeder.php:69-76,133` | Não duplica; não atualiza campos existentes. |
| Categorias | `firstOrCreate` por slug | `V2/Seeds/Content/DemoContentSeeder.php:170-186` | Não duplica; nome/parent de categoria existente não é corrigido. |
| Tags | `firstOrCreate` por slug | `V2/Seeds/Content/DemoContentSeeder.php:200-204` | Não duplica; cria `Inovação` sob demanda. |
| Notícias principais | `News::count() > 0` | `V2/Seeds/Content/DemoContentSeeder.php:214-217` | Pula tudo se houver qualquer notícia. |
| Eventos | `Evento::count() > 0` | `V2/Seeds/Content/DemoContentSeeder.php:609-612` | Pula tudo. |
| Associados | `AssociadoInstituicao::count() > 0` | `V2/Seeds/Content/DemoContentSeeder.php:755-758` | Pula tudo. |
| Parceiros | `Parceiro::count() > 0` | `V2/Seeds/Content/DemoContentSeeder.php:925-928` | Pula tudo. |
| Vagas + candidaturas | `JobOpening::count() > 0` | `V2/Seeds/Content/DemoContentSeeder.php:1039-1042` | Pula ambos; candidaturas nunca são completadas se as vagas já existirem. |
| Notícias regionais | `News::where('slug', str_slug(title))->exists()` | `V2/Seeds/Content/DemoContentSeeder.php:1394` | Idempotente, salvo colisão de slug. |
| Associados regionais | `AssociadoInstituicao::where('name', ...)->exists()` | `V2/Seeds/Content/DemoContentSeeder.php:1519` | Idempotente por nome; não repara vínculo regional ausente. |

### 3.2 Escritas que sobrescrevem conteúdo

| Alvo | O que acontece |
|---|---|
| `v2_unidade_regionais` | `firstOrCreate` nunca sobrescreve; edições são preservadas. |
| `v2_news_categories` | `newsCategoryFor()` **atualiza** `name` e `parent_id` de `regional-<sigla>` se divergirem (`V2/Core/Services/RegionalIdentity/RegionalIdentityService.php:42-58`). |
| `v2_news_tag_pivot` | `sync()` remove tags que não estão no array semeado (`V2/Seeds/Content/DemoContentSeeder.php:590`). |
| Demais tabelas de conteúdo | Só escrevem quando a tabela está vazia; não há sobrescrita. |
| `v2_feed_rss_items` | `updateOrCreate` por `entity_type`+`entity_id`; cada `save()` reescreve o item. |

Não há seed destrutivo por `delete`/`truncate`; o risco é de **dados parciais permanentes** por causa das guardas de volume.

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

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| `RN-DATA-SEEDS-CONTENT-01` | Produção é bloqueada duas vezes (DatabaseSeeder e o próprio seeder). | Nem `db:seed` nem `--class` populam produção. | `database/seeds/DatabaseSeeder.php:104-107`; `V2/Seeds/Content/DemoContentSeeder.php:43-48` |
| `RN-DATA-SEEDS-CONTENT-02` | Cada seção de conteúdo só roda com a tabela vazia. | Base parcial nunca é completada; falha no meio exige limpeza manual. | `V2/Seeds/Content/DemoContentSeeder.php:214-217,609-612,755-758,925-928,1039-1042` |
| `RN-DATA-SEEDS-CONTENT-03` | A categoria da notícia é obrigatória e resolvida sem null-check. | Se `regional-pa` não existir, o seeder aborta com "Call to a member function id() on null". | `V2/Seeds/Content/DemoContentSeeder.php:219-221,543` |
| `RN-DATA-SEEDS-CONTENT-04` | Candidaturas só existem junto com as vagas. | Se as vagas já existem e as candidaturas não, não há como semeá-las por este seeder. | `V2/Seeds/Content/DemoContentSeeder.php:1039-1042,1178-1183` |
| `RN-DATA-SEEDS-CONTENT-05` | Imagens são referências, não uploads. | Sem Galeria populada, capas/logos ficam `null` silenciosamente. | `V2/Seeds/Content/DemoContentSeeder.php:1598-1606` |
| `RN-DATA-SEEDS-CONTENT-06` | Datas são relativas ao momento da execução. | O conteúdo envelhece; "publicado há 2 dias" depende de quando o seed rodou. | `V2/Seeds/Content/DemoContentSeeder.php:558-562` |

### 3.4 Casos-limite e armadilhas

- **`regional-pa` ausente:** a categoria é criada pelo hook `saved` de `UnidadeRegional` (`V2/Modules/Conexoes/UnidadeRegional/Models/UnidadeRegional.php:137-146`). Se as regionais já existiam porque foram inseridas por SQL direto (sem Eloquent), o hook não rodou e o seeder falha na notícia de índice 13. A falha ocorre **depois** de 12 notícias gravadas, sem rollback.
- **`NAC` ausente:** notícias nacionais ficam com `unidade_regional_id = null`; o filtro regional do portal pode ocultá-las.
- **Galeria limpa após os seeders de página:** todas as capas/logos ficam `null`; não há placeholder nem erro.
- **Duplicidade de descrição na Galeria:** `galleryId()` pega `first()` por `description`; como cada execução dos seeders de página reinsere a mesma descrição, a capa pode apontar para uma mídia antiga, não para o arquivo mais recente.
- **Notícias com o mesmo título:** o hook de slug adiciona `-1`, mas `seedRegionalNews` compara o slug base; uma segunda execução pode criar duplicata.
- **Vaga "encerrada":** a última vaga tem `is_active=false` e `closes_at` três dias atrás, mas `published_at` válido (`V2/Seeds/Content/DemoContentSeeder.php:1156-1158`).
- **Candidaturas com currículo inexistente:** `resume_path` aponta para `curriculos/<slug>-curriculo.pdf`, arquivo que o seeder não cria (`V2/Seeds/Content/DemoContentSeeder.php:1189`). Qualquer download resulta em 404.
- **Redes sociais fictícias:** os URLs são montados a partir do nome, não de dados reais; o link pode não existir.
- **`report()` pode falhar silenciosamente:** cada contagem está em `try/catch` e imprime "tabela indisponível" (`V2/Seeds/Content/DemoContentSeeder.php:1657-1662`).

### 3.5 Divergências e dívidas

- O docblock afirma "Idempotente: cada seção só roda se a tabela correspondente estiver vazia (ou via firstOrCreate para taxonomia)" (`V2/Seeds/Content/DemoContentSeeder.php:30-31`). Isso esconde que a base parcial não é reparada.
- `seedVagas` é a única seção que cria duas entidades com guarda em apenas uma delas.
- `seedAssociados` grava `benefits_list = null` e usa o accessor `metadata.benefits` (`V2/Seeds/Content/DemoContentSeeder.php:879,884`; `V2/Modules/Conexoes/Associados/Models/AssociadoInstituicao.php:131-139`), deixando a coluna legada sempre nula.
- O array `$slugs` é preenchido em `seedNoticias` e nunca usado (`V2/Seeds/Content/DemoContentSeeder.php:534,597`).
- `cover_image_id` e `logo_id` são inteiros de `v2_gallery_media`, mas o seeder não valida o `owner_module='sle'` da mídia encontrada.

## 4. Integrabilidade

### 4.1 Superfície pública

| Símbolo | Tipo | Assinatura | Precondição | Consumidores |
|---|---|---|---|---|
| `run()` | público | `run(): void` | `app.env !== production`, tabelas migradas | `DatabaseSeeder`, artisan |
| `seedUnidadesRegionais()` | protegido | sem parâmetros | `v2_unidade_regionais` | Uso interno |
| `seedNewsTaxonomy()` | protegido | sem parâmetros | categorias/tags migradas | Uso interno |
| `seedNoticias()` | protegido | sem parâmetros | taxonomia com `regional-<sigla>` | Uso interno |
| `seedEventos()` | protegido | sem parâmetros | `v2_eventos` | Uso interno |
| `seedAssociados()` | protegido | sem parâmetros | tags de associado | Uso interno |
| `seedParceiros()` | protegido | sem parâmetros | `v2_parceiros_institucionais` | Uso interno |
| `seedVagas()` | protegido | sem parâmetros | `v2_job_openings` | Uso interno |
| `seedRegionalNews()` | protegido | sem parâmetros | regionais + taxonomia | Uso interno |
| `seedRegionalAssociados()` | protegido | sem parâmetros | regionais | Uso interno |
| `report()` | protegido | sem parâmetros | tabelas existentes | Uso interno |

### 4.2 Como desligar

| Forma | Efeito | Fonte |
|---|---|---|
| `APP_ENV=production` | `DatabaseSeeder` não chama e o seeder se autoignora | `database/seeds/DatabaseSeeder.php:104-107`; `V2/Seeds/Content/DemoContentSeeder.php:43-48` |
| Remover/comentar a chamada em `callSeeds()` | Nenhuma execução, inclusive em desenvolvimento | `database/seeds/DatabaseSeeder.php:104-107` |
| Rodar seeders específicos com `--class` | O demo só roda se for chamado explicitamente | `database/seeds/DatabaseSeeder.php:92-100` |
| Variável de ambiente dedicada | **Não existe**; `APP_ENV` é a única chave | `V2/Seeds/Content/DemoContentSeeder.php:45` |

### 4.3 Receitas de uso

```bash
# 1. Popular ambiente de desenvolvimento (fora de produção)
php artisan db:seed

# 2. Popular somente o demo, em base já migrada e com a Galeria semeada
php artisan db:seed --class='V2\Seeds\Content\DemoContentSeeder'

# 3. Conferir o resultado (o próprio seeder imprime)
# === Relatório de conteúdo (DemoContentSeeder) ===
# Unidades Regionais ... 7
# Notícias ... 26
```

```php
// 4. Rodar apenas a garantia regional depois de importar associados manualmente
$seeder = new \V2\Seeds\Content\DemoContentSeeder();
$seeder->setContainer(app())->setCommand(app('command'));
// seedRegionalAssociados() é protegido: na prática, use db:seed --class.
```

### 4.4 Anti-uso

- **Rodar em produção esperando "só completar dados".** O seeder é bloqueado, e burlar o `APP_ENV` publica conteúdo fictício no portal (`V2/Seeds/Content/DemoContentSeeder.php:43-48`).
- **Assumir que uma segunda execução completa o que faltou.** As guardas por `count()` pulam a seção inteira (`V2/Seeds/Content/DemoContentSeeder.php:214-217`).
- **Rodar o demo antes dos seeders de página.** As capas/logos ficam `null`, pois a Galeria ainda não tem as descrições esperadas (`V2/Seeds/Content/DemoContentSeeder.php:1598-1606`).
- **Editar o conteúdo e rodar novamente esperando atualização.** As guardas impedem a atualização; o conteúdo editado permanece, mas novos registros regionais podem ser adicionados (`V2/Seeds/Content/DemoContentSeeder.php:1387-1394`).

### 4.5 Consumidores e risco

- `MOD-CONEXOES-UNIDADEREGIONAL`: depende das siglas e do vínculo `v2_unidade_regional_empresas`; remover o seeder esvazia as páginas regionais.
- `MOD-PUBLICACOES-NOTICIAS`: depende de categorias/tags e do status; as 26 notícias são o volume de teste do CMS e do portal.
- `MOD-PUBLICACOES-AGENDA`: 8 eventos, 7 ativos, com datas relativas.
- `MOD-CONEXOES-ASSOCIADOS` / `MOD-CONEXOES-PARCEIROSINSTITUCIONAIS`: 18 e 8 registros; capas/logos vêm da Galeria.
- `MOD-PUBLICACOES-TRABALHECONOSCO`: 6 vagas e 5 candidaturas, incluindo uma vaga encerrada.
- `SVC-FEEDRSS` / `SVC-SEARCH` / `SVC-ACTIVITYLOGGER`: recebem escritas indiretas pelos hooks de model durante o seed.

## 5. Lacunas

- `LG-DATA-SEEDS-CONTENT-01`: não foi localizado um teste de integração que execute `DemoContentSeeder` em base limpa e valide as 26 notícias; a contagem é derivada dos arrays. Onde procurar: `tests/Feature/V2/`.
- `LG-DATA-SEEDS-CONTENT-02`: não foi possível confirmar o efeito visual do `metadata.layout.super_highlight` da notícia "Reforma Tributária"; o seeder grava a flag, mas o consumidor está fora desta unidade. Onde procurar: `MOD-PUBLICACOES-NOTICIAS`.
- `LG-DATA-SEEDS-CONTENT-03`: o `resume_path` aponta para PDFs que não são criados; não se sabe se o CMS trata o 404. Onde procurar: `MOD-PUBLICACOES-TRABALHECONOSCO`.
- `LG-DATA-SEEDS-CONTENT-04`: a contagem de categorias depende do estado prévio de `v2_news_categories` e das unidades criadas por outros seeders; o valor "até 13" é derivado, não medido.

## 6. Evidências

| Arquivo | Faixas lidas |
|---|---|
| `V2/Seeds/Content/DemoContentSeeder.php` | 1-1688 |
| `database/seeds/DatabaseSeeder.php` | 1-108 |
| `database/seeds/NacionalUnidadeRegionalSeeder.php` | 1-24 |
| `V2/Core/Services/RegionalIdentity/RegionalIdentityService.php` | 1-115 |
| `V2/Modules/Conexoes/UnidadeRegional/Models/UnidadeRegional.php` | 95-150 |
| `V2/Modules/Publicacoes/Noticias/Models/News.php` | 176-194,288-310 |
| `V2/Modules/Conexoes/Associados/Models/AssociadoInstituicao.php` | 125-175,228-252 |
| `V2/Services/FeedRSS/Consumer/FeedSender.php` | 40-88 |
| `V2/Services/FeedRSS/Services/FeedRssService.php` | 39-70 |
