# Usuários — Modelagem de Dados

> Estrutura de dados do módulo de usuários, perfis, permissões e recursos.
> Fonte: schema real do banco `prefeituras_v5` (container `v6_6f22e1bd-postgres-1`).

---

## 1. Tabelas principais

### 1.1 `users`

Criada por `v6/database/migrations/2018_03_16_184826_migration_cartalyst_sentinel.php`
(migração do Sentinel).

| Coluna | Tipo | Null | Default | Observação |
|---|---|---|---|---|
| `id` | integer | não | `nextval('users_id_seq')` | PK |
| `name` | varchar(191) | sim | — | nome do operador |
| `email` | varchar(191) | **não** | — | **único** (`users_email_unique`) — é o login |
| `password` | varchar(191) | **não** | — | hash do Sentinel |
| `remember_token` | varchar(100) | sim | — | |
| `permissions` | text | sim | — | JSON de permissões **por usuário** (não usado pelo CRUD) |
| `last_login` | timestamp(0) | sim | — | |
| `created_at` / `updated_at` | timestamp(0) | sim | — | |
| `deleted_at` | timestamp(0) | sim | — | `SoftDeletes` |

Indexes: `users_pkey` (PK), `users_email_unique` (UNIQUE em `email`).

FKs que apontam para `users(id)`: `dashboard_widget_instances`,
`diarios` (4 colunas), `ombudsman_request_logs`, `persons`,
`service_letter_configs`, `service_letter_subjects` (2), `service_letters`,
`supports`.

### 1.2 `roles`

Criada pela mesma migração do Sentinel.

| Coluna | Tipo | Null | Default | Observação |
|---|---|---|---|---|
| `id` | integer | não | seq | PK |
| `slug` | varchar(191) | **não** | — | **único** (`roles_slug_unique`) |
| `name` | varchar(191) | **não** | — | rótulo do perfil |
| `permissions` | text | sim | — | **JSON** `{"recurso.acao": true, ...}` |
| `created_at` / `updated_at` | timestamp(0) | sim | — | |
| `deleted_at` | timestamp(0) | sim | — | `SoftDeletes` |

> **Não existe a tabela `role_permissions`.** As permissões de um perfil são
> um blob JSON na própria linha de `roles`. Isso impede consultas do tipo
> "quais perfis têm a permissão X" sem extrair o JSON em PHP.

### 1.3 `role_users` (pivô)

| Coluna | Tipo | Null | Default |
|---|---|---|---|
| `user_id` | integer | não | — |
| `role_id` | integer | não | — |
| `created_at` / `updated_at` | timestamp(0) | sim | — |
| `deleted_at` | timestamp(0) | sim | — |

PK composta `(user_id, role_id)`. **Sem FK declarada** para `users`/`roles`
(apenas a PK). A model `App\Admin\User::roles()` usa
`belongsToMany(Cartalyst\Sentinel\Roles\EloquentRole::class, 'role_users', 'user_id', 'role_id')->withTimestamps()`.

### 1.4 `permissions` (catálogo atômico)

Criada por `v6/database/migrations/2018_03_16_190613_create_permissions_table.php`.

| Coluna | Tipo | Null |
|---|---|---|
| `id` | integer | não |
| `slug` | varchar(191) | não |
| `name` | varchar(191) | não |
| `description` | varchar(191) | não |
| `created_at` / `updated_at` | timestamp(0) | sim |
| `deleted_at` | timestamp(0) | sim |

Conteúdo atual (9 linhas, via `v6/database/seeds/PermissionsTableSeeder.php`):

| id | slug | name | description |
|---|---|---|---|
| 2 | `index` | Listar | Permissão para visualizar uma lista de dados |
| 3 | `create` | Cadastrar | Permissão para cadastrar novos dados |
| 4 | `edit` | Editar | Permissão para editar dados |
| 5 | `delete` | Deletar | Permissao para deleta dados definitivamente |
| 6 | `inactive` | Listar Inativos | Permissão para listar usuários inativos |
| 7 | `inactivate` | Inativar | Permissão para inativar um usuário |
| 8 | `restore` | Ativar | Permissão para Ativar um usuário |
| 9 | `show` | Detalhes | Permissão para visualizar detalhes |
| 10 | `publish` | Publicar | Permissão para publicar oficialmente um registro |

> Os ids observados na base vão de 2 a 10; o `id = 1` não existe (a sequência
> `permissions_id_seq` foi criada e consumida em um ambiente anterior).

### 1.5 `resources` (linhas da matriz)

Criada por `v6/database/migrations/2018_03_16_190404_create_resources_table.php`;
`modulo_id` adicionado por `v6/database/migrations/2024_11_05_104026_add_modulo_id_to_resources_table.php`.

| Coluna | Tipo | Null | Default |
|---|---|---|---|
| `id` | integer | não | seq |
| `slug` | varchar(191) | não | — |
| `name` | varchar(191) | não | — |
| `description` | varchar(191) | não | — |
| `status_create` | boolean | sim | — |
| `created_at` / `updated_at` | timestamp(0) | sim | — |
| `deleted_at` | timestamp(0) | sim | — |
| `modulo_id` | integer | sim | — | **FK** → `modulos(id)` |

Recursos do módulo `usuarios` (modulo_id 11):

| id | slug | name |
|---|---|---|
| 5 | `usuario` | Usuários |
| 6 | `perfil` | Perfis |
| 7 | `permissao` | Permissão |
| 8 | `recurso` | Recursos |

Total na base: 79 recursos; 4 pertencem ao módulo `usuarios`.

### 1.6 `modulos`

Criada por `v6/database/migrations/2024_11_01_150206_create_modulos_table.php`.

| Coluna | Tipo | Null |
|---|---|---|
| `id` | integer | não |
| `name` | varchar(191) | não |
| `slug` | varchar(191) | não |
| `status` | boolean | sim |
| `created_at` / `updated_at` | timestamp(0) | sim |

**58 linhas** na base (o comentário do contrato fala em 56 — ver
`08-GAPS-E-DIVIDAS.md`). O módulo `usuarios` é o id 11, `status = true`.

`modulos` é referenciada por `menu_orders.modulo_id` e `resources.modulo_id`.

### 1.7 `activations` (estado do usuário)

| Coluna | Tipo | Null | Default |
|---|---|---|---|
| `id` | integer | não | seq |
| `user_id` | integer | não | — |
| `code` | varchar(191) | não | — |
| `completed` | boolean | não | `0` |
| `completed_at` | timestamp(0) | sim | — |
| `created_at` / `updated_at` | timestamp(0) | sim | — |

> Não tem `deleted_at`. A "inativação" de um usuário é a **remoção** do
> registro de ativação (`Activation::remove`).

### 1.8 `menu_orders` (contador de cliques do menu)

| Coluna | Tipo | Null | Default |
|---|---|---|---|
| `id` | integer | não | seq |
| `slug` | varchar(191) | sim | — |
| `clicks` | integer | **não** | — |
| `created_at` / `updated_at` | timestamp(0) | sim | — |
| `deleted_at` | timestamp(0) | sim | — |
| `modulo_id` | integer | sim | — | FK → `modulos(id)` |

Linha relevante: `id = 25`, `slug = 'usuario'`, `modulo_id = 11`.

---

## 2. Views SQL

Todas criada por migrations e consumidas por `Prefecture\DatatableController`
via `DB::table(...)`.

| View | Origem | Colunas | Consumida em |
|---|---|---|---|
| `listar_usuarios` | `users` LEFT JOIN `activations` | `value`, `name`, `id`, `email`, `data_criacao`, `status` | `GET /cms/datatable/listar_usuarios` |
| `listar_todos_usuarios` | idem, sem filtro de `activations` | idem | `GET /cms/datatable/listar_todos_usuarios` |
| `listar_usuarios_inativos` | idem, `a.user_id IS NULL` | idem | `GET /cms/datatable/listar_usuarios_inativos` |
| `perfis` | `roles` | `value`, `name`, `id` | `GET /cms/datatable/perfis` |
| `permissoes` | `permissions` | `value`, `titulo`, `descricao`, `data_criacao`, `id` | `GET /cms/datatable/permissoes` |
| `recursos` | `resources` | `value`, `titulo`, `descricao`, `data_criacao`, `id` | `GET /cms/datatable/recursos` |

Definição de `listar_usuarios` (extraída do banco):

```sql
SELECT row_number() OVER (ORDER BY u.created_at DESC) AS value,
    u.name, u.id, u.email,
    to_char(u.created_at::timestamp with time zone, 'DD/MM/YYYY às HH24:MI'::text) AS data_criacao,
    CASE WHEN a.user_id IS NOT NULL THEN true ELSE false END AS status
   FROM users u
     LEFT JOIN activations a ON a.user_id = u.id
  WHERE u.deleted_at IS NULL AND a.user_id IS NOT NULL
  ORDER BY u.created_at DESC;
```

> `listar_todos_usuarios` e `listar_usuarios_inativos` diferem de
> `listar_usuarios` apenas pelo predicado sobre `activations`. O controller
> `UserController@all()` consulta Eloquent (`has('activations')`) e entrega
> `$users` para `admin.user.index`, mas a view **ignora** `$users` e carrega a
> DataTable a partir da view SQL — o filtro "Todos" da UI, portanto, mostra
> exatamente os mesmos usuários de "Ativos".

---

## 3. Relacionamentos (Eloquent)

```
User (App\Admin\User)
 ├── roles()          BelongsToMany  -> role_users (user_id, role_id)
 ├── persistences()   HasMany        -> persistences
 ├── activations()    HasMany        -> activations
 ├── reminders()      HasMany        -> reminders
 ├── throttle()       HasMany        -> throttle
 └── person()         (via App\Admin\Person::$personModel = Person::class)

Role (App\Admin\Role)
 └── (herda EloquentRole do Sentinel) -> permissions via PermissibleTrait

Resource (App\Admin\Resource)
 └── modulo()         BelongsTo      -> modulos(id)

ManualArea -> Archive:  hasOne  (usado pelo módulo manual-do-sistema)
```

---

## 4. Migrations relacionadas

| Migration | Efeito |
|---|---|
| `v6/database/migrations/2018_03_16_184826_migration_cartalyst_sentinel.php` | `activations`, `persistences`, `reminders`, `roles`, `role_users`, `throttle`, `users` |
| `v6/database/migrations/2018_03_16_190404_create_resources_table.php` | `resources` |
| `v6/database/migrations/2018_03_16_190613_create_permissions_table.php` | `permissions` |
| `v6/database/migrations/2023_04_12_094342_create_view_to_resources.php` | view `recursos` |
| `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` | `modulos` + 58 inserts (inclui o slug `logs-do-sistema` **duas vezes**) |
| `v6/database/migrations/2024_11_04_094157_add_modulo_id_to_menu_order_table.php` | `menu_orders.modulo_id` |
| `v6/database/migrations/2024_11_05_104026_add_modulo_id_to_resources_table.php` | `resources.modulo_id` (FK) |

---

## 5. Seeds

| Seed | Caminho | O que popula |
|---|---|---|
| `RolesTableSeeder` | `v6/database/seeds/RolesTableSeeder.php` | 6 perfis: `super-admin`, `administrador` (com o JSON gigante de permissões), `basico`, `licitacoes`, `Lai`, `covid-19` |
| `PermissionsTableSeeder` | `v6/database/seeds/PermissionsTableSeeder.php` | as 9 permissões atômicas |
| `ResourcesTableSeeder` | `v6/database/seeds/ResourcesTableSeeder.php` | catálogo de recursos |
| `UserTableSeeder` | `v6/database/seeds/UserTableSeeder.php` | cria `suporte@maxima.inf.br` se ainda não existir |
| `RoleUsersTableSeeder` | `v6/database/seeds/RoleUsersTableSeeder.php` | vínculo usuário ↔ perfil |
| `ManualDoSistemaResourceSeeder` | `v6/database/seeds/ManualDoSistemaResourceSeeder.php` | cria o recurso `manual-usuario` (modulo_id 8) — **não presente na base atual** |

Estado atual na base: 6 perfis, 9 permissões, 79 recursos, 1 usuário,
0 linhas em `role_users`.

---

## 6. Observações de modelagem para a v7

1. **`roles.permissions` deve virar tabela.** A chave de autorização
   (`{recurso}.{acao}`) é a coluna mais consultada do sistema e hoje exige
   extração de JSON em PHP.
2. **`resources` + `permissions` + `modulos` podem ser unificados**: `resources`
   já é "recurso = módulo" e `permissions` é "ação"; a v7 pode usar
   `spatie/laravel-permission` (`permissions`, `roles`, `model_has_permissions`,
   `role_has_permissions`).
3. **`users.permissions`** (permissão por usuário) não é exercitado por
   nenhum controller do módulo — pode ser removido ou documentado como
   "concessão individual".
4. **A view `listar_usuarios`** repete a mesma lógica três vezes
   (`listar_usuarios`, `listar_todos_usuarios`, `listar_usuarios_inativos`);
   na v7 isso vira um `UserResource` com filtro `whereHas('activations')`.
