---
id: MOD-INFRAESTRUTURA-GESTAOPERMISSOES
tipo: modulo
onda: 4
status: concluido
unidades: [MOD-INFRAESTRUTURA-GESTAOPERMISSOES]
fontes:
  - V2/Modules/Infraestrutura/GestaoPermissoes/
depende_de: [CORE-AUTH, CORE-CMS, CORE-BOOT, SVC-ACTIVITYLOGGER]
consumidores: []
criticidade: alta
dono: Infra
---

# Infraestrutura/GestaoPermissoes

> Console de identidade e autorização do CMS: cria/edita usuários Sentinel, cria/edita
> perfis `V2Role` e define a matriz `módulo → none|read|write`. Não é apenas um CRUD:
> alterar a matriz de um perfil muda **imediatamente** o que todos os usuários daquele
> perfil veem e podem fazer, sem materializar permissões no usuário e sem cache.

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Um operador com `GestaoPermissoes:write` cria/edita usuários e perfis, e a matriz resultante controla sidebar, acesso às rotas CMS e ações de escrita de todos os módulos V2. |
| Entrada | 11 rotas `GET/POST /cms/permissoes/*`; `name`, `email`, `password`, `password_confirmation`, `v2_role_id`, `unidade_regional_id`, `is_active`; `permissions[<ModuleKey>]` = `none|read|write`; `slug`/`name` do perfil. |
| Saída | 6 telas HTML; linhas em `users` e `v2_roles`; pivôs Sentinel (`role_users`, `activations`); `activity_log`; `302` com flash; `403` com mensagem exata. |
| Efeitos colaterais | Permissões passam a valer na próxima requisição de cada usuário; ativar cria/completa ativação Sentinel; desativar remove ativações; exclusão de perfil é soft delete e só ocorre sem usuários vinculados; auditoria de perfil/usuário. |
| Particularidade central | A matriz é um JSONB (`v2_roles.permissions_v2`) lido a cada request; o bloqueio de `super-admin` protege apenas o **slug do V2Role**, não cria o papel, e o `super-admin` real do Sentinel não é semeado em lugar algum. |
| Como se integra | Rotas `v2.gestaopermissoes.*`; views `v2-gestaopermissoes`; `PermissionService` (Core) consome `V2Role`; `V2PermissionMiddleware`, `SidebarComposer` e `v2_can()` aplicam o resultado. |
| Se removido, quebra | Toda a administração de usuários/perfis do CMS; `PermissionService` continua lendo `v2_roles`, mas não há UI para criar/editar papéis nem usuários. |
| Superfície pública | 2 controllers (11 actions), `PermissionService` (10 métodos), `V2Role`, `ModuleProvider::getConfig()`, 11 rotas, 6 blades. |

## 1. Efeito

### 1.1 O que o usuário consegue fazer

- **Listar usuários** com busca por nome/e-mail, paginados de 15 em 15, exibindo perfil
  V2, unidade regional, status ativo/inativo e ação “Perfil”
  (`GestaoPermissoesPermissionsController.php:36-67`; `Views/permissions/index.blade.php:46-127`).
- **Criar usuário** com nome, e-mail, senha confirmada, perfil V2 opcional e unidade
  regional opcional (`GestaoPermissoesPermissionsController.php:255-363`).
- **Editar usuário**: nome, e-mail, senha opcional, perfil, regional e switch ativo
  (`:136-250`; `Views/permissions/edit.blade.php:38-108`).
- **Listar perfis** com busca por nome/slug, contagem de usuários e paginação 15
  (`GestaoPermissoesRolesController.php:36-57`; `Views/roles/index.blade.php:69-127`).
- **Criar/editar perfil** com nome, slug e uma linha por módulo com três níveis de
  acesso (`Views/roles/create.blade.php:79-137`; `Views/roles/edit.blade.php:79-143`).
- **Excluir perfil** somente quando não há usuários vinculados; o botão fica `disabled`
  no cliente e o serviço lança exceção no servidor
  (`Views/roles/index.blade.php:98-103`; `PermissionService.php:199-227`).

### 1.2 O que muda no sistema

| Efeito | Onde é produzido | Fonte |
|---|---|---|
| `INSERT/UPDATE` em `users` (`name`, `email`, `password`, `v2_role_id`, `unidade_regional_id`) | controllers de permissões | `GestaoPermissoesPermissionsController.php:202-213,320-331` |
| `INSERT` em `v2_roles` com `permissions_v2` JSON sanitizado | `PermissionService::createRole` | `V2/Core/Services/PermissionService.php:132-154` |
| `UPDATE` de nome/slug/matriz do perfil | `PermissionService::updateRole` | `PermissionService.php:163-190` |
| `deleted_at` no perfil (soft delete) | `PermissionService::deleteRole` | `PermissionService.php:199-227` |
| Ativação Sentinel criada e completada ao marcar “Usuário Ativo” | `sentinel.activations->create/complete` | `GestaoPermissoesPermissionsController.php:216-228` |
| Ativações removidas ao desmarcar “Usuário Ativo” | `sentinel.activations->remove` | `:225-227` |
| Role Sentinel `admin` criado/vinculado para permitir login | `ensureSentinelRole` | `:373-400` |
| `activity_log` `created`/`updated` de usuário e perfil, `deleted` de perfil | `ActivityLoggerService` | `:232-240,340-347`; `PermissionService.php:144-151,180-187,211-218` |
| Efeito imediato no menu e nas rotas de todos os usuários do perfil | `SidebarComposer`, `V2PermissionMiddleware`, `v2_can()` | `SidebarComposer.php:50-66`; `V2PermissionMiddleware.php:79-127` |

Nenhum e-mail, notificação, cache, índice de busca ou fila é disparado.

**O que acontece com os usuários existentes.** Alterar a matriz de um perfil não
reescreve nada em `users`: o vínculo é só `v2_role_id`, e `PermissionService` relê
`v2_roles.permissions_v2` a cada request (`PermissionService.php:71-103`). Consequências
diretas:

| Mudança | Efeito para quem já está vinculado |
|---|---|
| `read` → `write` | passa a ver ações de escrita e a conseguir POST na próxima requisição, sem novo login. |
| `write` → `read` | perde as ações de escrita imediatamente; POSTs passam a ser bloqueados pelo middleware e por `v2_can`. |
| `*` → `none` | o módulo some da sidebar e as rotas passam a redirecionar com “Acesso negado. Seu perfil não possui permissão para acessar o módulo: …”. |
| Perfil excluído (soft delete) | `V2Role::find` não encontra mais o registro → `getUserPermissions` devolve `[]` → todos os módulos ficam `none`; o usuário continua logando (role Sentinel intacto). |
| Troca de `v2_role_id` | efeito imediato na próxima requisição; não há logout forçado nem invalidação de sessão. |
| Desativação (`is_active=0`) | `activations->remove` apaga as ativações; um novo login falha com “Acesso negado. O seu usuário está desativado.” (`LoginCmsController.php:61-69`), mas uma sessão já aberta não é encerrada por este módulo. |
| E-mail de suporte | a desativação é ignorada; a conta permanece ativa mesmo com o switch desmarcado. |

### 1.3 Contrato observável (entrada → saída)

| Entrada | Resultado |
|---|---|
| `GET /cms/permissoes/permissions` autenticado com acesso ao módulo | `200` com a lista paginada de usuários. |
| `GET /cms/permissoes/roles` autenticado com acesso ao módulo | `200` com a lista paginada de perfis. |
| Criar perfil sem nome | `O nome do perfil é obrigatório.` (`GestaoPermissoesRolesController.php:91-98`). |
| Criar perfil sem slug | `O slug identificador é obrigatório.` (`:96`). |
| Criar perfil com slug repetido | `Este slug já está sendo utilizado por outro perfil.` (`:97`). |
| Criar perfil com slug `super-admin` (qualquer caixa/espaços) | `302` back + `O perfil super-admin é reservado e não pode ser criado manualmente.` + `Log::warning` (`:100-106`). |
| Renomear perfil para `super-admin` | `302` back + `O slug super-admin é reservado e não pode ser atribuído manualmente.` (`:188-194`). |
| Editar perfil inexistente | `302` lista + `Perfil não encontrado.` (`:150-155`). |
| Excluir perfil com usuários | `302` lista + `Não é possível excluir um perfil que possui usuários vinculados.` (`PermissionService.php:206-209`; `GestaoPermissoesRolesController.php:248-251`). |
| Excluir perfil sem usuários | `302` lista + `Perfil de acesso excluído com sucesso.` (`:241-243`). |
| Atualizar perfil válido | `302` lista + `Perfil de acesso atualizado com sucesso.` (`:216-217`). |
| Criar usuário sem senha | `A senha é obrigatória.` (`GestaoPermissoesPermissionsController.php:280-298`). |
| Senha com menos de 6 caracteres | `A senha deve possuir pelo menos 6 caracteres.` (`:292`). |
| Confirmação diferente | `A confirmação de senha não coincide.` (`:293`). |
| E-mail já cadastrado | `Este e-mail já está cadastrado no sistema.` (`:290`). |
| Perfil inexistente no POST | `O Perfil de Acesso selecionado não existe no sistema.` (`:295`). |
| Regional inexistente no POST | `A Unidade Regional selecionada não existe no sistema.` (`:297`). |
| Criar usuário válido | `302` lista + `created_item` tipo `Usuário` e link Sentinel `admin` garantido (`:336-355`). |
| Editar usuário inexistente | `302` lista + `Usuário não encontrado.` (`:197-200`). |
| Atualizar usuário válido | `302` lista + `Usuário atualizado com sucesso.` (`:242-243`). |
| Desativar usuário (`is_active` ausente/0) | Remove ativações; próximo login falha com `Acesso negado. O seu usuário está desativado.` (`:225-227`; `LoginCmsController.php:61-69`). |
| Desativar o e-mail de suporte | Ignorado: a lógica de ativação é pulada (`:216`). |
| Qualquer action de escrita sem `GestaoPermissoes:write` | `403` com uma das mensagens “Apenas super-administradores…” (`:138-139,166-167,257-258,275-276`; roles `:66-67,87-88,146-147,175-176,234-235`). |

### 1.4 O que este módulo NÃO faz

- **Não cria roles Sentinel** além de garantir `admin` para login; o RBAC V2 é paralelo
  ao Sentinel (`ensureSentinelRole`; `V2/Core/Middleware/CmsAuthenticate.php:30-41`).
- **Não define permissões por usuário**: apenas um `v2_role_id`; não há tabela pivô
  usuário↔permissão.
- **Não apaga usuários**: não existe action `destroy` de usuário; só ativa/desativa.
- **Não envia e-mail de boas-vindas** nem de redefinição de senha.
- **Não valida a força da senha** além de `min:6` + confirmação.
- **Não invalida sessões existentes** ao desativar ou trocar perfil; o efeito em uma
  sessão aberta depende da próxima requisição e do middleware.
- **Não protege o próprio módulo contra auto-bloqueio**: um `write` pode remover o
  próprio `write` do perfil e perder acesso na requisição seguinte.

## 2. Abordagem

### 2.1 Estratégia em uma página

Dois controllers finos sobre três peças do Core: `PermissionService` (regras e
persistência de `V2Role`), `V2Role` (model com UUIDv4 e cast de array) e `App\Admin\User`
(usuário Sentinel). A matriz de permissões é montada a partir de
`app('v2.modules.configs')`, que contém todos os `ModuleProvider` descobertos no boot.
O controller remove módulos `restricted` antes de gravar; o serviço sanitiza os valores
válidos. Não há cache: a cada request o middleware consulta `v2_roles`.

```mermaid
flowchart TD
  A[POST /cms/permissoes/roles] --> B[v2_can GestaoPermissoes write]
  B --> C[validate name, slug]
  C --> D{slug == super-admin?}
  D -->|sim| E[flash error + Log::warning]
  D -->|não| F[remove módulos restricted de permissions]
  F --> G[PermissionService::createRole]
  G --> H[sanitizePermissions: none/read/write]
  H --> I[(v2_roles.permissions_v2 JSONB)]
  I --> J[activity_log created]
  J --> K[redirect index]
  I -. próxima request .-> L[V2PermissionMiddleware / SidebarComposer / v2_can]
```

### 2.2 Mecânica passo a passo

1. **Boot**: `V2ServiceProvider` varre `V2/Modules/Infraestrutura/GestaoPermissoes`,
   registra o provider, injeta `group='Infraestrutura'`, o namespace de views
   `v2-gestaopermissoes` e as rotas CMS (`V2/Core/Providers/V2ServiceProvider.php:90-128,303-307`).
2. **Autenticação**: `CmsAuthenticate` exige Sentinel e ao menos um role Sentinel
   (`V2/Core/Middleware/CmsAuthenticate.php:18-44`).
3. **Autorização de módulo**: `V2PermissionMiddleware` casa
   `['cms/permissoes/permissions*', 'cms/permissoes/roles*']` e aplica `none/read/write`
   (`V2PermissionMiddleware.php:40-48,79-127`).
4. **Listagem de perfis**: query em `V2Role` com busca `name/slug`, `paginate(15)`;
   para cada perfil, `getAssignedUsersCount` executa um `COUNT` em `users`
   (`GestaoPermissoesRolesController.php:36-57`; `PermissionService.php:235-238`).
5. **Formulários de perfil**: `$modules = app('v2.modules.configs')` filtrado por
   `!isset($config['restricted'])`; cada linha renderiza `permissions[<moduleKey>]`
   com três radios (`roles/create.blade.php:93-134`; `roles/edit.blade.php:93-140`).
6. **Criação/edição de perfil**: valida nome/slug; bloqueia `super-admin`; remove chaves
   `restricted` de `$data['permissions']`; chama `createRole`/`updateRole`
   (`GestaoPermissoesRolesController.php:91-121,179-209`).
7. **Persistência do perfil**: `sanitizePermissions` mantém apenas valores em
   `['none','read','write']`; `V2Role::create/update` grava o array no cast JSONB;
   `activity_log` `created`/`updated` (`PermissionService.php:132-190,246-255`).
8. **Listagem de usuários**: query em `App\Admin\User`, busca `name/email`,
   `paginate(15)`; por usuário, busca o `V2Role` (N+1), verifica ativação Sentinel e
   resolve o rótulo da regional em uma query batch (`GestaoPermissoesPermissionsController.php:36-101`).
9. **Criação de usuário**: valida; `Sentinel::registerAndActivate`; corrige `name`/email;
   grava `v2_role_id`/`unidade_regional_id`; `ensureSentinelRole`; `activity_log` `created`
   (`:273-363`).
10. **Edição de usuário**: valida; atualiza campos; se senha preenchida, `bcrypt`;
    se `is_active`, cria/completa ativação ou remove; audita `updated`
    (`:164-250`).
11. **Exclusão de perfil**: `getAssignedUsersCount > 0` → exceção; senão audita `deleted`
    e faz soft delete (`PermissionService.php:199-227`).

### 2.3 Decisões de design e por quê

- **RBAC paralelo ao Sentinel** `[C]`: o login exige um role Sentinel (`admin`), mas a
  autorização de módulo usa `users.v2_role_id → v2_roles.permissions_v2`
  (`CmsAuthenticate.php:30-41`; `PermissionService.php:71-103`).
- **Matriz como JSONB no perfil** `[C]`: evita tabela de permissões e permite adicionar
  módulos sem migração; o custo é não ter FK/integridade referencial para as chaves
  (`2026_06_01_000005_create_v2_roles_and_add_role_id_to_users_table.php:27-35`).
- **Filtro de módulos `restricted` em dois pontos** `[C]`: some do formulário e é
  removido do POST mesmo que forjado (`GestaoPermissoesRolesController.php:71-74,109-119,157-161,197-207`).
- **Bloqueio de `super-admin` no controller** `[C]`: a regra é textual
  (`strtolower(trim($slug)) === 'super-admin'`) e não existe no serviço; qualquer
  importação direta no banco pode criar o slug (`:100-106,188-194`).
- **Ativação via Sentinel** `[C]`: “ativo” não é uma coluna de `users`; é a existência
  de `activations.completed` (`GestaoPermissoesPermissionsController.php:61,216-227`).
- **Sem transação no controller de usuário** `[I]`: `registerAndActivate` + `save` +
  `ensureSentinelRole` + auditoria são passos sequenciais; uma falha após o registro
  deixa usuário parcial (o catch só devolve flash de erro).

### 2.4 Trechos de código pivô

```php
// V2/Core/Services/PermissionService.php:40-63
// Suporte e Sentinel super-admin passam; write implica read; 'none' bloqueia.
public function hasPermission($user, string $moduleKey, string $action): bool
{
    if (!$user) {
        return false;
    }
    if ($user->email === config('v2.support.email') || $user->inRole('super-admin')) {
        return true;
    }
    $permissions = $this->getUserPermissions($user);
    $userPermission = $permissions[$moduleKey] ?? 'none';
    if ($userPermission === 'write') {
        return true;
    }
    if ($userPermission === 'read' && $action === 'read') {
        return true;
    }
    return false;
}
```

O `inRole('super-admin')` consulta a tabela Sentinel `roles`, **não** `v2_roles`; é
diferente do slug protegido no CRUD de perfis.

```php
// V2/Modules/Infraestrutura/GestaoPermissoes/Controllers/GestaoPermissoesRolesController.php:100-121
// O bloqueio do slug reservado e a remoção dos módulos restritos antes de persistir.
if (strtolower(trim($request->input('slug'))) === 'super-admin') {
    Log::warning('[V2 > CONTROLLER > Roles] Tentativa de criação de perfil super-admin bloqueada.');
    return redirect()->back()
        ->with('error', 'O perfil super-admin é reservado e não pode ser criado manualmente.')
        ->withInput();
}
try {
    $data = $request->all();
    $modules = app()->bound('v2.modules.configs') ? app('v2.modules.configs') : [];
    $restrictedKeys = array_keys(array_filter($modules, function ($config) {
        return isset($config['restricted']);
    }));
    if (!empty($restrictedKeys) && isset($data['permissions'])) {
        foreach ($restrictedKeys as $key) {
            unset($data['permissions'][$key]);
        }
    }
    $this->permissionService->createRole($data);
```

O `$request->all()` leva também `_token`, `name` e `slug`; `createRole` só usa
`name`, `slug` e `permissions`, então o resto é ignorado.

```php
// V2/Core/Services/PermissionService.php:246-255
// A sanitização é a única barreira de valores; chaves de módulo não são validadas.
private function sanitizePermissions(array $permissions): array
{
    $sanitized = [];
    foreach ($permissions as $module => $val) {
        if (in_array($val, ['none', 'read', 'write'])) {
            $sanitized[$module] = $val;
        }
    }
    return $sanitized;
}
```

Um POST pode gravar qualquer chave de módulo, inclusive uma que não existe; ela fica
inerte até um provider futuro usar o mesmo nome. Valores fora do enum são descartados.

```php
// V2/Core/Services/PermissionService.php:71-103
// A leitura por request: sem v2_role_id, perfil removido ou matriz vazia => [].
$v2RoleId = $user->v2_role_id ?? null;
if (empty($v2RoleId)) { return []; }
try {
    $role = V2Role::find($v2RoleId);
    if (!$role) { return []; }
    $permissionsVal = $role->permissions_v2;
    if (empty($permissionsVal)) { return []; }
    if (is_array($permissionsVal)) {
        return $permissionsVal;
    }
    if (is_string($permissionsVal)) {
        return json_decode($permissionsVal, true) ?: [];
    }
} catch (\Exception $e) {
    // ... Log::error('[V2 > SERVICE > Permissions] Erro ao carregar permissões...')
}
return [];
```

Não há cache: cada chamada faz `V2Role::find`. Como `V2Role` usa `SoftDeletes`, um
perfil excluído deixa de ser encontrado e todos os seus usuários ficam sem permissões.

```php
// V2/Modules/Infraestrutura/GestaoPermissoes/Controllers/GestaoPermissoesPermissionsController.php:216-227
// "Ativo" é derivado da ativação Sentinel; desativar remove as ativações.
if ($user->email !== config('v2.support.email')) {
    $activations = app('sentinel.activations');
    if (filter_var($request->input('is_active'), FILTER_VALIDATE_BOOLEAN)) {
        $activation = $activations->create($user);
        $activations->complete($user, $activation->code);
        // Garante que o usuário possui o role Sentinel necessário para login
        $this->ensureSentinelRole($user);
        Log::info('[V2 > Permissions] Usuário ativado: ' . $user->email);
    } else {
        $activations->remove($user);
        Log::info('[V2 > Permissions] Usuário desativado: ' . $user->email);
    }
}
```

O guard pelo e-mail de suporte impede auto-desativação, mas não impede que outro
`write` altere o e-mail do suporte antes (o e-mail é editável).

### 2.5 Algoritmos, consultas e custos

- Lista de perfis: 1 query paginada + 1 `COUNT` por perfil (N+1) → 16 queries por página
  de 15 (`GestaoPermissoesRolesController.php:49-54`).
- Lista de usuários: 1 query paginada + 1 `V2Role::find` por usuário (N+1) + 1 query
  batch de regionais + 1 `activations->completed` por usuário (possivelmente N+1)
  (`GestaoPermissoesPermissionsController.php:50-64`).
- Escrita de perfil: 1 `SELECT` (update) + 1 `UPDATE` + auditoria; sem transação.
- `getAssignedUsersCount` não filtra ativos nem soft-deleted, apesar do docblock dizer
  “usuários ativos” (`PermissionService.php:230-238`).
- Sem índices específicos em `users.v2_role_id` além do índice da migration; `COUNT`
  usa esse índice quando presente (`2026_06_01_000005...:38-45`).

## 3. Particularidades

### 3.1 O que só existe aqui

- **É a única interface que escreve `v2_roles` e a matriz `permissions_v2`**; o
  `PermissionService` é o único leitor em runtime.
- **A matriz é montada a partir dos providers descobertos**, então a tela muda de
  conteúdo conforme os módulos instalados; não há lista fixa de módulos.
- **O conceito de “ativo” é uma ativação Sentinel**, não um campo booleano; a tela
  traduz `activations.completed` (`GestaoPermissoesPermissionsController.php:61,150`).
- **O bloqueio `super-admin` é textual e local ao controller**, não uma invariante de
  banco nem do serviço (`GestaoPermissoesRolesController.php:100-106,188-194`).

### 3.2 Invariantes e regras não óbvias

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| RN-GESTAOPERMISSOES-01 | Só valores `none`, `read`, `write` são persistidos | Qualquer outro valor é descartado silenciosamente | `PermissionService.php:246-255` |
| RN-GESTAOPERMISSOES-02 | Um perfil não pode ser excluído com usuários vinculados | Exceção com mensagem, redirecionada como flash `error` | `PermissionService.php:206-209` |
| RN-GESTAOPERMISSOES-03 | O slug `super-admin` não pode ser criado nem atribuído por edição | Bloqueio case-insensitive com `trim` | `GestaoPermissoesRolesController.php:101,189` |
| RN-GESTAOPERMISSOES-04 | `write` implica `read` na checagem de permissão | Um perfil `write` acessa GET e POST | `PermissionService.php:54-59` |
| RN-GESTAOPERMISSOES-05 | Sem `v2_role_id`, perfil removido ou matriz vazia, o usuário tem `none` em tudo | Sidebar vazia e rotas de módulo bloqueadas | `PermissionService.php:75-89` |
| RN-GESTAOPERMISSOES-06 | Módulos com `restricted` nunca entram na matriz | Não aparecem no formulário e são removidos do POST | `GestaoPermissoesRolesController.php:71-74,109-119` |
| RN-GESTAOPERMISSOES-07 | O e-mail de `v2.support.email` não pode ser desativado por esta tela | A chamada de ativação/desativação é pulada | `GestaoPermissoesPermissionsController.php:216` |
| RN-GESTAOPERMISSOES-08 | Exclusão de perfil é soft delete, mas a unicidade de slug permanece no banco | Recriar o mesmo slug falha na validação `unique` | `V2Role.php:18-33`; `2026_06_01_000005...:30` |
| RN-GESTAOPERMISSOES-09 | Todo usuário criado recebe o role Sentinel `admin` se não tiver nenhum | Sem ele, `CmsAuthenticate` força logout | `GestaoPermissoesPermissionsController.php:333-336,373-400` |
| RN-GESTAOPERMISSOES-10 | A edição de perfil substitui a matriz inteira | Módulos ausentes do POST deixam de ter permissão (inclusive os que não existem mais) | `PermissionService.php:170-176` |

### 3.3 Casos-limite e comportamentos surpreendentes

- **Matriz parcial**: o formulário de edição renderiza todos os módulos ativos; se um
  módulo foi desinstalado ou está `restricted`, ele não é enviado. Como `updateRole`
  substitui o array, a permissão antiga daquele módulo é perdida
  (`PermissionService.php:170-176`).
- **Salvar perfil sem nenhum `permissions`**: `sanitizePermissions([])` grava `[]`;
  todos os usuários do perfil perdem acesso (`:170`).
- **Editar um perfil `super-admin` hipotético**: como o slug não pode ser reenviado,
  qualquer tentativa de salvar cai no bloqueio; o perfil fica imutável pela UI.
- **Perfil soft-deleted atribuído por POST forjado**: `exists:v2_roles,id` não considera
  `deleted_at`, então a validação passa; `V2Role::find` não acha o registro e o usuário
  fica sem permissões (`GestaoPermissoesPermissionsController.php:173`; `PermissionService.php:80-83`).
- **Usuário desativado com sessão aberta**: `CmsAuthenticate` não checa ativação; a
  sessão continua válida até expirar ou até o usuário tentar logar de novo
  (`CmsAuthenticate.php:18-44` vs. `LoginCmsController.php:61-69`).
- **Troca de e-mail do suporte**: o formulário de edição permite alterar o e-mail de
  qualquer usuário; não há proteção específica para a conta de suporte.
- **Alteração de `v2_role_id`**: vale na próxima requisição; não há logout forçado nem
  invalidação de sessão.
- **Contagem de usuários**: inclui usuários inativos e excluídos (soft delete do model
  User aplica global scope? `App\Admin\User` usa `SoftDeletes`, portanto excluídos são
  omitidos; inativos contam).

### 3.4 Armadilhas de reimplementação

- **Não materializar permissões no usuário.** O contrato é `users.v2_role_id` +
  `v2_roles.permissions_v2`; copiar a matriz para o usuário quebra a propagação
  imediata e a edição de perfil.
- **Não usar `v2_roles.slug = 'super-admin'` como bypass.** O bypass real é
  `PermissionService::hasPermission`/`V2PermissionMiddleware` com `inRole('super-admin')`
  (Sentinel) ou `config('v2.support.email')`.
- **Não contar apenas usuários ativos ao excluir perfil.** A regra usa todos os
  registros com `v2_role_id`, então um perfil “vazio” na tela pode estar bloqueado.
- **Não omitir `ensureSentinelRole`.** Sem role Sentinel, o login é recusado mesmo com
  `v2_role_id` e matriz corretos (`CmsAuthenticate.php:30-41`).
- **Não criar o usuário apenas com `User::create`.** O login exige ativação Sentinel;
  o caminho canônico é `Sentinel::registerAndActivate` + ativação completa.

### 3.5 Divergências documentação × código e dívidas

- O `README` do grupo fala em “controle de permissões, usuários e perfis”; o módulo
  também **cria** usuários, não só permissões.
- O docblock de `getAssignedUsersCount` diz “usuários ativos”, mas o código conta todos
  (`PermissionService.php:229-238`).
- O formulário de usuário afirma “Apenas super-administradores…” em todas as mensagens
  de 403, mas a autorização real é o slug RBAC `GestaoPermissoes:write`; o e-mail de
  suporte e o Sentinel `super-admin` também passam.
- O `super-admin` mencionado nas mensagens/README não é semeado: `DatabaseSeeder` cria
  apenas o role Sentinel `admin` e o usuário fundador sem `v2_role_id`
  (`database/seeds/DatabaseSeeder.php:24-60`).
- Não há `GestaoPermissoes` no seed de perfis; o único perfil semeado por migration é
  `regional` (`V2/Core/Migrations/2026_09_14_000002_seed_v2_regional_role.php:20-46`).

## 4. Integrabilidade

### 4.1 Superfície pública

| Símbolo | Tipo | Assinatura | Precondição | Consumidores |
|---|---|---|---|---|
| `PermissionService::hasPermission` | método | `($user, string $moduleKey, string $action): bool` | usuário autenticado | `v2_can()` |
| `PermissionService::getUserPermissions` | método | `($user): array` | usuário com `v2_role_id` | middleware, sidebar |
| `PermissionService::getAllRoles` | método | `(): Collection` | — | formulários de usuário |
| `PermissionService::getRoleById` | método | `(string $id): ?V2Role` | — | controllers |
| `PermissionService::createRole` | método | `(array $data): V2Role` | `name`, `slug` | controller de perfis |
| `PermissionService::updateRole` | método | `(string $id, array $data): ?V2Role` | perfil existente | controller de perfis |
| `PermissionService::deleteRole` | método | `(string $id): bool` | sem usuários | controller de perfis |
| `PermissionService::getAssignedUsersCount` | método | `(string $roleId): int` | — | lista de perfis |
| `V2Role` | model | `$table = v2_roles`, cast `permissions_v2 => array` | — | `PermissionService` |
| `ModuleProvider::getConfig` | provider | `array` | — | `V2ServiceProvider`, sidebar |

### 4.2 Contrato de UI/JS

- **Namespace**: `v2-gestaopermissoes`; layout `v2-cms::layouts.app`.
- **IDs/names**: `name`, `slug`, `email`, `password`, `password_confirmation`,
  `v2_role_id`, `unidade_regional_id`, `is_active`, `permissions[<ModuleKey>]`.
- **JS de slug** (create): gera slug a partir do nome, remove acentos com
  `normalize('NFD')`, troca espaços por hífen e para de sobrescrever após o primeiro
  `change` manual (`Views/roles/create.blade.php:161-187`).
- **Switch de ativação**: há um hidden `is_active=0` antes do checkbox `value=1`;
  desmarcar envia `0` (`Views/permissions/edit.blade.php:101-105`).
- **Confirmação de exclusão**: `onsubmit="return confirm('Deseja realmente remover este perfil de acesso?');"`
  e botão desabilitado quando `users_count > 0` (`Views/roles/index.blade.php:98-103`).
- **Sem AJAX**: todas as ações são POST + redirect/flash; nenhum endpoint JSON.
- **Mensagens**: `session('success')`, `session('error')` e `$errors`; a lista de
  perfis usa alertas Bootstrap dismissíveis (`Views/roles/index.blade.php:47-66`).

### 4.3 Ordem e ciclo de vida

1. `V2ServiceProvider::boot` descobre o módulo e injeta `group='Infraestrutura'`
   (`V2ServiceProvider.php:90-128`).
2. As rotas CMS são registradas com `web` + `module.active:GestaoPermissoes`
   (`V2ServiceProvider.php:303-307`).
3. `PermissionService` é singleton registrado apenas se `ActivityLoggerService`
   existir (`V2ServiceProvider.php:353-360`).
4. `V2Role` gera UUIDv4 no evento `creating` se `id` estiver vazio
   (`V2/Core/Models/V2Role.php:63-73`).
5. A matriz é lida do banco a cada request; não há evento de invalidação.

### 4.4 Receitas de uso

```php
// Verificar autorização em qualquer módulo/action
if (v2_can('Noticias', 'write')) {
    // ...
}
```

Resultado: `true` para `write`, para o e-mail de suporte e para Sentinel
`super-admin`; `false` para `none` e para `read` quando a action é `write`.

```php
// Criar um perfil programaticamente (mesmo caminho do controller)
$role = app(\V2\Core\Services\PermissionService::class)->createRole([
    'name' => 'Redator',
    'slug' => 'redator',
    'permissions' => [
        'Noticias' => 'write',
        'Agenda'   => 'read',
    ],
]);
```

Resultado: `v2_roles.permissions_v2 = {"Noticias":"write","Agenda":"read"}` e
`activity_log` `created`; valores inválidos são descartados.

```php
// Trocar o perfil de um usuário (efeito imediato na próxima requisição)
$user = \App\Admin\User::find($id);
$user->v2_role_id = $role->id;
$user->save();
```

Resultado: sidebar e middleware passam a usar a nova matriz; não há logout nem
recálculo em background.

```blade
{{-- Esconder ações de escrita para perfis de leitura --}}
@v2can('GestaoPermissoes', 'write')
    <a href="{{ route('v2.gestaopermissoes.roles.create') }}">Novo Perfil</a>
@endv2can
```

Resultado: o botão aparece apenas para quem tem `write` (ou suporte/super-admin).

### 4.5 Anti-uso

- **Criar `V2Role` diretamente sem sanitizar** `permissions_v2`: valores arbitrários
  ficam inertes no middleware (`getUserPermissions` devolve o array cru), mas podem
  confundir a UI e não são `none/read/write` válidos.
- **Apagar a linha de `v2_roles` com `forceDelete`/SQL**: usuários ficam com
  `v2_role_id` órfão, sem permissões e sem registro para auditoria; use `deleteRole`
  (soft delete) e realoque os usuários antes.
- **Confiar no `disabled` do botão de exclusão**: é só cliente; o servidor rejeita,
  mas a mensagem vem como flash `error`, não `403`.

### 4.6 Custos e limites da integração

| Limite | Valor | Efeito |
|---|---|---|
| Paginação de perfis | 15 | `paginate(15)` |
| Paginação de usuários | 15 | `paginate(15)` |
| Senha | mínimo 6 + confirmação | `min:6|confirmed` |
| Slug | 255, único | `unique:v2_roles,slug` |
| Nome | 255 | `max:255` |
| Módulos na matriz | todos os providers não `restricted` | cresce com o número de módulos |
| Consultas por página de perfis | 1 + N | N+1 em `getAssignedUsersCount` |

## 5. Rotas e contratos HTTP

| Tipo | Método | URI | Nome | Middleware | Controller@ação | Permissão | Efeito | Fonte |
|---|---|---|---|---|---|---|---|---|
| endpoint | GET | `/cms/permissoes/permissions` | `v2.gestaopermissoes.permissions.index` | `web; module.active:GestaoPermissoes; CmsAuthenticate; V2PermissionMiddleware` | `GestaoPermissoesPermissionsController@index` | acesso ao módulo | Lista usuários paginados | `Routes/cms.php:12` |
| endpoint | GET | `/cms/permissoes/permissions/create` | `v2.gestaopermissoes.permissions.create` | idem | `@create` | `GestaoPermissoes:write` | Formulário de usuário | `Routes/cms.php:13` |
| endpoint | POST | `/cms/permissoes/permissions` | `v2.gestaopermissoes.permissions.store` | idem + CSRF | `@store` | `write` | Cria usuário Sentinel + vínculo | `Routes/cms.php:14` |
| endpoint | GET | `/cms/permissoes/permissions/{id}/edit` | `v2.gestaopermissoes.permissions.edit` | idem | `@edit` | `write` | Formulário de edição | `Routes/cms.php:15` |
| endpoint | POST | `/cms/permissoes/permissions/{id}` | `v2.gestaopermissoes.permissions.update` | idem + CSRF | `@update` | `write` | Atualiza dados/ativação | `Routes/cms.php:16` |
| endpoint | GET | `/cms/permissoes/roles` | `v2.gestaopermissoes.roles.index` | idem | `GestaoPermissoesRolesController@index` | acesso ao módulo | Lista perfis paginados | `Routes/cms.php:19` |
| endpoint | GET | `/cms/permissoes/roles/create` | `v2.gestaopermissoes.roles.create` | idem | `@create` | `write` | Formulário de perfil + matriz | `Routes/cms.php:20` |
| endpoint | POST | `/cms/permissoes/roles` | `v2.gestaopermissoes.roles.store` | idem + CSRF | `@store` | `write` | Cria perfil | `Routes/cms.php:21` |
| endpoint | GET | `/cms/permissoes/roles/{id}/edit` | `v2.gestaopermissoes.roles.edit` | idem | `@edit` | `write` | Formulário de perfil | `Routes/cms.php:22` |
| endpoint | POST | `/cms/permissoes/roles/{id}` | `v2.gestaopermissoes.roles.update` | idem + CSRF | `@update` | `write` | Atualiza perfil/matriz | `Routes/cms.php:23` |
| endpoint | POST | `/cms/permissoes/roles/{id}/delete` | `v2.gestaopermissoes.roles.destroy` | idem + CSRF | `@destroy` | `write` | Soft delete de perfil | `Routes/cms.php:24` |

Não há throttle, cache nem API. Não existe rota de exclusão de usuário.

## 6. Dados e modelo

O módulo não cria migrations; usa tabelas do Core. Os schemas de `users`, `roles`,
`role_users` e `activations` estão em `CORE-AUTH` (`../../../01_arquitetura/03-auth-rbac-sentinel.md`).

### 6.1 `v2_roles`

| Coluna | Tipo | Null/default | Papel |
|---|---|---|---|
| `id` | uuid | PK | gerado por `V2Role` (UUIDv4) |
| `name` | varchar(255) | não | rótulo exibido |
| `slug` | varchar(255) | não, unique | identificador; `super-admin` reservado na UI |
| `permissions_v2` | jsonb | sim, default `null` | matriz `módulo → none/read/write` |
| `created_at`, `updated_at`, `deleted_at` | timestamps | sim | soft delete |

Índices: `slug` unique e `name` (`2026_06_01_000005_create_v2_roles_and_add_role_id_to_users_table.php:27-35`).

### 6.2 `users` (colunas usadas)

`name`, `email`, `password`, `permissions` (legado Sentinel), `v2_role_id` (uuid,
nullable, index), `unidade_regional_id` (uuid, nullable, index), timestamps e
`deleted_at`. `v2_role_id` é adicionado pela mesma migration
(`2026_06_01_000005...:38-45`); `unidade_regional_id` por
`2026_09_14_000001_add_unidade_regional_id_to_users_table.php:24-30`.

### 6.3 `V2Role`

`$fillable = ['id','name','slug','permissions_v2']`, `$casts = ['permissions_v2' => 'array']`,
`$incrementing = false`, `$keyType = 'string'`, `SoftDeletes`, relação `users()`
(`V2/Core/Models/V2Role.php:18-84`). Não há observer nem escopo além do soft delete.

```mermaid
erDiagram
    users }o--|| v2_roles : "v2_role_id"
    users ||--o{ role_users : "Sentinel"
    roles ||--o{ role_users : "Sentinel"
    users ||--o{ activations : "Sentinel"
    v2_roles {
      uuid id PK
      string name
      string slug UK
      jsonb permissions_v2
      timestamp deleted_at
    }
    users {
      int id PK
      string name
      string email UK
      uuid v2_role_id
      uuid unidade_regional_id
      timestamp deleted_at
    }
```

## 7. Fluxos e estados

```mermaid
stateDiagram-v2
    [*] --> Ativo: registerAndActivate + activation complete
    Ativo --> Inativo: is_active=0 (activations->remove)
    Inativo --> Ativo: is_active=1 (create+complete)
    Ativo --> Ativo: troca de v2_role_id (efeito imediato)
    Ativo --> [*]: sem delete de usuário
```

```mermaid
sequenceDiagram
    participant A as Admin
    participant C as RolesController
    participant P as PermissionService
    participant DB as Postgres
    A->>C: POST /cms/permissoes/roles (name, slug, permissions[])
    C->>C: v2_can write + validate
    C->>C: bloqueia slug super-admin
    C->>C: remove módulos restricted
    C->>P: createRole($data)
    P->>P: sanitizePermissions
    P->>DB: INSERT v2_roles
    P->>DB: activity_log created
    C-->>A: 302 index + created_item
```

`FL-GESTAOPERMISSOES-01` (criar perfil): validação → bloqueio reservado → filtro
restricted → sanitização → insert → auditoria. `FL-GESTAOPERMISSOES-02` (editar
usuário): validação → update de campos → ativação/desativação → role Sentinel →
auditoria. `FL-GESTAOPERMISSOES-03` (excluir perfil): contagem de usuários → exceção
ou auditoria + soft delete.

## 8. UI CMS

### 8.1 `permissions/index` — Permissões e Usuários

| Item | Valor |
|---|---|
| Rota | `GET /cms/permissoes/permissions` |
| Layout | `v2-cms::layouts.app`; breadcrumb Dashboard › Infraestrutura › Gestão de Permissões › Permissões |
| Objetivo | listar usuários e abrir edição |
| Estados | vazio: “Nenhum usuário encontrado com os filtros aplicados.”; sem loading |
| Filtro | `search` por nome/e-mail; botão “Novo Usuário” só com `write` |
| Colunas | Nome, E-mail, Perfil de Acesso, Unidade Regional, Status, Ações |
| Status | badge “Total (Admin)” para o e-mail de suporte; “Ativo”/“Inativo” via ativação |
| Regional | “Nacional” quando nula; rótulo `Nome (ACR/UF)` |
| Paginação | 15, com `appends(['search'])` |
| Ações | “Perfil” (link para edit) ou “Acesso Total” desabilitado para suporte |

### 8.2 `permissions/create` e `permissions/edit`

Campos de create: Nome, E-mail, Perfil (select de `getAllRoles`), Unidade Regional
(select de `v2_unidade_regionais` sem soft-deleted), Senha, Confirmação. Edit adiciona
“Nova Senha” opcional, switch “Usuário Ativo” com hidden `0` e mantém o e-mail.
O texto de ajuda afirma que a regional limita “Notícias e Associados”
(`Views/permissions/edit.blade.php:79-105`).

### 8.3 `roles/index` — Perfis de Acesso V2

Colunas: Nome, Slug, Operadores Vinculados (badge), Ações (Editar/Excluir). A busca é
por nome ou slug. O botão Excluir é desabilitado quando `users_count > 0`; a paginação
preserva `search` (`Views/roles/index.blade.php:39-45,69-127`).

### 8.4 `roles/create` e `roles/edit` — Matriz

| Aspecto | Valor |
|---|---|
| Campos | `name`, `slug`, `permissions[<ModuleKey>]` |
| Linhas da matriz | um módulo por linha, com ícone, título, badge do grupo e 3 radios |
| Valores | `none` (Sem Acesso), `read` (Leitura), `write` (Leitura & Escrita) |
| Default create | `none` marcado em todos |
| Default edit | `$role->permissions_v2[$moduleKey] ?? 'none'` |
| Módulos excluídos | qualquer config com `restricted` |
| Acessibilidade | labels por radio, tabela com `aria-label` |
| JS | geração automática de slug; sem AJAX |

O formulário não tem “select all” nem busca de módulo; com muitos módulos a tabela
cresce linearmente (`Views/roles/create.blade.php:79-137`).

## 9. UI Portal

**Não se aplica.** O módulo não tem views públicas nem efeito direto no portal, exceto
indiretamente ao liberar/bloquear módulos que publicam conteúdo.

## 10. Configuração, permissões e flags

### 10.1 Configuração

| Chave | Default | Efeito | Consumidor |
|---|---|---|---|
| `v2.support.email` | `suporte@maxima.inf.br` | bypass de autorização; conta não desativável por esta tela | `v2_can`, `PermissionService`, controller, views |
| `v2.permissions.support_group` | `SUPPORT` | **não é lida**: middleware e sidebar comparam o literal `'SUPPORT'` | nenhum (config morta) |

### 10.2 Permissões

| Slug | Ação | Middleware | Onde é aplicada |
|---|---|---|---|
| `GestaoPermissoes` | `read` | `V2PermissionMiddleware` | rotas que casam `cms/permissoes/*` |
| `GestaoPermissoes` | `write` | `v2_can()` | create/store/edit/update/destroy de usuários e perfis |

Sem `GestaoPermissoes` na matriz, o usuário não vê o módulo nem acessa as rotas
(middleware), mesmo que tente a URL direta.

### 10.3 Flag de módulo

`module.active:GestaoPermissoes` bloqueia as 11 rotas quando desativado; sem linha em
`v2_module_flags`, considera-se ativo (`ModuleFlagService.php:72-95`). Como o prefixo é
`cms/permissoes` (não exatamente `cms`), o bloqueio é `404` `Modulo desativado.`;
AJAX/JSON recebe `404` com `Modulo desativado: GestaoPermissoes`
(`ModuleAccessMiddleware.php:46-62`).

### 10.4 `ModuleProvider::getConfig()`

```php
[
  'title' => 'Gestão de Permissões',
  'description' => 'Permissões, perfis de acesso e segurança do CMS.',
  'icon' => 'bi bi-shield-lock-fill',
  'position' => 100,
  'active_pattern' => ['cms/permissoes/permissions*', 'cms/permissoes/roles*'],
  'subModulesLevel1' => [
    ['title' => 'Permissões e Usuários', 'route' => 'v2.gestaopermissoes.permissions.index', ...],
    ['title' => 'Perfis de Acesso V2', 'route' => 'v2.gestaopermissoes.roles.index', ...],
  ],
]
```

`group` é injetado pelo `V2ServiceProvider` (`ModuleProvider.php:14-37`).

## 11. Observabilidade, erros e limites

| Evento | Log | Dados |
|---|---|---|
| Perfil criado | `activity_log` `created` | `role_name`, `role_slug` |
| Perfil atualizado | `activity_log` `updated` | `role_name`, `role_slug` |
| Perfil excluído | `activity_log` `deleted` (antes do delete) | `role_name`, `role_slug` |
| Usuário criado | `activity_log` `created` | `email`, `name` |
| Usuário atualizado | `activity_log` `updated` | `user_email`, `v2_role_id`, `unidade_regional_id` |
| Bloqueio de slug reservado | `Log::warning` | mensagem fixa com o controller |
| Erros de CRUD | `Log::error`/`Log::warning` | mensagem da exceção |
| Ativação/desativação | `Log::info` | e-mail do usuário |
| Role Sentinel criado/atribuído | `Log::info` | slug/e-mail |

Falhas de auditoria não interrompem o fluxo: são capturadas e logadas como warning
(`GestaoPermissoesPermissionsController.php:238-240`; `PermissionService.php:148-151`).
Códigos: `403` para falta de `write`; `302` para erros de domínio/validação; `419` para
CSRF. Não há retenção específica; o `activity_log` cresce indefinidamente.

## 12. Seeds e dados iniciais

| Seeder/migration | Tabela | Registros | Idempotência | Fonte |
|---|---|---|---|---|
| `DatabaseSeeder` | `roles`, `users`, `role_users`, `activations` | role Sentinel `admin` + usuário fundador (sem `v2_role_id`) | verifica e-mail antes de criar | `database/seeds/DatabaseSeeder.php:17-60` |
| `SeedV2RegionalRole` | `v2_roles` | perfil `regional` com `Noticias=write`, `Associados=write` | `where(slug)->first()` → update ou insert; restaura `deleted_at` | `V2/Core/Migrations/2026_09_14_000002_seed_v2_regional_role.php:20-46` |

Não há seed de `super-admin`, de perfis administrativos genéricos nem de permissões
para `GestaoPermissoes`; o primeiro acesso é feito pelo usuário de suporte, que ignora
a matriz.

## 13. Lacunas

| ID | Lacuna | Impacto | Onde procurar |
|---|---|---|---|
| LG-GESTAOPERMISSOES-01 | Não há testes automatizados do módulo | Regressões de matriz/ativação não detectadas | `sucesu-cms/tests`, `tests-e2e` |
| LG-GESTAOPERMISSOES-02 | O comportamento exato de `activations->create` para um usuário já ativo não está documentado no projeto (múltiplas linhas?) | Re-salvar pode criar ativações duplicadas | `vendor/cartalyst/sentinel`, `activations` |
| LG-GESTAOPERMISSOES-03 | Não há proteção contra excluir/alterar o último usuário com `GestaoPermissoes:write` | Risco de auto-bloqueio administrativo | ausência de regra no controller |
| LG-GESTAOPERMISSOES-04 | Não existe tela/rota para excluir usuário | Offboarding depende de intervenção no banco | `Routes/cms.php` |

## 14. Checklist de paridade

- [ ] `PC-GESTAOPERMISSOES-01` — Criar perfil com nome/slug válidos grava `v2_roles.permissions_v2` com apenas `none/read/write`. Fonte: `PermissionService.php:132-154,246-255`.
- [ ] `PC-GESTAOPERMISSOES-02` — Slug `super-admin` (qualquer caixa/espaços) é rejeitado na criação e na edição. Fonte: `GestaoPermissoesRolesController.php:100-106,188-194`.
- [ ] `PC-GESTAOPERMISSOES-03` — Módulos com `restricted` não aparecem na matriz nem são gravados. Fonte: `:71-74,109-119`.
- [ ] `PC-GESTAOPERMISSOES-04` — Excluir perfil com usuários retorna flash `Não é possível excluir um perfil que possui usuários vinculados.`. Fonte: `PermissionService.php:206-209`.
- [ ] `PC-GESTAOPERMISSOES-05` — Excluir perfil sem usuários faz soft delete e audita `deleted`. Fonte: `:211-226`.
- [ ] `PC-GESTAOPERMISSOES-06` — Atualizar a matriz de um perfil muda imediatamente o menu e o acesso dos usuários vinculados. Fonte: `PermissionService.php:163-190`; `V2PermissionMiddleware.php:79-127`.
- [ ] `PC-GESTAOPERMISSOES-07` — Criar usuário sem `v2_role_id` é permitido; ele loga, mas não vê módulos. Fonte: `GestaoPermissoesPermissionsController.php:283-285`; `PermissionService.php:75-89`.
- [ ] `PC-GESTAOPERMISSOES-08` — Criar usuário garante o role Sentinel `admin`. Fonte: `:333-336,373-400`.
- [ ] `PC-GESTAOPERMISSOES-09` — Desmarcar “Usuário Ativo” remove ativações e bloqueia novo login. Fonte: `:225-227`; `LoginCmsController.php:61-69`.
- [ ] `PC-GESTAOPERMISSOES-10` — O e-mail de suporte não pode ser desativado por esta tela. Fonte: `:216`.
- [ ] `PC-GESTAOPERMISSOES-11` — Trocar o perfil de um usuário não força logout. Fonte: `:202-213`.
- [ ] `PC-GESTAOPERMISSOES-12` — Todas as escritas exigem `GestaoPermissoes:write`; sem ela, `403` com mensagem “Apenas super-administradores…”. Fonte: `GestaoPermissoesRolesController.php:66-67`; `GestaoPermissoesPermissionsController.php:138-139`.

## 15. Evidências consultadas

- `V2/Modules/Infraestrutura/GestaoPermissoes/Controllers/GestaoPermissoesPermissionsController.php:1-400`
- `V2/Modules/Infraestrutura/GestaoPermissoes/Controllers/GestaoPermissoesRolesController.php:1-253`
- `V2/Modules/Infraestrutura/GestaoPermissoes/ModuleProvider.php:1-38`
- `V2/Modules/Infraestrutura/GestaoPermissoes/Routes/cms.php:1-25`
- `V2/Modules/Infraestrutura/GestaoPermissoes/Views/permissions/{index,create,edit}.blade.php:1-130`
- `V2/Modules/Infraestrutura/GestaoPermissoes/Views/roles/{index,create,edit}.blade.php:1-187`
- `V2/Core/Services/PermissionService.php:1-256`
- `V2/Core/Models/V2Role.php:1-84`
- `V2/Core/Migrations/2026_06_01_000005_create_v2_roles_and_add_role_id_to_users_table.php:17-53`
- `V2/Core/Migrations/2026_09_14_000001_add_unidade_regional_id_to_users_table.php:18-38`
- `V2/Core/Migrations/2026_09_14_000002_seed_v2_regional_role.php:15-55`
- `V2/Core/Middleware/CmsAuthenticate.php:18-44`; `V2/Core/Middleware/V2PermissionMiddleware.php:22-136`
- `V2/Core/ViewComposers/SidebarComposer.php:20-101`; `V2/Core/Helpers/helpers.php:12-26`
- `V2/Core/Controllers/Auth/LoginCmsController.php:31-85`
- `database/seeds/DatabaseSeeder.php:10-60`
