# Notícias (V2) — Aproveitamento para o V7

> **Slug:** `publicacoes-noticias` · **Origem:** `V2/Modules/Publicacoes/Noticias` · **Destino:** V7 (Laravel 12/PHP 8.4).
>
> Este arquivo decide, item a item, o que **portar** (padrão concreto), o que **adaptar** e o que **rejeitar**. As ADRs citadas estão em `wiki/adr/` do repositório V7.

---

## 1. Critério

1. O V7 é greenfield e não mantém retrocompatibilidade de código ou dado (ADR-042).
2. Só se porta o que representa **comportamento** ou **padrão de engenharia** verificável; não se porta estrutura acidental.
3. Toda decisão precisa de uma ADR ou regra explícita do V7.
4. O que não tem justificativa em ADR fica de fora até haver necessidade demonstrável (ADR-001, simplicidade).

## 2. Portar

| # | Padrão do legado | Como fica no V7 | Justificativa |
|---|---|---|---|
| P1 | Quatro estados `draft/pending_approval/published/rejected` | Enum backed `StatusNoticia` com os mesmos valores; transições em Actions | Preserva dados e comportamento; ADR-001 (concreto) |
| P2 | Preview por `draft_token` | `URL::temporarySignedRoute('noticias.preview', ttl, ['noticia' => $id])`; token derivado da assinatura, sem coluna | Mesmo efeito (link compartilhável), com expiração; ADR-020/022 |
| P3 | Categoria hierárquica (`parent_id`) + tags N:N | Tabelas `noticias_categorias` (auto-FK) e `noticias_tags` + pivot, **com FKs reais** | ADR-017 (integridade) |
| P4 | Galeria ordenada por `sort_order` | Pivot `noticias_galeria` com `ordem` e FK para `gallery_media` | ADR-026 |
| P5 | Super-destaque único | `DefinirSuperDestaque` em transação + índice único parcial `WHERE super_destacada` | Garante a regra no banco, não só no PHP; ADR-017 |
| P6 | Prev/next determinístico por `(data, id)` | Coluna gerada `publicado_seq` (`COALESCE(publicado_em, created_at)`) + índice composto `(publicado_seq, id)`; consulta keyset | Mesma ordenação, sem full scan; ADR-008 (otimização) |
| P7 | JSON-LD `NewsArticle` com headline, datas, autor, publisher, imagem, `articleSection`, `keywords`, `hasPart` | Schema do módulo dentro do `@graph` emitido por `<x-service-seo::tags>` | ADR-027 |
| P8 | Sitemap de notícias com janela de 6 meses | Sitemap por tipo do service SEO, com `lastmod`, limite 20k, `ETag`/`Cache-Control` | ADR-027/022 |
| P9 | Sincronização com índice de busca a cada `save()` | Evento de domínio + listener + job na fila; indexa CMS e portal | ADR-013/015; evita efeito colateral em `saved` |
| P10 | Auditoria de criação, edição, status, destaque e categoria | Subscriber global do Core (`before/after`), sem chamadas manuais | ADR-035 |
| P11 | SEO em `metadata['seo']` (jsonb) | `metadados['seo']` jsonb + componente `<x-service-seo::input>` | ADR-027 |
| P12 | Capa/galeria renderizadas em `<picture>` AVIF/WebP | Componente `<x-service-media::picture>` com `width`/`height` e LCP prioritário | ADR-026 |
| P13 | Contadores por status e cartões agrupados por categoria no CMS | Manter como padrão de UX, calculados em uma única consulta agregada | ADR-009 (CMS próprio) |
| P14 | Notificação de aprovadores por e-mail, excluindo o autor da ação | Job `NotificarAprovadores` com a mesma regra de exclusão | ADR-035/024 (observabilidade e fila) |
| P15 | `getPublicUrl()`/`getPreviewUrl()` nunca vazias | Método equivalente no model/DTO, com fallback explícito | Evita href vazio; ADR-001 (previsibilidade) |
| P16 | Validação de `content` como AST válido | Rule/Validator dedicado ao schema TipTap (`type=doc`) | ADR-028 |
| P17 | `status` com labels/cores para UI | `StatusNoticia::label()`/`color()` no enum | Mantém a UI previsível; ADR-001 |

## 3. Adaptar

| # | No legado | Problema | Como adaptar | Justificativa |
|---|---|---|---|---|
| A1 | Aprovação opcional (publicar direto no update) | Bypass do workflow | Remover `status` do request de edição; `PublicarDireto` só com `noticias.aprovar` | ADR-033 |
| A2 | Slug validado, mas ignorado | UI enganosa | Slug editável com `unique` e redirect 301 via `seo.redirects` | ADR-027 |
| A3 | `Redis::keys('*:page:*')` | O(N), bloqueante | Cache por tags/versão e invalidação por evento | ADR-022 |
| A4 | Busca `ilike '%termo%'` no CMS | Full scan e curingas do usuário | `tsvector` gerado + GIN, ou `pg_trgm` com índice; sanitizar `%`/`_` | ADR-008/017 |
| A5 | Filtro do portal no cliente | Não encontra itens de outras páginas | Busca/filtro server-side com paginação preservando query | ADR-010 (mínimo JS) |
| A6 | Iframe de preview com `postMessage` e `sandbox allow-scripts allow-same-origin` | Combinação enfraquece o sandbox | Componente de preview no CMS com endpoint autenticado e CSP restritiva | ADR-022/020 |
| A7 | `sidebar_items` em `v2_news_configs` | Configuração morta, fora do layout | Mover para o serviço de layout/tema (slots/presets) | ADR-016 |
| A8 | Paginação 12 (portal) / 15 (CMS) fixa | Regra espalhada | Configuração por módulo, com valores padrão iguais | ADR-001 |
| A9 | Sitemap com purge por janela de horário | Frágil (depende de request) | Invalidação por evento de publicação + TTL | ADR-022 |
| A10 | `author_id`/`reviewer_id` string sem FK | Integridade na aplicação | `foreignUlid` para `cms_users` com FK e `nullOnDelete` | ADR-017/033 |
| A11 | `cover_image_id` string ↔ mídia inteira | Tipo divergente | `foreignId('capa_id')` para `gallery_media` | ADR-026 |
| A12 | `metadata.seo.json_ld` texto livre editável | JSON-LD arbitrário, sem governança | Schema gerado pelo service SEO; `json_ld` deixa de ser campo do usuário | ADR-027 |
| A13 | E-mail síncrono no request | Latência e falha parcial | Job na fila com retry e log estruturado | ADR-024 |
| A14 | `flash()` e `redirect()` no controller | Acoplamento à sessão | Action retorna resultado; controller HTTP adapta | ADR-013 |
| A15 | Token de preview permanente | Vaza indefinidamente | Assinatura com TTL e revogação ao publicar | ADR-020 |
| A16 | `search_vector` por trigger | Duplicado se o service Search já indexa | Coluna gerada (PG 18) ou remoção, conforme o service escolhido | ADR-017 |
| A17 | Categorias `is_system` protegidas só no controller | Regra dispersa | Policy/Action valida; flag permanece como dado | ADR-033 |
| A18 | Rota `/noticia/{slug}` | URL consolidada | Manter; se mudar para `/noticias/{slug}`, 301 obrigatório | ADR-027/042 |

## 4. Rejeitar

| # | Item do legado | Por que rejeitar | Regra/ADR |
|---|---|---|---|
| R1 | CSS/JS inline nas views (`@push('styles')`, `@push('scripts')`, `onclick`) | CSP sem inline e `ETag` estável | ADR-022 |
| R2 | `Redis::keys` + `del` em massa | Operação O(N), não cluster-safe | ADR-022/008 |
| R3 | Traits, classes abstratas, genéricos | Proibido no V7 | ADR-001 |
| R4 | `ilike '%termo%'` como busca principal | Não escala e ignora o índice textual existente | ADR-008/017 |
| R5 | UUID v4 gerado por helper chamado `v2_uuid()` | Nome enganoso; ULID é ordenável | ADR-017 |
| R6 | FK lógica para autor/revisor | Integridade só na aplicação | ADR-017 |
| R7 | Colunas de importação V1 (`v1_id`, `imported_at`, `legacy_author`) | Migração é manual; sem ETL | ADR-042 |
| R8 | `metadata.seo.json_ld` editável como string | JSON-LD sem schema e risco de XSS/erro | ADR-027 |
| R9 | jQuery nos componentes de mídia | V7 é JS moderno sem jQuery | ADR-010 |
| R10 | `pending()` duplicando `index()` | Divergência de regras | ADR-001 |
| R11 | `allPublished` sem limite | Custo crescente no render | ADR-008 |
| R12 | Endpoints JSON que serializam o model inteiro | Vazamento de campos e contrato instável | ADR-015 |
| R13 | `NewsRequest::authorize() => true` | Autorização implícita no middleware | ADR-033 |
| R14 | `News::saved` como barramento de indexação | Efeito colateral invisível, sem retry | ADR-013/035 |
| R15 | `Log::info` manual por ação | Log não estruturado, sem correlação | ADR-024 |
| R16 | `down()` de migration que dropa colunas adicionadas | Perda de dados em rollback | ADR-017 |
| R17 | Seed de notícias como bootstrap | Seeds são só demo | ADR-041 |
| R18 | `MarkdownToTiptapConverter` no módulo | Duplica o parser do service RichText | ADR-015/028 |
| R19 | Categoria com cor (`v2_news_categories.color`) sem consumidor | Dado decorativo sem uso | ADR-017 |
| R20 | `NewsConfig` chave/valor no módulo | Configuração deve ser dado do layout/tema ou `config/` | ADR-016 |

## 5. Mapa de artefatos

| Legado | V7 |
|---|---|
| `V2/Modules/Publicacoes/Noticias/Models/News.php` | `Noticia` + `StatusNoticia` + Actions de transição |
| `V2/Modules/Publicacoes/Noticias/Models/NewsCategory.php` | `Categoria` com auto-FK |
| `V2/Modules/Publicacoes/Noticias/Models/NewsTag.php` | `Tag` |
| `V2/Modules/Publicacoes/Noticias/Models/NewsApprover.php` | `Aprovador` (ou papel `noticias.aprovar`, se a lista de e-mail for derivada de permissões) |
| `V2/Modules/Publicacoes/Noticias/Models/NewsConfig.php` | removido; defaults em `config/` |
| `V2/Modules/Publicacoes/Noticias/Controllers/NoticiasCmsController.php` | Controllers finos + Actions + Requests + Policy |
| `V2/Modules/Publicacoes/Noticias/Controllers/NoticiasPortalController.php` | Controller público + Query Objects |
| `V2/Modules/Publicacoes/Noticias/Controllers/NoticiasSitemapController.php` | Sitemap do service SEO |
| `V2/Modules/Publicacoes/Noticias/Controllers/NoticiasConfigController.php` | Tela de aprovadores integrada ao Core (perfis/permissões) |
| `V2/Modules/Publicacoes/Noticias/Services/NewsSearchSender.php` | Listener + job de indexação |
| `V2/Modules/Publicacoes/Noticias/Services/NewsNotificationService.php` | Job de notificação |
| `V2/Modules/Publicacoes/Noticias/Services/MarkdownToTiptapConverter.php` | não portar (service RichText) |
| Views do módulo | componentes Blade/Tailwind + Vite |
| Migrations do módulo | migration única (ou duas) com FKs e tipos corretos |

## 6. Ordem de implementação sugerida

1. Migration + enum + models com FKs.
2. Policy e declaração das quatro permissões no `ModuleProvider`.
3. Actions de CRUD e workflow, com testes de transição.
4. Preview assinado e listagem pública.
5. SEO (`@graph`) e sitemap.
6. Mídia (capa/galeria) com `<picture>`.
7. Indexação e notificação em fila.
8. Views CMS (Tailwind/daisyUI) e portal.
9. Auditoria (subscriber do Core) e observabilidade.
10. Testes de acessibilidade e e2e dos fluxos críticos.

## 7. Critérios de aceite da paridade

- [ ] Os quatro estados e as transições de `05-REGRAS-E-PORTABILIDADE.md` §1 são respeitados; publicar exige `noticias.aprovar`.
- [ ] Preview só abre com URL assinada válida; publicar revoga.
- [ ] Super-destaque é único no banco (índice parcial).
- [ ] `/noticias` pagina com ordenação determinística; prev/next usa a mesma chave.
- [ ] `/noticia/{slug}` mantém URL e emite `NewsArticle` no `@graph`.
- [ ] `/news-sitemap.xml` exclui arquivadas e respeita a janela de 6 meses.
- [ ] Arquivar remove do índice público e invalida cache.
- [ ] Nenhum HTML/CSS/JS inline; nenhum CDN.
- [ ] Conteúdo só em AST jsonb; imagens só via Media.
- [ ] Toda mutação auditada automaticamente.
- [ ] Cobertura ≥90% e PHPStan 0 erros (ADR-038/040).

## 8. O que **não** se aproveita de forma alguma

- A permissão genérica `write` como passe livre para aprovar.
- O cache de página por varredura de chaves.
- O sitemap que ignora soft delete.
- O token de preview permanente.
- A indexação disparada por `saved` sem fila.
- O controller de 1041 linhas como unidade de organização.
