# VIAP — Modelagem de Dados

> Tabelas, view SQL, chaves e seeds do módulo VIAP na `v6`.
> Todas as estruturas abaixo foram confirmadas com `psql \d` no banco
> `prefeituras_v5` do container `v6_6f22e1bd-postgres-1`.

---

## 1. Diagrama de relacionamentos

```
┌────────────────────┐                ┌──────────────────────────┐
│      councilmen    │                │        contents          │
│  (parlamentares)   │                │  (órgãos / secretarias) │
├────────────────────┤                ├──────────────────────────┤
│ id PK              │                │ id PK                    │
│ name               │                │ title                    │
│ status             │                │ category_contents_id FK  │
└─────────┬──────────┘                └────────────┬─────────────┘
          │ 1                                        │ 1
          │                                           │
          │ N                                         │ N
          ▼                                           ▼
      ┌───────────────────────────────────────────────────────┐
      │                        viap                          │
      ├───────────────────────────────────────────────────────┤
      │ id PK            │ publication_date  │ amount        │
      │ reference_month  │ reference_year    │ description   │
      │ link_type        │ councilman_id FK  │ organ_id FK   │
      │ status           │ slug              │ created_at    │
      │ updated_at       │ deleted_at        │               │
      └───────────────────────────┬───────────────────────────┘
                                  │ N
                                  │
                    ┌─────────────┴──────────────┐
                    │    viap_has_archives       │
                    ├────────────────────────────┤
                    │ id PK                      │
                    │ viap_id   FK → viap.id     │
                    │ archive_id FK → archives.id│
                    └─────────────┬──────────────┘
                                  │ N
                                  ▼
                        ┌───────────────────┐
                        │     archives      │
                        ├───────────────────┤
                        │ id PK             │
                        │ title/description │
                        │ path/name/type    │
                        └───────────────────┘

      view_viap  (CREATE VIEW)  ← LEFT JOIN councilmen / contents
```

## 2. Tabela `viap`

Criada por `v6/database/migrations/2026_05_25_112000_create_viap_table.php`.

| Coluna | Tipo (PostgreSQL) | Nullable | Default | Observação |
|---|---|---|---|---|
| `id` | `integer` (serial) | não | `nextval('viap_id_seq')` | PK |
| `publication_date` | `timestamp(0) without time zone` | sim | — | Data de publicação (vinda de `dateTime`) |
| `amount` | `numeric(15,2)` | sim | — | **Valor da verba — nunca preenchido** (Gap G-02) |
| `reference_month` | `integer` | sim | — | 1..12 |
| `reference_year` | `integer` | sim | — | Ano de referência |
| `description` | `text` | sim | — | **Descrição — nunca persistida** (Gap G-03) |
| `link_type` | `character varying(191)` | **não** | — | `gabinete` \| `outro_orgao` (o `->comment()` da migration **não** foi aplicado no Postgres) |
| `councilman_id` | `integer` | sim | — | FK → `councilmen.id` ON DELETE CASCADE |
| `organ_id` | `integer` | sim | — | FK → `contents.id` ON DELETE CASCADE |
| `status` | `boolean` | não | `true` | Controla a exibição no portal (`scopeActive`) |
| `slug` | `character varying(191)` | sim | — | Chave pública `/viap/{slug}` — **sem índice único** (Gap G-06) |
| `created_at` | `timestamp(0)` | sim | — | |
| `updated_at` | `timestamp(0)` | sim | — | |
| `deleted_at` | `timestamp(0)` | sim | — | SoftDeletes |

Índices e constraints (saída de `\d viap`):

```
Indexes:
    "viap_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "viap_councilman_id_foreign" FOREIGN KEY (councilman_id) REFERENCES councilmen(id) ON DELETE CASCADE
    "viap_organ_id_foreign" FOREIGN KEY (organ_id) REFERENCES contents(id) ON DELETE CASCADE
```

Observações:

- A migration declara `$table->engine = 'InnoDB'` — **sem efeito no PostgreSQL**
  (o motor é definido no cluster, não por tabela).
- **Não existe** `unique` em `slug` nem em `(reference_year, reference_month, link_type, councilman_id/organ_id)`.
- Estado atual da base: `SELECT count(*) FROM viap` → **0 registros**.

## 3. Tabela `viap_has_archives` (pivot)

Criada na mesma migration `2026_05_25_112000_create_viap_table.php`.

| Coluna | Tipo | Nullable | Default | Chave |
|---|---|---|---|---|
| `id` | `integer` (serial) | não | `nextval(...)` | PK |
| `viap_id` | `integer` | não | — | FK → `viap.id` ON DELETE CASCADE |
| `archive_id` | `integer` | não | — | FK → `archives.id` ON DELETE CASCADE |

```
Foreign-key constraints:
    "viap_has_archives_archive_id_foreign" FOREIGN KEY (archive_id) REFERENCES archives(id) ON DELETE CASCADE
    "viap_has_archives_viap_id_foreign" FOREIGN KEY (viap_id) REFERENCES viap(id) ON DELETE CASCADE
```

Relacionamento Eloquent: `Viap::archives()` →
`belongsToMany(Archive::class, 'viap_has_archives', 'viap_id', 'archive_id')`
(`v6/app/Http/Model/Prefecture/Viap.php:69-72`).

Estado atual: `SELECT count(*) FROM viap_has_archives` → **0 registros**.

## 4. View SQL `view_viap`

Criada por `v6/database/migrations/2026_05_25_112100_create_view_to_viap.php`.

```sql
CREATE OR REPLACE VIEW view_viap AS
SELECT
    v.id,
    v.publication_date,
    v.amount,
    v.reference_month,
    v.reference_year,
    v.description,
    v.link_type,
    CASE
        WHEN v.link_type = 'gabinete' THEN c.name
        WHEN v.link_type = 'outro_orgao' THEN o.title
        ELSE NULL
    END as linked_name,
    v.status,
    v.slug,
    v.created_at,
    v.updated_at,
    v.deleted_at
FROM viap v
LEFT JOIN councilmen c ON c.id = v.councilman_id
LEFT JOIN contents o ON o.id = v.organ_id
WHERE v.deleted_at IS NULL;
```

| Aspecto | Detalhe |
|---|---|
| Quem abre | **Somente o CMS**, via `DatatableController::get_viap()` (`DB::table('view_viap')->get()`) |
| Coluna virtual | `linked_name` — nome do parlamentar (`councilmen.name`) ou título do órgão (`contents.title`) |
| Filtro de exclusão | Já embutido: `WHERE v.deleted_at IS NULL` (redundante com `SoftDeletes`) |
| Portal | **Não usa a view** — o portal consulta a tabela `viap` via Eloquent |
| Colunas expostas | 13 (as 12 da tabela + `linked_name`) |

## 5. Tabelas do sistema usadas (não criadas pelo módulo)

| Tabela | Uso no VIAP | Origem |
|---|---|---|
| `councilmen` | Fonte dos gabinetes (parlamentares ativos) | Módulo **Vereadores** (`v6/app/Http/Model/Prefecture/CityCouncilor.php`) |
| `contents` | Fonte dos órgãos (conteúdos da categoria `orgaos`) | Espinha dorsal do portal (`v6/app/Http/Model/Prefecture/Content.php`) |
| `category_contents` | Filtra `contents` por `category_contents_id` dos registros com `slug = 'orgaos'` | `v6/app/Http/Model/Prefecture/CategoryContent.php` |
| `archives` | Metadados dos anexos | `v6/app/Http/Model/Prefecture/Archive.php` |
| `modulos` | Registro do módulo (id 58) | `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` |
| `resources` | Recurso/permissão `viap` (id 79) | Gerenciado pelo CMS |
| `menu_orders` | Entrada do menu lateral (id 45, `modulo_id` 58) | Gerenciado pelo CMS |
| `roles` / `role_users` | Permissões `viap.*` do papel | Sentinel (`v6/app/Http/Model/Admin/User.php`) |

Consulta da categoria "órgãos" usada pelo módulo:

```php
// v6/app/Http/Controllers/Prefecture/ViapController.php:124-136
$categoryIds = CategoryContent::where('slug', 'orgaos')->pluck('id')->toArray();
return [
    'councilmen' => CityCouncilor::where('status', true)->orderBy('name')->get(),
    'organs' => Content::whereIn('category_contents_id', $categoryIds)->where('deleted_at', null)->orderBy('title')->get(),
];
```

## 6. Migrations do módulo

| Migration | Data (nome do arquivo) | Efeito |
|---|---|---|
| `v6/database/migrations/2026_05_25_112000_create_viap_table.php` | 2026-05-25 11:20:00 | Cria `viap` e `viap_has_archives` com FKs |
| `v6/database/migrations/2026_05_25_112100_create_view_to_viap.php` | 2026-05-25 11:21:00 | Cria a view `view_viap` (`DROP VIEW IF EXISTS` no `down()`) |

**Não há** migration que registre o módulo em `modulos` — o registro é feito
exclusivamente por seed (ver seção 7).

## 7. Seed — `v6/database/seeds/ViapSeeder.php`

O seed é **idempotente** (`updateOrCreate` / "se existe, atualiza") e executa
quatro passos:

| Passo | Ação | Verificado no banco |
|---|---|---|
| 1 | `modulos`: cria ou atualiza `{name: 'VIAP', slug: 'viap', status: 1}` | `modulos.id = 58`, `name = 'VIAP'`, `slug = 'viap'`, `status = t` |
| 2 | `resources`: cria o recurso/permissão `viap` | `resources.id = 79`, `name = 'VIAP'`, `modulo_id = 58`, descrição *"Verba Indenizatória de Apoio Parlamentar"* |
| 3 | `menu_orders`: cria a entrada de menu `{slug: 'viap', clicks: 0, modulo_id: 58}` | `menu_orders.id = 45` |
| 4 | `roles`: mescla as permissões `viap.*` no papel `administrador` | `roles.permissions` contém `viap.index/create/edit/delete/inactive/inactivate/restore/show = true` |

Permissões gravadas (extraídas do seed, linhas 61-70):

```php
$viapPermissions = [
    "viap.index" => true,
    "viap.create" => true,
    "viap.edit" => true,
    "viap.delete" => true,
    "viap.inactive" => true,
    "viap.inactivate" => true,
    "viap.restore" => true,
    "viap.show" => true
];
```

Observações:

- O passo 4 só roda se o papel `administrador` existir; **não** há fallback.
- As permissões `viap.store`, `viap.update`, `viap.destroy`,
  `viap.delete-por-lote` e `viap.ajax.delete.files` **não são gravadas** — ver
  a matriz de autorização em `03-ROTAS-E-CONTROLADORES.md`.
- `viap.inactive`, `viap.inactivate`, `viap.restore` e `viap.show` são
  permissões **sem rota correspondente** (Gap G-08).

## 8. Regras de integridade que a migration não impõe

| Regra esperada | Situação real |
|---|---|
| `link_type` ∈ {`gabinete`, `outro_orgao`} | Validada só na aplicação (`ViapRequest`); o banco aceita qualquer texto |
| Exatamente um entre `councilman_id` e `organ_id` preenchido | Validado parcialmente (`required_if`); o banco não impede os dois preenchidos ou os dois vazios |
| `slug` único | **Não garantido** — sem índice único |
| `amount` ≥ 0 | Sem check; e nunca é enviado |
| `reference_month` entre 1 e 12 | Validado na aplicação; sem check no banco |
