# Relatório Estatístico da Ouvidoria — Modelagem

> Estrutura de dados da `v5`. Banco `prefeituras_v5` (container `v5_3f273858-postgres-1`),
> inspecionado em modo **read-only**.

---

## 1. Tabela própria do módulo

### 1.1 `relatorios_estatisticos_ouvidoria`

Colunas exatas (fonte: `\d relatorios_estatisticos_ouvidoria` no PostgreSQL 10):

| # | Coluna | Tipo | Null | Default | Observação |
|---|---|---|---|---|---|
| 1 | `id` | `bigint` | não | `nextval('relatorios_estatisticos_ouvidoria_id_seq'::regclass)` | PK, `bigIncrements` |
| 2 | `ano` | `character varying(191)` | sim | — | Texto puro (não é inteiro nem ano fiscal) |
| 3 | `descricao` | `text` | sim | — | Texto livre do relatório |
| 4 | `arquivo` | `character varying(191)` | sim | — | Caminho relativo `docs/<arquivo>` dentro do disco `public` |
| 5 | `created_at` | `timestamp(0) without time zone` | sim | — | `timestamps()` |
| 6 | `updated_at` | `timestamp(0) without time zone` | sim | — | `timestamps()` |
| 7 | `deleted_at` | `timestamp(0) without time zone` | sim | — | `softDeletes()` |

Índices:

| Nome | Tipo | Coluna |
|---|---|---|
| `relatorios_estatisticos_ouvidoria_pkey` | PRIMARY KEY (btree) | `id` |

**Sem chaves estrangeiras, sem índices únicos, sem views SQL** relacionadas.

Estado atual no banco v5: **0 registros** (`select count(*) from relatorios_estatisticos_ouvidoria` → `0`).
A tabela foi criada (migration aplicada) mas nunca foi populada no ambiente consultado.

Observações de modelagem:

- `ano` é `varchar(191)`, portanto `orderBy('ano','desc')` faz **ordem lexicográfica**
  (`'999'` > `'2025'` lexicograficamente não é verdade) — ver `05-REGRAS-E-PORTABILIDADE.md`.
- A coluna `ano` é **nullable no banco**, embora a aplicação exija `required`.
- `arquivo` guarda `docs/<nome>.pdf`; o link público é `url('storage/' . $arquivo)`, o que
  pressupõe `php artisan storage:link` (o link `public/storage` não existe no repositório).

### 1.2 Migration

`v5/database/migrations/2026_05_27_135849_create_relatorios_estatisticos_ouvidoria_table.php`

```php
public function up()
{
    Schema::create('relatorios_estatisticos_ouvidoria', function (Blueprint $table) {
        $table->bigIncrements('id');

        $table->string('ano')->nullable();
        $table->text('descricao')->nullable();
        $table->string('arquivo')->nullable();

        $table->timestamps();
        $table->softDeletes();
    });
}
```

Não há migrations posteriores que alterem esta tabela (verificado por
`ls v5/database/migrations | grep -i relatorio` → um único arquivo).

### 1.3 Model Eloquent

`v5/app/Http/Model/Prefecture/RelatorioEstatisticoOuvidoria.php`

```php
class RelatorioEstatisticoOuvidoria extends Model
{
    use SoftDeletes;

    protected $table    = 'relatorios_estatisticos_ouvidoria';
    protected $fillable = ['ano', 'descricao', 'arquivo'];
}
```

| Aspecto | Situação |
|---|---|
| Casts | nenhum (`ano` fica string; `created_at`/`deleted_at` são `Carbon` por padrão do Eloquent) |
| Relações | nenhuma — a tabela é isolada |
| `$table` explícita | sim, apesar do nome plural já casar com a convenção |
| SoftDeletes | sim |
| Auditoria (`LogsActivity`) | **não** — sem trilha de alterações |

---

## 2. Tabelas de origem das estatísticas (face portal)

Estas tabelas **não pertencem** ao módulo, mas são sua fonte de dados. São consultadas
diretamente via `DB::table()` e via o model `OmbudsmanRequest`.

### 2.1 `ombudsman_requests`

| Coluna | Tipo | Null | Papel no módulo |
|---|---|---|---|
| `id` | `integer` | não | PK |
| `protocol` | `varchar(191)` | não | aparece mascarado (`masked_protocol`) na listagem |
| `name` | `varchar(191)` | sim | aparece mascarado (`masked_name`); `anonymous = true` → "Anônimo" |
| `gender` | `varchar(191)` | sim | dimensão do gráfico "Por Gênero" |
| `type_id` | `integer` | não | FK → `ombudsman_manifestation_types` (gráfico por tipo) |
| `subject_id` | `integer` | não | FK → `ombudsman_subjects` (agregado por assunto) |
| `status_id` | `integer` | não | FK → `statuses` (1 Em Tramitação, 2 Atendido, 3 Indeferido, 4 Prorrogado) |
| `created_at` | `timestamp(0)` | sim | filtro por ano e ordenação da listagem |
| `updated_at` | `timestamp(0)` | sim | usado nos agregados de prazo (status 1/2/3) |
| `deleted_at` | `timestamp(0)` | sim | A coluna existe, mas o model `OmbudsmanRequest` **não usa o trait `SoftDeletes`** (`v5/app/Http/Model/Prefecture/OmbudsmanRequest.php:15-17`). Consequência: apenas as três consultas `DB::table` (porTipo/porAssunto/porGenero) aplicam `whereNull('deleted_at')`; os contadores de prazo via model e a listagem completa **incluem registros removidos logicamente** |
| demais | — | — | `email`, `phone`, `cpf_cnpj`, `education_level`, `birth_date`, `attachment`, `message`, `response_method_id`, `origin_id`, `anonymous`, `modality`, `address`, `number`, `neighborhood`, `complement`, `reference_point`, `history`, `description`, `mask_description`, `ip`, `browser`, `user_option` |

FKs: `origin_id → ombudsman_origins`, `response_method_id → ombudsman_response_methods`,
`subject_id → ombudsman_subjects`, `type_id → ombudsman_manifestation_types`,
`status_id → statuses`. `protocol` tem **índice único**.

### 2.2 `ombudsman_answers`

| Coluna | Tipo | Null | Papel |
|---|---|---|---|
| `id` | `integer` | não | PK |
| `answer` | `text` | não | texto exibido no modal Ajax |
| `archives_id` | `integer` | sim | FK → `archives` |
| `ombudsman_request_id` | `integer` | não | FK → `ombudsman_requests` |
| `created_at` / `updated_at` / `deleted_at` | `timestamp(0)` | sim | base do cálculo "dentro/fora do prazo" dosstatus Atendido e Indeferido |

### 2.3 Tabelas de dimensão

| Tabela | Colunas relevantes | Uso |
|---|---|---|
| `ombudsman_manifestation_types` | `id`, `title`, `status`, soft deletes | `title` vira rótulo do gráfico "Por Tipo de Manifestação" |
| `ombudsman_subjects` | `id`, `title`, `secretary_id`, `email`, `responsible` | `title` vira rótulo do agregado `$porAssunto` |
| `statuses` | `id`, `name` | 1 Em Tramitação, 2 Atendido, 3 Indeferido, 4 Prorrogado, 5 Aberta, 6 Finalizada, 7 Cancelada |
| `static_pages` | `slug = 'relatorio-estatistico-ouvidoria'` | título, texto e contador `clicks` da página pública |
| `archives` | `static_pages_id`, `highlight`, `view_pdf` | anexos exibidos no bloco de arquivos da página |
| `configurations` | `key = 'layout'`, `key = 'full_list_requests_disabled'` | template do portal e chaveamento da listagem completa |

---

## 3. Views SQL

**Nenhuma** view SQL está associada a este módulo. Confirmado por inspeção de `pg_views` e
pelo código: todas as consultas do módulo usam `DB::table()` ou Eloquent sobre tabelas
base.

---

## 4. Seeds

| Arquivo | O que faz em relação ao módulo |
|---|---|
| `v5/database/seeds/OuvidoriaPaginasLinksSeeder.php` | Cria/atualiza a `static_pages` com `slug = 'relatorio-estatistico-ouvidoria'` (título "Relatório Estatístico de Ouvidoria", texto legal do Art. 30, III, da LAI, `status_create = true`), o `contents` correspondente, os `links` e os `archives` (banners) |
| — | **Não existe** seed para a tabela `relatorios_estatisticos_ouvidoria`; ela nasce vazia |

Trecho do seeder (página estática):

```php
DB::table('static_pages')->updateOrInsert(
    ['slug' => 'relatorio-estatistico-ouvidoria'],
    [
        'title' => 'Relatório Estatístico de Ouvidoria',
        'text' => 'Segue abaixo o relatório da transparência contendo a quantidade de pedidos de informação recebidos,
    bem como informações genéricas sobre os solicitantes (artigo 30, III, da Lei 12.527/2011).',
        'status_create' => true,
        'created_at' => now(),
    ]
);
```

> Observação: o texto do seed fala em "pedidos de informação", texto herdado do relatório
> de LAI; a página da Ouvidoria mostra ao mesmo tempo os dois textos.

---

## 5. Relacionamentos resumidos

```
static_pages (slug=relatorio-estatistico-ouvidoria)
   └── archives (static_pages_id, highlight, view_pdf)      [arquivos exibidos no portal]

ombudsman_manifestation_types ──< ombudsman_requests >── ombudsman_subjects
                                        │  │
                                        │  └──< ombudsman_answers >── archives
                                        └──> statuses

relatorios_estatisticos_ouvidoria   (tabela isolada, sem FK)
```

O model `RelatorioEstatisticoOuvidoria` **não** declara nenhuma relação
(`belongsTo`/`hasMany`); o vínculo com a página pública é feito apenas pela
seleção de `ano` no controller do portal.
