# Fila de Espera SUS — Modelagem de Dados

> Versão analisada: `v5`. Banco `prefeituras_v5` (PostgreSQL 10), leitura apenas.

## 1. Tabela `fila_espera_sus`

Comando de verificação usado (somente leitura):

```bash
docker exec v5_3f273858-postgres-1 psql -U postgres -d prefeituras_v5 -c "\d fila_espera_sus"
```

### 1.1 Colunas

| Coluna | Tipo (PostgreSQL) | Nullable | Default | Origem na migration | Observação |
|---|---|---|---|---|---|
| `id` | `integer` (`increments`) | não | `nextval('fila_espera_sus_id_seq')` | `increments('id')` | PK auto-incremental. |
| `sus_card` | `character varying(191)` | não | — | `string('sus_card')` | Número do cartão SUS. **Dado pessoal de saúde**, guardado em texto claro. |
| `update_date` | `date` | não | — | `date('update_date')` | Data de atualização do registro. |
| `register_date` | `date` | não | — | `date('register_date')` | Data de registro do paciente na fila. |
| `patients_count` | `integer` | não | — | `integer('patients_count')` | Quantidade de pacientes na fila. |
| `specialty` | `character varying(191)` | não | — | `string('specialty')` | Texto livre (ex.: Cardiologia). |
| `procedure` | `character varying(191)` | não | — | `string('procedure')` | Texto livre (ex.: Consulta). |
| `avg_wait_time` | `integer` | não | — | `integer('avg_wait_time')` | Tempo médio de espera em **dias**. |
| `priority_criteria` | `character varying(191)` | não | — | `string('priority_criteria')` | Critério de priorização (texto livre). |
| `contact` | `character varying(191)` | sim | — | `string('contact')->nullable()` | Telefone/e-mail de contato do paciente ou da unidade. |
| `queue_status` | `character varying(191)` | não | — | `string('queue_status')` | Situação da fila (texto livre). |
| `file_path` | `text` | sim | — | `text('file_path')->nullable()` | **JSON** com a lista de anexos: `[{"path": "...", "description": "..."}]`. |
| `is_show` | `boolean` | não | `true` | `boolean('is_show')->default(true)` | Publicação no portal. |
| `slug` | `character varying(191)` | sim | — | `string('slug')->nullable()->unique()` | URL pública do detalhe. |
| `created_at` | `timestamp(0) without time zone` | sim | — | `timestamps()` | |
| `updated_at` | `timestamp(0) without time zone` | sim | — | `timestamps()` | |
| `deleted_at` | `timestamp(0) without time zone` | sim | — | `softDeletes()` | **Soft delete**. |

### 1.2 Índices e constraints

| Nome | Tipo | Coluna(s) |
|---|---|---|
| `fila_espera_sus_pkey` | PRIMARY KEY (btree) | `id` |
| `fila_espera_sus_slug_unique` | UNIQUE CONSTRAINT (btree) | `slug` |

Observações:

- O índice único de `slug` permite múltiplos `NULL` (PostgreSQL), mas o
  controller sempre grava o slug, o que transforma a coluna em effectively
  `NOT NULL` na prática.
- **Não há índices** sobre `specialty`, `priority_criteria`, `is_show`,
  `update_date` ou `sus_card` — as colunas usadas em filtro e ordenação do
  portal.

### 1.3 Soft delete

A tabela possui `deleted_at` e o model usa o trait `SoftDeletes`. O
`DatatableController` filtra explicitamente com `whereNull('deleted_at')`
porque usa `DB::table()` (sem escopo global). O model, usado no portal e no
CMS, já aplica o escopo global automaticamente.

### 1.4 Estado atual dos dados

```sql
SELECT count(*) FROM fila_espera_sus;   -- 0
```

A tabela está **vazia** no banco `prefeituras_v5`: o módulo foi criado
(migration, resource, menu) mas nenhuma prefeitura cadastrou registros.
Isso explica por que a implementação nunca foi exercitada em produção.

## 2. Views SQL

Verificado com:

```sql
SELECT viewname FROM pg_views
WHERE schemaname = 'public'
  AND (viewname ILIKE '%fila%' OR viewname ILIKE '%espera%' OR viewname ILIKE '%sus%');
```

**Resultado: 0 linhas.** Não existe nenhuma view SQL associada ao módulo
(ao contrário de `listagem_obras`, `view_estagiarios` e
`view_concursos_pss`, que servem a outros módulos da v5). Todo o acesso é
feito direto na tabela.

## 3. Relacionamentos

| Lado | Tipo | Coluna | Observação |
|---|---|---|---|
| `fila_espera_sus` → `resources` | referência lógica por slug | `resources.slug = 'fila-espera-sus'` | Sem FK. O vínculo é feito pelo slug do recurso de permissão. |
| `fila_espera_sus` → `menu_orders` | referência lógica por slug | `menu_orders.slug = 'fila-espera-sus'` | Sem FK; usado por `menuClicks()`. |
| `fila_espera_sus` → `activity_log` | polimórfica | `activity_log.subject_type = 'App\Prefecture\FilaEsperaSus'` | Criada pelo `LogsActivity` do model. |
| `fila_espera_sus` → anexos | embutida em `file_path` (JSON) | — | Sem tabela filha; sem integridade referencial. |

**Não há relacionamentos Eloquent declarados**: o model `FilaEsperaSus` não
define `belongsTo`, `hasMany` nem `hasOne`. As colunas `specialty`,
`priority_criteria` e `queue_status` funcionam como domínios livres, sem
tabela de apoio e sem chave estrangeira.

## 4. Migrations

| Migration | Data | Efeito |
|---|---|---|
| `v5/database/migrations/2026_05_28_200000_create_fila_espera_sus_table.php` | 2026-05-28 | Cria `fila_espera_sus` com as 14 colunas + `timestamps` + `softDeletes`; insere `menu_orders` (slug `fila-espera-sus`, `clicks = 0`) e `resources` (nome "Fila de Espera SUS", descrição "Módulo de Fila de Espera SUS") se ainda não existirem. O `down()` remove a tabela e as duas linhas inseridas. |

Trecho real da criação da tabela:

```php
Schema::create('fila_espera_sus', function (Blueprint $blueprint) {
    $blueprint->increments('id');
    $blueprint->string('sus_card');
    $blueprint->date('update_date');
    $blueprint->date('register_date');
    $blueprint->integer('patients_count');
    $blueprint->string('specialty');
    $blueprint->string('procedure');
    $blueprint->integer('avg_wait_time');
    $blueprint->string('priority_criteria');
    $blueprint->string('contact')->nullable();
    $blueprint->string('queue_status');
    $blueprint->text('file_path')->nullable();
    $blueprint->boolean('is_show')->default(true);
    $blueprint->string('slug')->nullable()->unique();
    $blueprint->timestamps();
    $blueprint->softDeletes();
});
```

Observação de arquitetura: a migration mistura **DDL** e **DML** (inserção em
`menu_orders` e `resources`). Não há seeders separados para o módulo.

## 5. Seeds e dados iniciais

Não existe seeder específico do módulo em `v5/database/seeds/`. Os únicos
dados iniciais são os dois `insert` da própria migration (menu e resource).

## 6. Tabelas do entorno (estado no banco v5)

| Tabela | Registro | Campos | Papel no módulo |
|---|---|---|---|
| `resources` | `id = 17`, slug `fila-espera-sus`, nome "Fila de Espera SUS", descrição "Módulo de Fila de Espera SUS" | `id, slug, name, description, status_create, created_at, updated_at, deleted_at` | Recurso de permissão. Não existe `modulo_id` na v5. |
| `menu_orders` | `id = 37`, slug `fila-espera-sus`, `clicks = 0` | — | Contador de acessos incremented por `menuClicks()` no `index` do CMS. |
| `permissions` | 8 verbos: `index`, `create`, `edit`, `delete`, `inactive`, `inactivate`, `restore`, `show` | — | Combinados com o slug do recurso para formar `fila-espera-sus.index`, `fila-espera-sus.create`, `fila-espera-sus.edit`, `fila-espera-sus.delete`. |
| `activity_log` | 0 linhas com `log_name = 'fila-espera-sus'` | `log_name, description, subject_id, subject_type, causer_id, causer_type, properties, created_at, ...` | Trilha de auditoria do model e da listagem do CMS. |

## 7. Model Eloquent

Arquivo: `v5/app/Http/Model/Prefecture/FilaEsperaSus.php`.
Namespace `App\Prefecture` (autoload por `classmap` de `app/` em
`v5/composer.json`).

| Elemento | Valor | Efeito |
|---|---|---|
| `protected $table` | `fila_espera_sus` | — |
| Traits | `AppTrait`, `SoftDeletes`, `LogsActivity` | Soft delete, validações genéricas e auditoria. |
| `$fillable` | `sus_card, update_date, register_date, patients_count, specialty, procedure, avg_wait_time, priority_criteria, contact, queue_status, file_path, is_show, slug` | Mass assignment liberado para o `slug` e o `is_show` (o controller sobrescreve ambos). |
| `$logFillable` | `true` | **Todos** os campos fillable (inclusive `sus_card`) são gravados no `properties` do `activity_log`. |
| `$logName` | `fila-espera-sus` | Filtro do log de atividades. |
| `getDescriptionForEvent()` | "O registro de Fila de Espera SUS ({specialty}) foi {evento}" | Texto do log. |
| `getSusCardMaskedAttribute()` | acessor `sus_card_masked` | Acessor de máscara **não utilizado** em nenhum ponto do sistema (a máscara é recalculada em 4 lugares). |

Não há `$casts` no model: `update_date` e `register_date` são `date` no
banco, mas chegam como string; `is_show` chega como booleano do PostgreSQL;
`file_path` é uma string JSON que precisa ser decodificada manualmente.

## 8. Formato do `file_path`

```json
[
  {"path": "fila-espera-sus/aBcDeFgHiJkLmNoP.pdf", "description": "Listagem"},
  {"path": "fila-espera-sus/OutRoArQuIvO.pdf", "description": ""}
]
```

- O valor salvo é **relativo** ao diretório `public/storage`
  (`public_path('storage/fila-espera-sus')` no upload).
- As views prefixam `storage/` ao montar a URL de download.
- O controller aceita JSON antigo no formato string simples
  (`["fila-espera-sus/x.pdf"]`) e normaliza para o formato com descrição
  (ver `update()` e `destroyArquivo()`).
