# Notícias (V2) — Resumo Executivo

> **Versão analisada:** `V2/Modules/Publicacoes/Noticias` do repositório `lotep/` (Laravel 5.5.50, PHP 7.4).
> **Grupo/namespace:** `Publicacoes\Noticias` — view namespace `v2-noticias`, rotas CMS `cms/noticias`, portal `/noticias`.
> **Natureza:** módulo de conteúdo editorial com workflow de aprovação, taxonomia (categoria hierárquica + tags), galeria de mídias, destaques e integração com os services `SEO`, `Search`, `Gallery`/`Media` e `ContentRichEditor`.
> **Estado:** operacional; coberto por 3 testes de feature e 3 de unidade sob `tests/Feature/V2/Noticias/` e `tests/Unit/V2/Noticias/`.

---

## 1. Objetivo

Publicar notícias oficiais no portal municipal, com controle editorial: o autor redige um rascunho em editor rico (AST TipTap), um aprovador publica (ou rejeita com motivo) e o cidadão lê a matéria no portal. O módulo também alimenta a busca interna, o sitemap do Google News, os dados estruturados `NewsArticle` e os blocos de destaque da página de notícias.

Serve a três públicos:

| Público | O que obtém |
|---|---|
| Cidadão | Listagem paginada, matéria com capa, galeria, tags, relacionadas e navegação anterior/próxima. |
| Redação (CMS) | CRUD, categorias, tags, capa, galeria, SEO, preview por token e histórico de alterações. |
| Aprovador / gestão | Fila de pendentes, aprovação, rejeição motivada, configuração de aprovadores e super-destaque. |

## 2. Efeito / Impacto

| Faixa | Efeito |
|---|---|
| Cidadão | Conteúdo oficial em `/noticias` e `/noticia/{slug}`, com acessibilidade e imagens otimizadas. |
| Prefeitura (CMS) | Nenhuma notícia entra no ar sem passar pelo fluxo formal — **exceto** quando o formulário envia `status=published` diretamente (ver §5 e `08-GAPS-E-DIVIDAS.md`). |
| Transparência e legalidade | Registro de autoria, revisor, motivo de rejeição e trilha de auditoria (`V2AuditLogger`). |
| Operação técnica | Indexação automática em `v2_public_search_index` e `v2_cms_links_index`, sitemap cacheado e cache de página limpo em Redis. |

## 3. Comportamento

Estados da notícia (`News::$validStatuses`, `V2/Modules/Publicacoes/Noticias/Models/News.php:152-157`):

| Estado | Significado | Visível no portal |
|---|---|---|
| `draft` | Rascunho em edição; recebe `draft_token` UUID. | Só via `/noticia/{slug}/preview?token={token}` |
| `pending_approval` | Enviada para aprovação; aprovadores ativos são notificados por e-mail. | Só via preview com token |
| `published` | No ar; `published_at` preenchido e `draft_token` anulado. | Sim |
| `rejected` | Devolvida ao autor com `rejection_reason` e `reviewer_id`. | Só via preview com token |

Transições observáveis:

- `draft → pending_approval` — `submitForApproval()` (`V2/Modules/Publicacoes/Noticias/Controllers/NoticiasCmsController.php:552`), aceita também `rejected`.
- `pending_approval → published` — `approve()`, grava `published_at=now()`, limpa token e revisor.
- `pending_approval → rejected` — `reject()` exige `rejection_reason` (máx. 1000).
- Qualquer estado → `published` pela edição comum: `update()` aceita `status` do formulário (`:378-417`) e publica sem passar por aprovação.
- Publicada → arquivada (soft delete) — `destroy()`; reativada como `draft` — `restore()` gera novo token.
- `is_highlighted`: liga/desliga por AJAX. `is_super_highlighted`: **singleton global** — `setSuperHighlight()` zera todas e marca uma (`:724-731`).

Regras de exibição pública:

- Só `status = published`. A listagem ordena por `published_at desc` e pagina de 12 em 12; o CMS pagina de 15 em 15.
- Preview exige `draft_token` exato (`News::isAccessible()`, `V2/Modules/Publicacoes/Noticias/Models/News.php:417`); notícia publicada ignora o token.
- A matéria renderiza o AST (`content`) via `ContentRichEditorRendererService::toHtml()`; se falhar, cai para o subtítulo.

## 4. Como o usuário interage

**CMS (`/cms/noticias`)** — protegido por `v2.authenticate` + `v2.permission:noticias` e pelo `module.active:Noticias` injetado pelo provider:

- Listagem com filtros de status/categoria/tag, busca textual, ordenação, cartões agrupados por categoria e barra de contadores por status.
- Botões: Nova Notícia, Categorias, Super-destaque (modal com todas as publicadas), ações por item (editar, ver no portal, destacar, arquivar).
- Formulário em blocos: Dados Críticos (título, slug, categoria, tags), Metadados (responsável, fonte, SEO), Conteúdo (resumo, editor TipTap, capa, preview em iframe), Galeria (até 20 imagens) e histórico.
- Botões de envio disponíveis: **Salvar Rascunho** e **Publicar** (`V2/Modules/Publicacoes/Noticias/Views/cms/includes/_editor-content.blade.php:352-360`). O envio para aprovação existe no backend (`POST /cms/noticias/{id}/submeter`), mas **não há botão no formulário**.
- Página de pendentes reaproveita a mesma view da listagem; a aprovação/rejeição é feita por endpoints dedicados.
- Categorias: CRUD em modal, hierarquia de um nível (`parent_id`), categorias `is_system` não podem ser editadas nem removidas.
- Configurações: cadastro de aprovadores com vigência (`valid_from`/`valid_until`) e itens de sidebar por módulo.

**Portal** — `/noticias` (listagem + filtros client-side por texto/categoria, paginação server-side) e `/noticia/{slug}` (matéria, compartilhamento, galeria com lightbox, relacionadas, prev/next, breadcrumb e JSON-LD `NewsArticle`).

## 5. Detalhes e Particularidades Técnicas

- **Fluxo:** rota → middleware → controller → `NewsRequest` → model/Eloquent → view. Não há repositório nem Action: as regras vivem no controller e nos hooks do model.
- **Slug:** gerado no `saving` do model com desempate `-1`, `-2`, `-3` (`V2/Modules/Publicacoes/Noticias/Models/News.php:342-375`). O campo `slug` do formulário é validado, mas **o controller ignora o valor enviado** em `store()`/`update()`: o slug efetivo vem sempre do título e não muda depois da criação.
- **Busca interna:** cada `saved` do model chama `syncToSearch()` (`V2/Modules/Publicacoes/Noticias/Models/News.php:377-384`), que envia DTOs para `CMSIndexerService` e `PublicIndexerService`. Arquivamento por soft delete **não** dispara `saved`, portanto o índice público não é limpo ao arquivar.
- **Cache:** `clearPageCache()` apaga `laravel:page:*` via `Redis::keys()` quando o driver é Redis (`V2/Modules/Publicacoes/Noticias/Controllers/NoticiasCmsController.php:1015-1039`). `approve()`, `reject()` e `submitForApproval()` **não** chamam esse método.
- **Sitemap:** `GET /news-sitemap.xml` gera XML com notícias dos últimos 6 meses, limite 1000, cache `v2_news_sitemap_xml` por 360 minutos e purge entre 03:00 e 03:10 (`V2/Modules/Publicacoes/Noticias/Controllers/NoticiasSitemapController.php:24-63`). Usa `DB::table('v2_news')` — **não** respeita `deleted_at`.
- **Data-driven activation:** o provider dos módulos envolve `V2/Modules/Publicacoes/Noticias/Routes/cms.php` com `module.active:Noticias`; `V2/Modules/Publicacoes/Noticias/Routes/web.php` recebe `web` + `MaintenanceMiddleware`, declarando `web` de novo (duplicação inócua).
- **Dependências de terceiros:** Sentinel (auth), `parsedown` (conversor de Markdown auxiliar), Bootstrap 5/Icons no CMS e portal, Redis para cache de página.

## 6. Estrutura de Dados (resumo)

| Tabela | Papel |
|---|---|
| `v2_news` | Notícia: título, slug único, subtítulo, `content` (AST JSON), `summary`, status, `draft_token`, autor/revisor, `published_at`, motivo de rejeição, `metadata` jsonb (SEO), capa, destaques, `category_id`, `search_vector` + GIN e colunas de importação legada. |
| `v2_news_categories` | Categoria hierárquica (`parent_id`), cor, ordem, `is_system`, soft delete. |
| `v2_news_tags` | Tag única por slug, soft delete. |
| `v2_news_tag_pivot` | N:N notícia↔tag (PK composta). |
| `v2_news_gallery_media` | N:N notícia↔`v2_gallery_media`, com `sort_order`. |
| `v2_news_approvers` | Usuários aprovadores (`user_id` inteiro de `users`) com vigência. |
| `v2_news_configs` | Chave/valor jsonb (hoje apenas `sidebar_items`). |

Detalhes completos em `02-MODELAGEM.md`.

## 7. Como Testar (resumo)

```bash
# a partir da raiz de lotep/
vendor/bin/phpunit --filter 'Tests\\(Feature|Unit)\\V2\\Noticias'
```

- Feature: `tests/Feature/V2/Noticias/NoticiasCmsControllerTest.php` (CRUD, galeria, aprovação/rejeição, SEO, categorias), `tests/Feature/V2/Noticias/NoticiasPortalControllerTest.php` (publicação, preview, capa, galeria), `tests/Feature/V2/Noticias/NoticiasNavegacaoPaginacaoTest.php` (paginação e prev/next).
- Unidade: `tests/Unit/V2/Noticias/NewsModelTest.php` (UUID, slug, status, SEO, soft delete), `tests/Unit/V2/Noticias/NewsSearchSenderTest.php` (DTOs por status), `tests/Unit/V2/Noticias/NewsUrlTest.php` (URLs nunca vazias).

## 8. Migração para Laravel Moderno

O dossiê `06-MIGRACAO-MODERNA.md` entrega o schema equivalente (Laravel 12/PHP 8.4), enum de status, Form Requests, Policy com as quatro ações `noticias.{ver|editar|aprovar|arquivar}`, endpoints REST/JSON, resources, views e fila. Pontos inegociáveis de equivalência:

- Mesmos estados e transições; aprovação só a partir de `pending_approval`.
- `draft_token` opaco, preview acessível somente com token.
- Super-destaque único; `is_highlighted` independente.
- URL pública `/noticia/{slug}` e listagem `/noticias` (301 se mudar).
- JSON-LD `NewsArticle` e sitemap de notícias.
- Conteúdo em AST jsonb, nunca HTML (ADR-028); imagens AVIF/WebP (ADR-026).

O plano de aproveitamento seletivo está em `09-APROVEITAMENTO-V7.md`.

## 9. Resumo Geral

O módulo Notícias V2 é o caso de uso editorial mais completo da plataforma: CRUD com workflow de aprovação, taxonomia hierárquica, tags, capa e galeria, SEO em jsonb, preview por token, destaques e super-destaque, sitemap Google News e indexação automática em dois índices de busca. A implementação é enxuta (controller + model), sem repositórios, e depende de hooks de model para slug, datas, token e indexação. O que o leitor precisa retém: **quatro estados**, **duas superfícies** (CMS e portal), **sete tabelas**, **duas integrações de indexação**, **dois artefatos de SEO** (JSON-LD e sitemap). As fragilidades estão concentradas em três pontos: o `update()` permite publicar saltando a aprovação; aprovar/rejeitar não invalidam o cache de página; e o sitemap/busca não conhecem soft delete. Esses riscos são detalhados e quantificados em `05-REGRAS-E-PORTABILIDADE.md` e `08-GAPS-E-DIVIDAS.md`; o plano de portabilidade para o V7, com o que portar, adaptar e rejeitar, está em `09-APROVEITAMENTO-V7.md`.

---

## Índice do dossiê

| Arquivo | Conteúdo |
|---|---|
| `00-RESUMO.md` | Este documento |
| `01-ARQUITETURA.md` | Camadas, classes, fluxos CMS/portal e acoplamentos |
| `02-MODELAGEM.md` | Tabelas, colunas, relações, triggers, migrations e seeds |
| `03-ROTAS-E-CONTROLADORES.md` | Rotas, controllers, request, permissões e respostas |
| `04-VIEWS-E-UX.md` | Blades, layouts, JS, portal e CMS |
| `05-REGRAS-E-PORTABILIDADE.md` | Regras não óbvias, armadilhas e acoplamentos |
| `06-MIGRACAO-MODERNA.md` | Plano de reconstrução em Laravel 12/PHP 8.4 |
| `07-TESTES.md` | Estratégia, casos, factories e comandos |
| `08-GAPS-E-DIVIDAS.md` | Bugs, riscos e perguntas em aberto |
| `09-APROVEITAMENTO-V7.md` | O que portar, adaptar e rejeitar, com ADRs |
