# Pauta do Dia — Modelagem de Dados

> **Slug:** `pauta-do-dia` · **Módulo:** `modulos.id = 42` · **Novo na v6**
> O módulo **não possui tabela própria**: reaproveita `contents`,
> `category_contents` e `archives`, e projeta a view SQL `pauta_do_dia`.

---

## 1. Objetos de banco envolvidos

| Objeto | Tipo | Papel no módulo | Origem |
|---|---|---|---|
| `contents` | tabela | Armazena a pauta (título, texto, data/hora, slug, autor, fonte, status, categoria). | `v6/database/migrations/2018_04_02_180433_create_contents_table.php` |
| `category_contents` | tabela | Classifica os conteúdos; a linha `slug = 'pauta-do-dia'` define o que é uma pauta. | `v6/database/migrations/2018_04_01_180916_create_category_contents_table.php` |
| `archives` | tabela | Anexos da pauta (documentos, PDF embutido, imagem de destaque). | `v6/database/migrations/2018_04_02_181807_create_archives_table.php` |
| `pauta_do_dia` | **view SQL** | Projeção consumida pela listagem do CMS. | `v6/database/migrations/2024_10_13_010549_create_view_pauta_do_dia.php` |
| `modulos` | tabela | Habilita/desabilita o módulo no CMS. | `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` |
| `menu_orders` | tabela | Telemetria de cliques do item de menu. | (tabela do CMS; sem migration no escopo deste módulo) |
| `menu_items` | tabela | Item de menu público "Pauta do Dia". | `v6/database/seeds/MenuItemsTableSeeder.php` |

## 2. Colunas da view `pauta_do_dia`

Definida em `v6/database/migrations/2024_10_13_010549_create_view_pauta_do_dia.php`
(data **13/10/2024**, classe `CreateViewPautaDoDia`):

```sql
CREATE OR REPLACE VIEW pauta_do_dia AS
SELECT row_number() OVER (ORDER BY c.publication_date DESC) AS value,
    c.title AS titulo,
    to_char(c.publication_date::timestamp with time zone, 'DD/MM/YYYY às HH24:MI'::text) AS data_e_hora,
    c.id,
    c.slug as slug
   FROM contents c
  WHERE c.deleted_at IS NULL AND c.category_contents_id = (
        SELECT id FROM category_contents WHERE slug = 'pauta-do-dia'
  );
```

| Coluna | Tipo | Origem | Significado |
|---|---|---|---|
| `value` | `bigint` | `row_number() OVER (ORDER BY c.publication_date DESC)` | Numeração sequencial da pauta por data de publicação decrescente. **Não é o `id` do conteúdo.** |
| `titulo` | `varchar(191)` | `c.title` | Título digitado pelo operador (ex.: "19ª Sessão"). |
| `data_e_hora` | `text` | `to_char(... 'DD/MM/YYYY às HH24:MI')` | Data e hora **formatadas como texto** em português. |
| `id` | `integer` | `c.id` | Identificador real do conteúdo em `contents`. |
| `slug` | `varchar(191)` | `c.slug` | Link permanente usado no portal e no compartilhamento. |

Pontos de atenção da view:

- O `row_number()` ordena apenas por `publication_date` (timestamp completo),
  **sem** a regra de número de sessão usada pelo portal; a coluna "ID" exibida
  no CMS é, portanto, uma numeração de exibição e não o identificador.
- `data_e_hora` é `text`, não `timestamp`: o DataTable ordena por uma chave
  textual calculada em JavaScript.
- O filtro usa **subquery escalar** em `category_contents`; se existirem duas
  categorias com o mesmo slug, a view passa a levantar erro do PostgreSQL
  ("more than one row returned by a subquery used as an expression").
- Apenas a categoria raiz é considerada: pautas em subcategorias não entram
  (a categoria raiz tem `category_contents_id IS NULL` no banco).
- O `down()` da migration executa `DROP VIEW if exists pauta_do_dia;`.

## 3. Tabela `contents` (colunas relevantes)

Verificado em `\d contents` (PostgreSQL):

| Coluna | Tipo | Null | Default | Observação |
|---|---|---|---|---|
| `id` | `integer` | not null | `nextval('contents_id_seq')` | PK |
| `title` | `varchar(191)` | sim | — | Texto livre do operador; a ordenação extrai números daqui. |
| `text` | `text` | sim | — | Corpo HTML (Summernote). |
| `clicks` | `integer` | sim | — | Contador de cliques. |
| `highlights` | `boolean` | sim | — | Destaque genérico. |
| `galery` | `boolean` | sim | — | Galeria de imagens. |
| `status` | `boolean` | sim | — | Publicação; **não filtrado** na listagem pública nem na view. |
| `source` | `varchar(191)` | sim | — | Exibido no detalhe público. |
| `author` | `varchar(191)` | sim | — | Exibido no detalhe público. |
| `icon` | `varchar(191)` | sim | — | — |
| `slug` | `varchar(191)` | sim | — | Link permanente (`/pauta-do-dia/{slug}`). |
| `publication_date` | `timestamp(0) without time zone` | sim | — | Data **e hora**; base da ordenação. |
| `order` | `integer` | sim | — | Posição no CMS. |
| `category_contents_id` | `integer` | **not null** | — | FK → `category_contents(id)`. |
| `created_at` / `updated_at` | `timestamp(0)` | sim | — | — |
| `deleted_at` | `timestamp(0)` | sim | — | Soft delete. |
| `secretaria` | `varchar(191)` | sim | — | — |
| `carousel` | `boolean` | sim | — | — |
| `orgaos_id` | `integer` | sim | — | FK → `contents(id)`. |
| `position_id` | `bigint` | sim | — | — |
| `acronym` | `varchar(191)` | sim | — | — |
| `layout` | `integer` | sim | — | — |
| `councilmen_id` | `integer` | sim | — | FK → `councilmen(id)`, `ON DELETE SET NULL`. |
| `linha_fina` | `varchar(300)` | sim | — | — |
| `referencia` | `varchar(50)` | sim | — | — |

FK relevantes: `contents_category_contents_id_foreign` →
`category_contents(id)`; `archives_contents_id_foreign` → `contents(id)`.

## 4. Tabela `category_contents` (colunas relevantes)

| Coluna | Tipo | Null | Observação |
|---|---|---|---|
| `id` | `integer` | not null | PK |
| `name` | `varchar(191)` | not null | "Pautas do Dia" (seed) |
| `slug` | `varchar(191)` | not null | `pauta-do-dia` — **chave semântica do módulo** |
| `status_create` | `boolean` | sim | Permite criação de conteúdo na categoria |
| `quantidade_itens` | `integer` | sim | — |
| `order` | `integer` | sim | — |
| `layout` | `integer` | sim | — |
| `category_contents_id` | `integer` | sim | FK auto-referenciada (árvore); a raiz do módulo tem `NULL` |
| `status` | `boolean` | sim | default `true` |
| `display_type`, `custom_icon` | `varchar(191)` | sim | — |
| `description` | `text` | sim | — |
| `created_at`, `updated_at`, `deleted_at` | `timestamp(0)` | sim | — |

Linha do módulo (verificada no banco):

```
 id |     name      |     slug     | status_create | category_contents_id | status
----+---------------+--------------+---------------+----------------------+--------
 38 | Pautas do Dia | pauta-do-dia | t             | (null)               | t
```

## 5. Tabela `archives` (colunas relevantes)

| Coluna | Tipo | Null | Papel no módulo |
|---|---|---|---|
| `id` | `integer` | not null | PK |
| `contents_id` | `integer` | sim | FK → `contents(id)`; liga o anexo à pauta. |
| `title` | `varchar(300)` | sim | — |
| `description` | `text` | sim | Texto exibido no download ("Arquivos para Download"). |
| `name` | `varchar(300)` | sim | Nome do arquivo exibido no download. |
| `path` | `varchar(300)` | sim | Caminho relativo no storage (`storage/content/...`). |
| `type` | `varchar(191)` | sim | MIME type; `image/jpeg`/`image/png` mudam a renderização do PDF. |
| `extension` | `varchar(191)` | sim | Filtrada pela lista de extensões permitidas na view de anexos. |
| `highlight` | `boolean` | sim | `true` = imagem de destaque; `null` = anexo comum. |
| `view_pdf` | `boolean` | sim | `true` = documento embutido em `iframe`. |
| `status_create` | `boolean` | sim | — |
| `hash` | `varchar(300)` | sim | — |
| `deleted_at`, `created_at`, `updated_at` | `timestamp(0)` | sim | — |
| `order` | `integer` | sim | — |
| `static_pages_id`, `biddings_id`, `calendar_events_id`, `requerimento_id`, `tv_camara_id`, `highlight_id`, `type_size_id` | bigint/int | sim | FKs de outros módulos (nulos para pauta). |
| `width`, `height` | `varchar(191)` | sim | Dimensões da imagem de destaque. |
| `reference_id` | `text` | sim | — |

## 6. Relacionamentos

```
category_contents (1) ────< contents (N)      contents.category_contents_id
contents (1) ────< archives (N)                archives.contents_id
```

No Eloquent (`v6/app/Http/Model/Prefecture/Content.php`):

```php
public function archives()
{
    return $this->hasMany(Archive::class, 'contents_id', 'id');
}

public function categoryContents()
{
    return $this->belongsTo(CategoryContent::class, 'category_contents_id', 'id');
}
```

Observação: `categoryContents()` é `belongsTo` (uma categoria por conteúdo).
Por isso `whereHas('categoryContents', ...)` equivale a filtrar por
`category_contents_id` — a mesma condição usada pela view SQL.

## 7. Diagrama de dados

```
                     category_contents
                     ┌────────────────────────────────────────┐
                     │ id (PK)          │ 38                 │
                     │ name             │ "Pautas do Dia"    │
                     │ slug            │ "pauta-do-dia"     │
                     │ category_contents_id (FK, NULL = raiz)│
                     │ status, status_create, order, layout  │
                     └───────────────────┬────────────────────┘
                                         │ 1
                                         │
                                         │ N
                     contents            │
                     ┌────────────────────────────────────────┐
                     │ id (PK)                               │
                     │ title          ← "19ª Sessão"         │
                     │ text           ← corpo HTML            │
                     │ publication_date ← data/hora (ordem)   │
                     │ slug           ← /pauta-do-dia/{slug} │
                     │ author, source                          │
                     │ status        ← publicado (não filtrado)│
                     │ category_contents_id (FK, NOT NULL)    │
                     │ deleted_at    ← soft delete            │
                     └───────────────────┬────────────────────┘
                                         │ 1
                                         │
                                         │ N
                     archives            │
                     ┌────────────────────────────────────────┐
                     │ id (PK)          │ contents_id (FK)    │
                     │ path, name, description, type          │
                     │ extension                              │
                     │ highlight  ← imagem de destaque        │
                     │ view_pdf   ← PDF embutido               │
                     └────────────────────────────────────────┘

                     View SQL pauta_do_dia
                     ┌────────────────────────────────────────┐
                     │ value (row_number por publication_date)│
                     │ titulo (= contents.title)               │
                     │ data_e_hora (text "DD/MM/YYYY às HH24:MI")│
                     │ id (= contents.id)                      │
                     │ slug (= contents.slug)                  │
                     └────────────────────────────────────────┘
```

## 8. Migrations relacionadas

| Migration | Data no nome | Efeito |
|---|---|---|
| `v6/database/migrations/2018_04_01_180916_create_category_contents_table.php` | 2018-04-01 | Cria `category_contents` (árvore por `category_contents_id`). |
| `v6/database/migrations/2018_04_02_180433_create_contents_table.php` | 2018-04-02 | Cria `contents` com `category_contents_id`, `softDeletes`, `publication_date`. |
| `v6/database/migrations/2018_04_02_181807_create_archives_table.php` | 2018-04-02 | Cria `archives` (anexos de conteúdo). |
| `v6/database/migrations/2022_11_29_092240_add_viewpdf_to_archives_table.php` | 2022-11-29 | Acrescenta `archives.view_pdf` (PDF embutido usado no detalhe da pauta). |
| `v6/database/migrations/2023_08_17_094529_add_position_id_to_contents_table.php` | 2023-08-17 | Acrescenta `contents.position_id`. |
| `v6/database/migrations/2024_10_13_010549_create_view_pauta_do_dia.php` | 2024-10-13 | **Cria a view `pauta_do_dia`**. |
| `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` | 2024-11-01 | Cria `modulos` e insere o módulo 42 `pauta-do-dia` com `status = 0`. |
| `v6/database/migrations/2026_06_25_000001_add_linha_fina_to_contents_table.php` | 2026-06-25 | Acrescenta `contents.linha_fina`. |

## 9. Seeds e dados iniciais

| Arquivo | O que popula |
|---|---|
| `v6/database/seeds/CategoryContentTableSeeder.php:212` | `CategoryContent::updateOrCreate(['slug' => 'pauta-do-dia'], ['name' => 'Pautas do Dia', 'status_create' => 1])`. |
| `v6/database/seeds/ResourcesTableSeeder.php:504` | Recurso/funcionalidade `pauta-do-dia` com `modulo_id = 42`. |
| `v6/database/seeds/MenuOrderSeeder.php:284` | `MenuOrder` com `slug = 'pauta-do-dia'`, `clicks = 0`, `modulo_id = 42`. |
| `v6/database/seeds/MenuItemsTableSeeder.php:382` | Item de menu do portal "Pauta do Dia" (`order` 26, `menus_id` 1), ligado à categoria. |
| `v6/database/migrations/2024_11_01_150206_create_modulos_table.php:277` | `modulos` (id 42, `name` "Pauta do Dia", `status` 0 — desligado). |

Estado verificado no banco `prefeituras_v5`:

```
modulos:     id 42 | Pauta do Dia | pauta-do-dia | status = f
menu_orders: id 36 | pauta-do-dia | modulo_id = 42
category_contents: id 38 | Pautas do Dia | pauta-do-dia | category_contents_id = NULL
contents na categoria pauta-do-dia: 0 registros (ambiente sem dados de pauta)
```

> O módulo nasce **sem nenhuma pauta cadastrada** e **desligado**; o
> operador precisa habilitá-lo em `/cms/modulos` e cadastrar a primeira pauta.

## 10. Regras de integridade do banco

1. `contents.category_contents_id` é **NOT NULL** com FK para
   `category_contents(id)`: uma pauta sempre pertence a uma categoria; se a
   categoria for excluída, a FK impede a exclusão.
2. `category_contents.category_contents_id` é auto-referenciada: a raiz do
   módulo tem `NULL`; o controller do portal usa `whereNull('category_contents_id')`
   para achar a raiz e a view SQL não pode ser ambígua.
3. `archives.contents_id` é FK para `contents(id)`: apagar a pauta (soft
   delete) não apaga os anexos, mas eles ficam órfãos de visão.
4. A view `pauta_do_dia` é um **objeto do banco**: alterar a view exige
   `CREATE OR REPLACE VIEW` (ou nova migration), pois o CMS consulta
   `DB::table('pauta_do_dia')` diretamente.
