# Módulo Notícias — Gerenciamento de Notícias do Portal

> **Namespace raiz:** `V2\Modules\Publicacoes\Noticias`
> **Blade namespace CMS:** `v2-noticias::`
> **Blade namespace Portal:** `v2-noticias::portal`
> **Tabelas:** `v2_news`, `v2_news_categories`, `v2_news_tags`, `v2_news_tag_pivot`, `v2_news_approvers`, `v2_news_configs`

---

## Sumário

1. [Visão Geral](#visão-geral)
2. [Estrutura de Diretórios](#estrutura-de-diretórios)
3. [Models](#models)
   - [News](#news)
   - [NewsCategory](#newscategory)
   - [NewsTag](#newstag)
   - [NewsApprover](#newsapprover)
   - [NewsConfig](#newsconfig)
4. [Controllers](#controllers)
   - [NoticiasCmsController](#noticiascmscontroller)
   - [NoticiasPortalController](#noticiasportalcontroller)
   - [NoticiasConfigController](#noticiasconfigcontroller)
   - [NoticiasSitemapController](#noticiassitemapcontroller)
5. [Views CMS](#views-cms)
6. [Views Portal](#views-portal)
7. [Rotas](#rotas)
   - [CMS](#cms)
   - [Portal](#portal)
8. [Funcionalidades Implementadas](#funcionalidades-implementadas)
   - [Fluxo de Aprovação](#fluxo-de-aprovação)
   - [Agendamento de Publicação](#agendamento-de-publicação)
   - [Destaque e Super-destaque](#destaque-e-super-destaque)
   - [Tags com seleção visual](#tags-com-seleção-visual)
   - [Categorias com subcategorias](#categorias-com-subcategorias)
   - [Segmentação da Página Principal](#segmentação-da-página-principal)
   - [Notícias Relacionadas por Tag](#notícias-relacionadas-por-tag)
9. [Migrações](#migrações)
10. [Auditoria e Activity Log](#auditoria-e-activity-log)

---

## Visão Geral

O módulo **Notícias** gerencia o portal de notícias institucionais. Suporta fluxo de aprovação (rascunho → pendente → publicado/rejeitado), agendamento futuro, categorias hierárquicas, tags, destaque e super-destaque.

### Fluxo de trabalho

```
CMS (CRUD + Aprovação) → v2_news → Portal (/noticias)
                                    ├── Super-destaque (topo)
                                    ├── Destaques (carrossel)
                                    ├── Nacional (featured + grid)
                                    └── Regionais (grid)
```

---

## Estrutura de Diretórios

```
V2/Modules/Publicacoes/Noticias/
├── Controllers/
│   ├── NoticiasCmsController.php        # CRUD + toggle + super-destaque + categorias + tags
│   ├── NoticiasConfigController.php     # Aprovadores + sidebar
│   ├── NoticiasPortalController.php     # Página pública (/noticias), show, preview
│   └── NoticiasSitemapController.php    # Google News Sitemap
├── Migrations/
│   ├── 2026_07_08_000001_create_v2_news_table.php
│   ├── 2026_07_08_000002_create_v2_news_approvers_and_configs_table.php
│   ├── 2026_07_22_000001_create_v2_news_categories_table.php
│   ├── 2026_07_22_000002_create_v2_news_tags_table.php
│   ├── 2026_07_22_000003_add_new_fields_to_v2_news_table.php
│   ├── 2026_07_22_000004_fix_pivot_uuid_types.php
│   └── 2026_07_22_000005_fix_tags_and_categories_uuid.php
├── Models/
│   ├── News.php
│   ├── NewsApprover.php
│   ├── NewsCategory.php
│   ├── NewsConfig.php
│   └── NewsTag.php
├── Requests/
│   └── NewsRequest.php
├── Routes/
│   ├── cms.php                          # Rotas administrativas
│   └── web.php                          # Rotas públicas
├── Services/
│   ├── MarkdownToTiptapConverter.php
│   ├── NewsNotificationService.php
│   └── NewsSearchSender.php
├── Views/
│   ├── cms/
│   │   ├── archived.blade.php           # Notícias arquivadas
│   │   ├── categories.blade.php         # Gestão de categorias
│   │   ├── config/
│   │   │   └── index.blade.php          # Configurações (aprovadores)
│   │   ├── create.blade.php             # Criação
│   │   ├── edit.blade.php               # Edição
│   │   ├── includes/
│   │   │   ├── _confirm-modal.blade.php
│   │   │   ├── _editor-content.blade.php # Editor + metadados
│   │   │   ├── form.blade.php           # Título + slug + SEO
│   │   │   ├── scripts.blade.php
│   │   │   └── styles.blade.php
│   │   ├── index.blade.php              # Listagem + toggle destaque + modal super
│   │   └── preview.blade.php            # Preview no CMS
│   ├── emails/
│   │   └── notification-content.blade.php
│   └── portal/
│       ├── index.blade.php              # Página principal segmentada + modo filtrado
│       └── show.blade.php               # Detalhe + sidebar + relacionadas
└── ModuleProvider.php
```

---

## Models

### News

**Arquivo:** `Models/News.php`, **Tabela:** `v2_news`

| Coluna | Tipo | Descrição |
|--------|------|-----------|
| `id` | uuid | PK, UUID v4 |
| `title` | string(255) | Título |
| `slug` | string(255) | URL amigável (unique) |
| `subtitle` | string(500) | Linha fina |
| `content_date` | date/null | Data do conteúdo/evento |
| `content` | text/null | JSON AST do ContentRichEditor |
| `summary` | text/null | Resumo em texto puro |
| `status` | string(20) | `draft`, `pending_approval`, `published`, `rejected` |
| `draft_token` | uuid/null | Token UUID para preview |
| `author_id` | string(36)/null | FK users (inteiro) |
| `reviewer_id` | string(36)/null | FK users (inteiro) |
| `published_at` | timestamp/null | Data de publicação (agendável) |
| `rejection_reason` | text/null | Motivo da rejeição |
| `metadata` | jsonb/null | SEO + dados flexíveis |
| `cover_image_id` | string(36)/null | FK v2_gallery_media.id |
| `is_highlighted` | boolean | Destacar na listagem |
| `is_super_highlighted` | boolean | Super-destaque (único) |
| `source` | text/null | Fonte da informação |
| `responsible_name` | string(255)/null | Nome do responsável |
| `category_id` | uuid/null | FK v2_news_categories.id |
| `search_vector` | tsvector | Índice de busca textual |

#### Scopes

| Scope | Query |
|-------|-------|
| `published()` | `WHERE status = 'published'` |
| `draft()` | `WHERE status = 'draft'` |
| `pendingApproval()` | `WHERE status = 'pending_approval'` |
| `recent()` | `ORDER BY created_at DESC` |
| `highlighted()` | `WHERE is_highlighted = true` |
| `superHighlighted()` | `WHERE is_super_highlighted = true` |
| `byCategory($id)` | `WHERE category_id = $id` |

#### Relacionamentos

| Método | Tipo | FK |
|--------|------|----|
| `author()` | `belongsTo(User)` | `author_id` |
| `reviewer()` | `belongsTo(User)` | `reviewer_id` |
| `coverImage()` | `belongsTo(GalleryMedia)` | `cover_image_id` |
| `category()` | `belongsTo(NewsCategory)` | `category_id` |
| `tags()` | `belongsToMany(NewsTag)` | pivot `v2_news_tag_pivot` |

#### Eventos

| Evento | Ação |
|--------|------|
| `creating` | Gera UUID, slug inicial, draft_token |
| `saving` | Slug dedup, published_at se publicado, remove draft_token ao publicar |
| `saved` | Sincroniza SearchService + Feed RSS |
| `deleted` | Remove do índice público + feed RSS |

#### Status

| Constante | Label | Cor |
|-----------|-------|-----|
| `STATUS_DRAFT` | Rascunho | secondary |
| `STATUS_PENDING_APPROVAL` | Pendente de Aprovação | warning |
| `STATUS_PUBLISHED` | Publicado | success |
| `STATUS_REJECTED` | Rejeitado | danger |

### NewsCategory

**Arquivo:** `Models/NewsCategory.php`, **Tabela:** `v2_news_categories`

| Coluna | Tipo | Descrição |
|--------|------|-----------|
| `id` | uuid | PK |
| `name` | string(100) | Nome |
| `slug` | string(100) | URL amigável (unique) |
| `parent_id` | uuid/null | FK para subcategoria |
| `is_system` | boolean | Protegida contra exclusão |
| `sort_order` | integer | Ordenação |

**Categorias de sistema (is_system = true):** `Nacional`, `Regionais` — seed na migration, não podem ser removidas.

**Relacionamentos:** `parent()`, `children()`, `news()`

### NewsTag

**Arquivo:** `Models/NewsTag.php`, **Tabela:** `v2_news_tags`

| Coluna | Tipo | Descrição |
|--------|------|-----------|
| `id` | uuid | PK |
| `name` | string(100) | Nome |
| `slug` | string(100) | URL amigável (unique) |

### NewsApprover

**Arquivo:** `Models/NewsApprover.php`, **Tabela:** `v2_news_approvers`

Controla quem pode aprovar notícias. Com `valid_from` / `valid_until` para vigência.

### NewsConfig

**Arquivo:** `Models/NewsConfig.php`, **Tabela:** `v2_news_configs`

Configurações chave/valor (JSON) para sidebar items.

---

## Controllers

### NoticiasCmsController

**Serviços injetados:** `ActivityLoggerService`, `SEOService`, `NewsNotificationService`

| Método | Rota | Ação |
|--------|------|------|
| `index()` | `GET /cms/noticias` | Lista paginada + stats + super-destaque |
| `pending()` | `GET /cms/noticias/pendentes` | Lista pendentes |
| `archived()` | `GET /cms/noticias/arquivadas` | Lista soft-deleted |
| `create()` | `GET /cms/noticias/novo` | Formulário com categorias + tags |
| `store()` | `POST /cms/noticias/novo` | Persiste + tags |
| `edit($id)` | `GET /cms/noticias/editar/{id}` | Form com coverImage, category, tags |
| `update()` | `PUT/POST /cms/noticias/editar/{id}` | Atualiza + tags |
| `destroy($id)` | `DELETE /cms/noticias/{id}` | Soft delete |
| `restore($id)` | `POST /cms/noticias/{id}/restaurar` | Restaura como rascunho |
| `preview($id)` | `GET /cms/noticias/preview/{id}` | Preview no CMS |
| `submitForApproval($id)` | `POST /cms/noticias/{id}/submeter` | Draft → pending |
| `approve($id)` | `POST /cms/noticias/{id}/aprovar` | Pending → published |
| `reject($id)` | `POST /cms/noticias/{id}/rejeitar` | Pending → rejected |
| `updateSeo()` | `POST /cms/noticias/{id}/seo` | Atualiza SEO metadata |
| `toggleHighlight($id)` | `POST /cms/noticias/{id}/toggle-highlight` | AJAX toggle is_highlighted |
| `setSuperHighlight()` | `POST /cms/noticias/set-super-highlight` | Define ou remove super-destaque |
| `categoriesPage()` | `GET /cms/noticias/categorias` | Gestão visual de categorias |
| `categoriesIndex()` | `GET /cms/noticias/categorias/json` | JSON de categorias |
| `categoriesStore()` | `POST /cms/noticias/categorias` | Criar categoria |
| `categoriesUpdate()` | `PUT/POST /cms/noticias/categorias/{id}` | Editar categoria (não sistema) |
| `categoriesDestroy()` | `DELETE /cms/noticias/categorias/{id}` | Remover categoria (não sistema) |
| `tagsIndex()` | `GET /cms/noticias/tags` | JSON de tags |
| `tagsStore()` | `POST /cms/noticias/tags` | Criar tag (firstOrCreate) |
| `tagsDestroy()` | `DELETE /cms/noticias/tags/{id}` | Remover tag |

### NoticiasPortalController

**Serviços injetados:** `SEOService`, `ContentRichEditorRendererService`, `StorageService`

| Método | Rota | Ação |
|--------|------|------|
| `index()` | `GET /noticias` | Página segmentada ou filtrada |
| `show($slug)` | `GET /noticia/{slug}` | Detalhe + sidebar + relacionadas por tag |
| `preview($slug)` | `GET /noticia/{slug}/preview` | Preview com token |

### NoticiasConfigController

Gerencia aprovadores e sidebar.

### NoticiasSitemapController

Gera Google News Sitemap em `/news-sitemap.xml`.

---

## Views CMS

### `index.blade.php` — Listagem principal

- Cards em grid com thumb, status, título, autor, datas
- **Toggle AJAX** `Destacar`/`Destacado` (estrela) — altera `is_highlighted`
- Badge **`Super`** vermelho na notícia em super-destaque
- Botão **Super-destaque** no header abre modal para selecionar notícia (ou remover)
- Filtros por status + busca textual
- Stats bar com contagens por status

### `_editor-content.blade.php` — Editor completo

- **Fluxo de aprovação** — timeline visual (Criado → Aguard. Aprovação → Aprovado/Recusado)
- **Metadados em cards:**
  - **Informações do Conteúdo:** Data do conteúdo, Data de publicação (date + time, agendável), Responsável, Fonte
  - **Categorização:** Categoria (select com optgroups) + Tags (input com autocomplete + pills clicáveis)
- **Editor split:** ContentRichEditor (Tiptap) à esquerda, Preview iframe à direita
- **Timeline** mostra autor, revisor, última edição, URL pública
- Botões de ação: Salvar, Enviar para aprovação, Aprovar, Rejeitar, Arquivar

### `categories.blade.php` — Gestão de categorias

- Tabela com colunas: Categoria (ícone + badge "sistema"), URL, Subcategorias (badges), Ações
- Categorias de sistema (Nacional, Regionais) protegidas (ícone cadeado, sem ações)
- Modal compacto para criar/editar (nome + parent select)
- Confirmação para exclusão

### `preview.blade.php` — Preview no iframe

Preview da notícia com o layout do portal.

---

## Views Portal

### `portal/index.blade.php` — Página principal segmentada

Quando **sem filtros**, organiza em 5 blocos:

1. **Super-destaque** — Card com imagem de fundo, overlay gradiente, sombra (`box-shadow: 0 8px 30px`), padding lateral
2. **Busca** — Input + botão integrados em pill arredondado (`border-radius: 50rem`)
3. **Destaques (carrossel)** — Bootstrap carousel com 3 cards por slide, setas e indicadores
4. **Nacional** — 1 notícia principal em destaque (featured, 260px altura) + grid de 4 cards menores
5. **Regionais** — Grid simples de cards (col-xl-3), sem destaque

**Badges nos cards:**
- `card-badge-nacional` — faixa vermelha "Nacional" no canto superior esquerdo
- `card-badge-star` — estrela amarela no canto superior direito se `is_highlighted`
- `card-badge-super` — trovão vermelho pulsante no canto superior direito se `is_super_highlighted`

Quando **com filtros** (`?categoria=slug`, `?tag=slug`, `?search=`), volta à paginação simples.

### `portal/show.blade.php` — Detalhe + Sidebar

- **Breadcrumb** compacto horizontal no topo (cinza claro)
- **Layout 2 colunas** (main 8 / sidebar 4):
  - **Main:** Imagem de capa (max 400px), metadados, corpo (ContentRichEditor), fonte, tags, "Relacionadas" (grid 3 colunas por tag)
  - **Sidebar (sticky):** Destaques (lista vertical com thumbs), Categoria (botão), Compartilhar (Facebook, Twitter/X, WhatsApp)
- Schema.org NewsArticle no head

---

## Rotas

### CMS

| Método | Rota | Nome |
|--------|------|------|
| GET | `/cms/noticias` | `v2.noticias.cms.index` |
| GET | `/cms/noticias/pendentes` | `v2.noticias.cms.pending` |
| GET | `/cms/noticias/novo` | `v2.noticias.cms.create` |
| POST | `/cms/noticias/novo` | `v2.noticias.cms.store` |
| GET | `/cms/noticias/editar/{id}` | `v2.noticias.cms.edit` |
| PUT/POST | `/cms/noticias/editar/{id}` | `v2.noticias.cms.update` |
| GET | `/cms/noticias/preview/{id}` | `v2.noticias.cms.preview` |
| DELETE | `/cms/noticias/{id}` | `v2.noticias.cms.destroy` |
| GET | `/cms/noticias/arquivadas` | `v2.noticias.cms.archived` |
| POST | `/cms/noticias/{id}/restaurar` | `v2.noticias.cms.restore` |
| POST | `/cms/noticias/{id}/submeter` | `v2.noticias.cms.submit-approval` |
| POST | `/cms/noticias/{id}/aprovar` | `v2.noticias.cms.approve` |
| POST | `/cms/noticias/{id}/rejeitar` | `v2.noticias.cms.reject` |
| POST | `/cms/noticias/{id}/seo` | `v2.noticias.cms.seo-update` |
| POST | `/cms/noticias/{id}/toggle-highlight` | `v2.noticias.cms.toggle-highlight` |
| POST | `/cms/noticias/set-super-highlight` | `v2.noticias.cms.set-super-highlight` |
| GET | `/cms/noticias/tags` | `v2.noticias.cms.tags.index` |
| POST | `/cms/noticias/tags` | `v2.noticias.cms.tags.store` |
| DELETE | `/cms/noticias/tags/{id}` | `v2.noticias.cms.tags.destroy` |
| GET | `/cms/noticias/categorias` | `v2.noticias.cms.categories` |
| GET | `/cms/noticias/categorias/json` | `v2.noticias.cms.categories.json` |
| POST | `/cms/noticias/categorias` | `v2.noticias.cms.categories.store` |
| PUT/POST | `/cms/noticias/categorias/{id}` | `v2.noticias.cms.categories.update` |
| DELETE | `/cms/noticias/categorias/{id}` | `v2.noticias.cms.categories.destroy` |
| GET | `/cms/noticias/config` | `v2.noticias.cms.config` |
| POST | `/cms/noticias/config/sidebar` | `v2.noticias.cms.config.sidebar` |
| POST | `/cms/noticias/config/approvers` | `v2.noticias.cms.config.approvers.add` |
| DELETE | `/cms/noticias/config/approvers/{id}` | `v2.noticias.cms.config.approvers.remove` |

### Portal

| Método | Rota | Nome |
|--------|------|------|
| GET | `/` | `v2.portal.home` (via Homepage widget `sucesu-noticias`) |
| GET | `/noticias` | `v2.noticias.portal.index` |
| GET | `/noticia/{slug}` | `v2.noticias.portal.show` |
| GET | `/noticia/{slug}/preview` | `v2.noticias.portal.preview` |
| GET | `/news-sitemap.xml` | `v2.noticias.sitemap.news` |

---

## Funcionalidades Implementadas

### Fluxo de Aprovação

Notícias passam por 4 estados: `draft` → `pending_approval` → `published` / `rejected`.

- **Rascunho:** autor pode editar, salvar, enviar para aprovação
- **Pendente:** aprovadores recebem notificação por email; podem aprovar ou rejeitar com motivo
- **Publicado:** visível no portal (sujeito ao agendamento)
- **Rejeitado:** autor recebe o motivo, pode corrigir e reenviar

### Agendamento de Publicação

- Campo `published_at_date` + `published_at_time` no formulário (date + time separados)
- Se preenchido com data futura, a notícia só fica visível no portal **a partir daquela data/hora** (fuso `America/Sao_Paulo`)
- Se vazio, publica imediatamente ao mudar status para "published"
- Portal adiciona `->where('published_at', '<=', now())` em todas as queries públicas

### Destaque e Super-destaque

**Destaque (`is_highlighted`):**
- Toggle AJAX no card da listagem CMS (estrela)
- Notícias destacadas aparecem no carrossel de destaques do portal
- Múltiplas notícias podem ser destacadas

**Super-destaque (`is_super_highlighted`):**
- **ÚNICA** notícia no topo do portal
- Botão "Super-destaque" no header da listagem CMS
- Modal com `<select>` de notícias publicadas — escolhe ou remove (opção "Nenhuma")
- Aparece também nos demais segmentos (destaques, nacional, regionais) com badge de trovão vermelho pulsante

### Tags com seleção visual

- Input de texto com autocomplete + lista de tags existentes como pills clicáveis
- Ao digitar, filtra as tags disponíveis (mostra só as que contêm o texto)
- Ao clicar numa pill, a tag vai para a área de selecionadas
- Ao remover uma tag selecionada, a pill reaparece nas disponíveis
- Tags são criadas via `firstOrCreate` por slug no controller
- Server-side: `resolveTagIds()` converte nomes para IDs

### Categorias com subcategorias

- Categorias hierárquicas (1 nível de profundidade)
- Categorias de sistema: **Nacional** e **Regionais** (protegidas, seed na migration)
- Submódulo na sidebar "Categorias" com gestão visual (tabela + modal)
- Filtro no portal via `?categoria=slug` — inclui subcategorias na query
- Badge "Nacional" no canto superior esquerdo dos cards do portal

### Segmentação da Página Principal

A página `/noticias` (sem filtros) segmenta automaticamente:

1. **Super-destaque** — se existir, card destacado com sombra
2. **Busca** — formulário centralizado com categorias como pills
3. **Destaques (carrossel)** — Bootstrap carousel com 3 cards por slide
4. **Nacional** — 1 featured (mais recente, 260px) + grid de 4 cards
5. **Regionais** — grid simples (col-xl-3), sem destaque

Quando há filtros (`?categoria=`, `?tag=`, `?search=`), volta à paginação simples.

### Widget Homepage (`SucesuNoticiasWidget`)

O módulo **Homepage** possui um widget específico (`sucesu-noticias`) que replica a mesma exibição segmentada na página inicial:

- **Banner de super-destaque** no topo da seção (apenas se `is_super_highlighted = true`)
- **Grid de notícias** abaixo, ordenadas da mais recente para a mais antiga
- **Badges** de categoria (canto superior esquerdo) e destaque/super-destaque (canto superior direito)
- **Filtros por categoria** configuráveis no formulário do widget

> ⚠️ O super-destaque só aparece no widget da homepage se a notícia estiver marcada como `is_super_highlighted` no CMS de Notícias. Não há fallback para a notícia mais recente.

**Arquivos do widget:**

| Tipo | Localização |
|------|-------------|
| Schema | `LseWidgets/Schemas/SucesuNoticiasSchema.php` |
| Widget Class | `LseWidgets/Widgets/SucesuNoticiasWidget.php` |
| Form View | `Views/lse/forms/noticias.blade.php` |
| Render View | `Views/lse/render/noticias.blade.php` |

**Registro:** `WidgetRegistry::register('sucesu-noticias', new SucesuNoticiasWidget())` no `ModuleProvider` da Homepage.

### Notícias Relacionadas por Tag

Na página de detalhe, as notícias relacionadas são selecionadas por **tags em comum**:
1. Se a notícia atual tem tags, busca até 4 notícias que compartilhem pelo menos uma tag
2. Se houver menos de 4 resultados, completa com as mais recentes (sem repetir a atual)

---

## Migrações

| Migration | Descrição |
|-----------|-----------|
| `2026_07_08_000001` | Cria `v2_news` com search vector trigger |
| `2026_07_08_000002` | Cria `v2_news_approvers` e `v2_news_configs` |
| `2026_07_22_000001` | Cria `v2_news_categories` (seeds Nacional + Regionais) |
| `2026_07_22_000002` | Cria `v2_news_tags` e `v2_news_tag_pivot` |
| `2026_07_22_000003` | Adiciona colunas a v2_news (content_date, is_highlighted, is_super_highlighted, source, responsible_name, category_id) |
| `2026_07_22_000004` | Converte pivot columns de varchar para uuid |
| `2026_07_22_000005` | Converte v2_news_tags.id, v2_news_categories.id, category_id para uuid |

---

## Auditoria e Activity Log

| Operação | Tipo |
|----------|------|
| Criar notícia | `news_create` |
| Atualizar notícia | `news_update` |
| Arquivar (soft delete) | `news_archive` |
| Restaurar | `news_restore` |
| Enviar para aprovação | `news_submit_approval` |
| Aprovar | `news_approve` |
| Rejeitar | `news_reject` |
| Atualizar SEO | `news_seo_update` |
| Toggle destaque | `news_highlight` |
| Definir super-destaque | `news_super_highlight` |
| Criar/editar/remover categoria | `news_category_create/update/delete` |
