# Atas e Pontos de Frequência — Modelagem de Dados

> Versão analisada: `v6`. Módulo **novo na v6**. Slug `modulos`:
> `atas-e-pontos-de-frequencia` (id 46).
> Tipos de coluna conferidos no PostgreSQL 10 em `\d contents`,
> `\d category_contents`, `\d archives` e `\d atas_e_pontos`.

---

## 1. Visão geral

O módulo **não tem tabela própria**. Reaproveita três tabelas genéricas do
conteúdo do portal e uma view SQL específica:

| Objeto | Tipo | Papel |
|---|---|---|
| `contents` | tabela | Documento publicado (ata ou ponto de frequência). |
| `category_contents` | tabela | Árvore de categorias; raiz `atas-e-pontos` e filhas. |
| `archives` | tabela | Anexos do documento (PDF/imagem). |
| `atas_e_pontos` | **view SQL** | Projeção para a listagem do CMS. |
| `modulos` | tabela | Registro do módulo no CMS (id 46). |
| `menu_orders` | tabela | Métrica de cliques do menu (`slug = 'atas-e-pontos'`, `modulo_id = 46`). |

**Não existe** tabela de atas, de sessões vinculadas a atas, de pontos de
frequência, de presença por vereador, de quórum ou de qualquer tipo de registro
de frequência. Verificação feita por busca em `v6/app`, `v6/database` e
`v6/resources` pelos termos `frequencia`, `presenca`, `presença`, `quorum`:
os únicos achados são o seed da **categoria** `pontos-de-frequencia`
(`v6/database/seeds/CategoryContentTableSeeder.php:238-242`), o nome do
módulo em `v6/database/migrations/2024_11_01_150206_create_modulos_table.php:299-303`,
a lista de módulos do formulário administrativo
(`v6/resources/views/admin/modulos/form.blade.php:114-118`) e textos não
relacionados (frequência de atualização do dashboard).

## 2. `contents` — o documento publicado

Migration: `v6/database/migrations/2018_04_02_180433_create_contents_table.php`
(2018-04-02). Colunas relevantes (estado real do banco):

| Coluna | Tipo | Null | Default | Papel no módulo |
|---|---|---|---|---|
| `id` | integer | não | serial | PK. |
| `title` | varchar(191) | sim | — | Título do documento. |
| `text` | text | sim | — | Corpo HTML (Summernote). |
| `clicks` | integer | sim | — | Contador incrementado a cada abertura (`view`). |
| `highlights` | boolean | sim | — | Não usado por este módulo. |
| `galery` | boolean | sim | — | Não usado por este módulo. |
| `status` | boolean | sim | — | Flag de status (usada pelo CMS genérico). |
| `slug` | varchar(191) | sim | — | Identificador na URL `atas-e-pontos/{cat}/{slug}`. |
| `publication_date` | timestamp(0) | sim | — | Data de publicação; base dos filtros e da regra de "conteúdo futuro invisível". |
| `order` | integer | sim | — | Ordenação; recalculada pelo CMS ao gravar. |
| `category_contents_id` | integer | **não** | — | FK para `category_contents.id`; aponta para a categoria **filha** (`atas` ou `pontos-de-frequencia`). |
| `created_at` / `updated_at` | timestamp(0) | sim | — | Timestamps. |
| `deleted_at` | timestamp(0) | sim | — | Soft delete. |
| `secretaria`, `orgaos_id`, `position_id`, `acronym`, `layout`, `councilmen_id`, `linha_fina`, `referencia` | — | sim | — | Colunas de outros módulos; `councilmen_id` existe na tabela mas **não é usada** por atas-e-pontos. |

FK: `contents_category_contents_id_foreign` → `category_contents(id)`.

## 3. `category_contents` — a árvore

Migration: `v6/database/migrations/2018_04_01_180916_create_category_contents_table.php`
(2018-04-01).

| Coluna | Tipo | Null | Papel |
|---|---|---|---|
| `id` | integer | não | PK. |
| `name` | varchar(191) | não | Nome exibido no menu lateral do portal. |
| `slug` | varchar(191) | não | Identificador na URL; `atas-e-pontos` é a raiz do módulo. |
| `status_create` | boolean | sim | Indica se a categoria aceita criação de conteúdo. |
| `quantidade_itens` | integer | sim | Não usado por este módulo. |
| `order` | integer | sim | Ordena as categorias filhas no menu e define a primeira exibida em `/atas-e-pontos`. |
| `layout` | integer | sim | Não usado por este módulo. |
| `category_contents_id` | integer | sim | FK autorreferente: define o pai. `NULL` = raiz. |
| `created_at`/`updated_at`/`deleted_at` | timestamp(0) | sim | Timestamps e soft delete. |

### Estado real (consulta ao banco)

| id | name | slug | category_contents_id | status_create | order |
|---|---|---|---|---|---|
| 41 | Atas e Pontos de Frequência | `atas-e-pontos` | (raiz) | true | — |
| 42 | Atas | `atas` | 41 | true | — |
| 43 | Pontos de Frequência | `pontos-de-frequencia` | 41 | true | — |

Origem: `v6/database/seeds/CategoryContentTableSeeder.php:227-241`
(`CategoryContent::updateOrCreate`). O CMS permite criar novas categorias
filhas em `/cms/categorias/cadastrar/atas-e-pontos`.

**Estado atual do conteúdo:** a consulta
`select count(*) from contents where category_contents_id in (42, 43)` retorna
**0** — não há atas nem pontos de frequência publicados na base analisada.

## 4. `archives` — os anexos

Migration: `v6/database/migrations/2018_04_02_181807_create_archives_table.php`
(2018-04-02); `view_pdf` em
`v6/database/migrations/2022_11_29_092240_add_viewpdf_to_archives_table.php`
(2022-11-29).

| Coluna | Tipo | Papel no módulo |
|---|---|---|
| `id` | integer | PK. |
| `title` / `description` / `name` | varchar(300) / text | Rótulo do arquivo; o portal mostra `description or name`. |
| `type` | varchar(191) | MIME type; `image/jpeg` e `image/png` são exibidos como `<img>`. |
| `path` | varchar(300) | Caminho relativo ao `public/`; usado no `<iframe src="/{path}">` e no link de download. |
| `extension` | varchar(191) | Filtrada pela lista de extensões permitidas no download. |
| `highlight` | boolean | Quando `true`, o anexo é destaque e **não** entra na lista de downloads (`where('highlight', null)` no controller). |
| `view_pdf` | boolean | Quando `true`, o anexo é exibido embutido na página do documento. |
| `contents_id` | integer | FK para `contents(id)` — o vínculo do anexo com o documento. |
| `deleted_at`, `created_at`, `updated_at` | timestamp(0) | Timestamps e soft delete. |
| Demais colunas (`biddings_id`, `static_pages_id`, `calendar_events_id`, `requerimento_id`, `tv_camara_id`, `highlight_id`, `type_size_id`, `reference_id`, `width`, `height`, `order`, `hash`, `status_create`) | — | Pertencem a outros módulos; `NULL` para atas-e-pontos. |

FK: `archives_contents_id_foreign` → `contents(id)`.

## 5. A view SQL `atas_e_pontos`

Migration: `v6/database/migrations/2025_01_20_122421_create_view_atas_e_pontos_table.php`
(2025-01-20). Código real:

```php
DB::statement("DROP VIEW IF EXISTS atas_e_pontos;");
DB::statement("
    CREATE OR REPLACE VIEW atas_e_pontos
    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_publicacao,
    cc.name as categoria,
    cc.slug as categoria_slug,
    c.slug as slug,
    c.id as id
    from contents c
        JOIN category_contents cc ON cc.id = c.category_contents_id
        join category_contents cc2 on 'atas-e-pontos' = cc2.slug
    WHERE c.deleted_at IS NULL AND cc.category_contents_id = cc2.id or c.category_contents_id = cc2.id  
");
```

### Colunas da view (verificado via `\d atas_e_pontos`)

| Coluna | Tipo | Origem | Uso |
|---|---|---|---|
| `value` | bigint | `row_number() OVER (ORDER BY c.publication_date DESC)` | Numeração sequencial exibida na coluna ID do DataTable. |
| `titulo` | varchar(191) | `c.title` | Coluna "Título". |
| `data_publicacao` | varchar(191) (texto) | `to_char(...,'DD/MM/YYYY às HH24:MI')` | Coluna "Data de publicação" já formatada. |
| `categoria` | varchar(191) | `cc.name` | Coluna "Categoria". |
| `categoria_slug` | varchar(191) | `cc.slug` | Monta o link público `/atas-e-pontos/{categoria_slug}/{slug}`. |
| `slug` | varchar(191) | `c.slug` | Mesma finalidade. |
| `id` | integer | `c.id` | Identificador do conteúdo, usado na edição. |

### O que a view abre

- A raiz `atas-e-pontos` **e** suas filhas (`atas`, `pontos-de-frequencia`),
  pois a condição `c.category_contents_id = cc2.id` cobre a raiz e
  `cc.category_contents_id = cc2.id` cobre as filhas.
- **Atenção**: como o `AND` tem precedência sobre o `OR`, a condição efetiva é
  `(c.deleted_at IS NULL AND cc.category_contents_id = cc2.id) OR
  (c.category_contents_id = cc2.id)`. A segunda disjuncão **não** exige
  `deleted_at IS NULL`, logo conteúdo removido (soft delete) cujo conteúdo
  está diretamente na categoria raiz aparece na listagem do CMS. Detalhamento
  em `05-REGRAS-E-PORTABILIDADE.md`.
- A view **não** filtra `publication_date` futura: o CMS pode listar conteúdo
  ainda não publicado no portal.

## 6. Diagrama de relacionamentos

```
                 category_contents (raiz)
                 id 41  slug='atas-e-pontos'
                        ▲            ▲
        category_contents_id       category_contents_id
            (filhas)                    (filhas)
                 │            │
        ┌────────┴───┐   ┌────┴──────────────┐
        │ id 42      │   │ id 43              │
        │ 'atas'     │   │ 'pontos-de-        │
        │            │   │  frequencia'       │
        └────┬───────┘   └────┬──────────────┘
             │                │
             │  contents.category_contents_id (FK)
             ▼                ▼
        ┌───────────────────────────────┐
        │ contents                      │
        │  id, title, text, slug,       │
        │  publication_date, clicks,    │
        │  status, order, deleted_at    │
        └───────────────┬───────────────┘
                        │ archives.contents_id (FK)
                        ▼
        ┌────────────────────────────────────────┐
        │ archives                               │
        │  path, name, description, extension,   │
        │  type, highlight, view_pdf             │
        └────────────────────────────────────────┘

        [atas_e_pontos]  ← VIEW SQL sobre contents ⋈ category_contents
        modulos (id 46)  ← módulo no CMS
        menu_orders (slug 'atas-e-pontos', modulo_id 46) ← métrica de cliques
```

## 7. Migrations e seeds relacionados

| Arquivo | Data | Efeito |
|---|---|---|
| `v6/database/migrations/2018_04_01_180916_create_category_contents_table.php` | 2018-04-01 | Cria `category_contents`. |
| `v6/database/migrations/2018_04_02_180433_create_contents_table.php` | 2018-04-02 | Cria `contents`. |
| `v6/database/migrations/2018_04_02_181807_create_archives_table.php` | 2018-04-02 | Cria `archives`. |
| `v6/database/migrations/2022_11_29_092240_add_viewpdf_to_archives_table.php` | 2022-11-29 | Adiciona `archives.view_pdf`. |
| `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` | 2024-11-01 | Registra o módulo (linhas 298-303), `status = 0`. |
| `v6/database/migrations/2025_01_20_122421_create_view_atas_e_pontos_table.php` | 2025-01-20 | Cria a view `atas_e_pontos`. |
| `v6/database/seeds/CategoryContentTableSeeder.php` | — | Cria a raiz `atas-e-pontos` e as filhas `atas` e `pontos-de-frequencia`. |

## 8. Ausências estruturais (lacunas)

| O que não existe | Consequência |
|---|---|
| Tabela de sessões vinculada a atas | Não é possível ligar a ata a uma sessão específica; a ligação é apenas textual. |
| Tabela de presença/frequência por vereador | Não há cálculo de quórum, maioria ou absenteísmo. |
| Campo `document_type` em `contents` | O tipo do documento é deduzido pela categoria filha (`atas` vs `pontos-de-frequencia`). |
| Tabela própria do módulo | Reutilização total de `contents`; a desambiguação depende do slug da categoria. |
