# Plataforma de treinamento — visão, histórico e inventário

## Visão de produto (alvo)

O repositório está sendo orientado para uma **plataforma de treinamento** corporativa, com foco em:

1. **Importação de usuários** — cadastro em massa ou integração de participantes e equipe.
2. **Importação de baterias de perguntas** — questões com **alternativas** (ex.: múltipla escolha) para avaliação ou pesquisa.
3. **Experiência do participante** — usuários entram (prioritariamente pelo **portal**), visualizam conteúdo e **respondem** às perguntas.
4. **BI / indicadores** — consolidação de respostas e acompanhamento para gestão (fase futura).

A **equipe Hypera** (operação, gestão, conteúdo) usa o fluxo **administrativo** com permissões por role. Os **participantes** são usuários com acesso **restrito ao portal**, sem telas de gestão da operação.

---

## O que já foi implementado (resumo)

- **Tipos de conta (`AccountKind`)** — distinção entre `staff` (equipe, pode ter gestão conforme roles) e `portal_only` (somente portal).
- **Perfil de participante (`TraineeProfile`)** — tabela ligada ao `user_id` (`metadata` JSON para cargo, etc.) e `training_group_id` (FK ao grupo pré-cadastrado).
- **Dois contextos após o login** — escolha ou redirecionamento entre **gestão** e **portal** (`RoleContext`, telas e middlewares de contexto).
- **Portal participante** — layout dedicado, home, topbar/sidebar, perfil em portal quando aplicável.
- **Aparência do portal** — configuração por empresa (logo, favicon, fundo, cores), menu em Configurações, policy e fluxo Controller → Service → Repository; favicon raster normalizado para **32×32 px** em PNG quando a extensão **GD** está disponível; URLs públicas relativas (`/storage/...`).
- **Papel de roles** — catálogo de definições de roles (`RoleCatalog` + seed) alinhado a permissões e ao desenho multi-contexto (gestão vs portal).
- **Testes de feature** — autenticação/autorização de portal, aparência, perfil, usuários (visibilidade e roles).
- **Login Microsoft (Entra ID) no portal** — OAuth 2.0 + Graph para participantes; credenciais do app Azure **só no `.env`**; identificação do tenant por **slug** na URL (`/login/{slug}` ou `?portal=`); se o e-mail ainda não existir na empresa, **onboarding** (Cargo, Área, Grupo) cria conta `portal_only`, role **Usuario** e `TraineeProfile`; **Área** e **Grupo** têm de coincidir com setores e grupos já cadastrados.
- **Correio (Mailjet + Markdown)** — envio por **SMTP** (mailer `mailjet`, sem SDK); tema de e-mail Laravel publicado em `resources/views/vendor/mail/`; **recuperação de senha** com `PasswordResetNotification`, template `mail/password-reset`, textos em `config/password_reset_mail.php` / `.env`; cabeçalho com logo (`MAIL_BRAND_LOGO_URL` ou `public/images/Logo_FUEGO.svg`); saudação com **nome do utilizador** no e-mail de reset.

- **Baterias de perguntas** — modelo `QuestionBattery` → `BatteryQuestion` → `BatteryQuestionAlternative` (4 alternativas por pergunta), escopo por `company_id`, `sector_id` opcional (setor = **Área**, tem de existir em `sectors`), `training_group_id` opcional (FK a `training_groups`, nome = coluna **Grupo**), import **XLSX/XLS** (uma planilha = uma bateria), fluxo Controller → `QuestionBatteryImportService` → repositórios, policy `QuestionBatteryPolicy`, rotas `training/question-batteries`, permissão de menu `menu.training.question_batteries`. **Áreas** e **grupos** são pré-cadastrados (menu Treinamento).
- **Grupos de treinamento** — CRUD `training/groups`, `trainee_profiles.training_group_id`, `training_groups.sector_id` (área obrigatória no grupo); alinhado a import, utilizadores e onboarding Microsoft.
- **Portal — questionário** — rotas `/portal/questionario/*`: bateria publicada para setor+grupo do participante, experiência visual “game” (splash/play), resposta e correção na sessão (`PortalQuestionnaireService::grade`); configuração em **Configurações → Aparência** (aba questionário game: cores, imagens, tagline).
- **Correção SCAN (2026-05-18)** — Axios **≥ 1.13.5** (CVE-2026-25639); clickjacking (`PreventClickjacking`); autocomplete `off` em auth; `XSRF-TOKEN` com **HttpOnly** (`App\Http\Middleware\ValidateCsrfToken`). Detalhe: [registro-entregas.md](./registro-entregas.md#correção-scan).

O que **ainda não** está no escopo: **persistência de tentativas** de questionário, histórico por participante, ligação opcional a `operation_events`, e **BI** — são próximos incrementos sobre esta base.

**Registro cronológico e inventário pendente de commit:** [registro-entregas.md](./registro-entregas.md).

---

## Baterias de perguntas (import XLSX)

**Registro:** **2026-05-14** (referência local; detalhe em `git log`).

### Modelo de dados (resumo)

- **`training_groups`** — `company_id`, `name` (único por empresa), `is_active`; usado na coluna **Grupo** das baterias e em `trainee_profiles.training_group_id`.
- **`question_batteries`** — `company_id`, `sector_id` (opcional, FK a `sectors`), `training_group_id` (opcional, FK a `training_groups`), `status`, `starts_at`, `ends_at`, `title`, `metadata` (JSON; o import grava metadados mínimos de ficheiro/data).
- **`battery_questions`** — `question_battery_id`, `stem` (enunciado), `weight`, `tags`, `sort_order` (ordem na bateria).
- **`battery_question_alternatives`** — `battery_question_id`, `position` (1 a 4), `body`, `is_correct`.

### Regra de importação (nova bateria ou acrescentar perguntas)

- Formato **XLSX** ou **XLS** (primeira folha).
- Colunas obrigatórias: **Pergunta**, **Alternativa 1** … **Alternativa 4**, **Alternativa correta** (número **1** a **4** por linha). Opcionais: **Peso**, **Tags**, **Área**, **Grupo**.
- Todas as linhas com pergunta devem ter o **mesmo** par **Área + Grupo** (trim, comparação sem diferenciar maiúsculas de minúsculas). Em caso de divergência, o import falha com mensagem de validação.
- A coluna **Área** tem de corresponder a um **setor já cadastrado** (`sectors`, mesmos nomes que em Treinamento → Áreas ou Inventário → Setores). `SectorService::resolveSectorIdByAreaName` apenas **localiza** pelo nome; não cria setor automaticamente.
- A coluna **Grupo** tem de corresponder a um **grupo de treinamento ativo** já cadastrado (`training_groups.name` na empresa).
- **Nova bateria:** deixar o destino em “Criar nova bateria”; opcionalmente preencher título, status e datas.
- **Acrescentar a uma bateria existente:** escolher a bateria no campo **Destino** (ou abrir o import a partir do detalhe da bateria). As perguntas da planilha são inseridas **no fim**, com `sort_order` contínuo. A **Área** (setor resolvido) e o **Grupo** têm de coincidir com os da bateria escolhida; caso contrário o pedido falha com erro de validação. Título, status e datas do formulário **não** alteram a bateria neste modo.

### Rotas e permissões (gestão)

- Listagem: `GET /training/question-batteries` (`training.question-batteries.index`) — colunas **Perguntas** e **Progresso**; detalhe com total de perguntas em destaque.
- Cadastro de **áreas** (setores, mesma tabela `sectors`): `GET/POST/PUT/DELETE /training/areas` (`training.areas.*`), permissão `menu.training.areas` (ou inventário → setores). Listagem com **Utilizadores**, **Perguntas** e **Progresso**; exclusão unitária e em lote (`training.areas.bulk.*`). Ver [lote-treinamento-areas-performance-2026-05-26.md](./lote-treinamento-areas-performance-2026-05-26.md).
- Cadastro de **grupos**: `GET/POST/PUT/DELETE /training/groups` (`training.groups.*`), permissão `menu.training.groups`.
- Detalhe (perguntas e alternativas): `GET /training/question-batteries/{id}` (`training.question-batteries.show`).
- Importar: `GET /training/question-batteries/import` (query opcional `?question_battery_id=` para pré-selecionar a bateria), `POST /training/question-batteries/import` (`training.question-batteries.import` / `import.store`).
- Permissão de menu: `menu.training.question_batteries` (cascata a partir de `menu.training` no formulário de roles).

### Variáveis de ambiente

Nenhuma dedicada; usa **PhpSpreadsheet** já declarado no `composer.json`.

### Testes e fixture

- Testes de feature: `tests/Feature/Training/QuestionBatteryImportTest.php`.
- Fixture de exemplo: `tests/Fixtures/question_battery_minimal.xlsx`.

---

## Login Microsoft (Entra ID) no portal — fluxo, `.env` e inventário

**Registro desta documentação:** **2026-05-13**, **20:15** (referência local do ambiente de desenvolvimento; para auditoria exata use `git log` / `git diff`).

### Visão do produto

- Participantes podem autenticar com **Microsoft** quando a empresa define, em **Configurações → Aparência do portal**, o modo de login **Microsoft** (enum `PortalLoginMethod::Microsoft`).
- O **slug público** (`public_portal_slug`, único no sistema) monta URLs como **`/login/{slug}`** ou **`/login?portal={slug}`**, permitindo saber qual `company_id` está no estado OAuth assinado (`MicrosoftOAuthPortalState`).
- Se existir **apenas um** portal com Microsoft configurado e `.env` completo, o botão Microsoft também pode aparecer em **`/login`** sem slug (heurística single-tenant).
- **Equipe / acesso global (e-mail e senha)** quando o portal está em modo Microsoft: use a query **`password_login=1`** — por exemplo **`/login?password_login=1`**, **`/login/{slug}?password_login=1`** ou **`/login?portal={slug}&password_login=1`**. Isso força o formulário de e-mail e senha e, em **`/login` sem slug**, desativa a heurística que “cola” o único portal Microsoft (evitando esconder o login por senha).
- **Credenciais** (tenant, client id, client secret) **não** são persistidas no banco; leem-se de `config/services.php` ← variáveis `MICROSOFT_*` no `.env`.
- **Pré-cadastro / primeiro acesso:** se o Graph devolver um e-mail **sem** utilizador naquela empresa, o utilizador preenche **Cargo**, **Área** (nome do setor, criado/associado como no import em lote) e **Grupo**; em seguida o sistema cria `User` (`AccountKind::PORTAL_ONLY`), atribui a role global **Usuario** (`RoleCatalog` / `UsuarioRoleDefinition`), grava `TraineeProfile.metadata` e inicia sessão.
- Se o e-mail **já** existir na mesma empresa e estiver ativo, o login Microsoft entra direto (sem formulário). E-mail noutra empresa ou conta inativa → mensagem de erro, sem criar duplicado.

### Variáveis de ambiente (`.env`)

| Variável | Função |
|----------|--------|
| `MICROSOFT_OAUTH_TENANT_ID` | ID do diretório (tenant) Azure AD. |
| `MICROSOFT_OAUTH_CLIENT_ID` | Application (client) ID do app registration. |
| `MICROSOFT_OAUTH_CLIENT_SECRET` | Client secret do app. |
| `MICROSOFT_REDIRECT_URI` | Opcional; se vazio, usa `APP_URL` + `/auth/microsoft/callback`. Deve coincidir **exatamente** com uma redirect URI registada no Azure. |

### Rotas HTTP relevantes (`routes/auth.php`)

| Método | Caminho | Nome | Descrição |
|--------|---------|------|-----------|
| GET | `/auth/microsoft/redirect` | `auth.microsoft.redirect` | Inicia OAuth; query `portal` = slug público. |
| GET | `/auth/microsoft/callback` | `auth.microsoft.callback` | Recebe `code` + `state`, Graph, login ou redirecionamento ao onboarding. |
| GET | `/auth/microsoft/complete` | `auth.microsoft.complete.show` | Formulário Cargo / Área / Grupo (sessão pendente). |
| POST | `/auth/microsoft/complete` | `auth.microsoft.complete.store` | Cria utilizador e conclui login. |
| GET | `/login/{portal_slug?}` | `login` | Login; slug opcional; `?portal=`; `password_login=1` força e-mail/senha e evita auto-slug do único portal Microsoft. |

### Inventário — arquivos **criados** (login Microsoft + onboarding)

- **`app/Enums/PortalLoginMethod.php`** — Valores `password` e `microsoft` para o modo de login na URL do portal (persistido em `portal_appearance_settings.portal_login_method`).

- **`app/Support/MicrosoftOAuthPortalState.php`** — Estado OAuth assinado (HMAC-SHA256 com chave derivada de `config('app.key')`): `company_id` + nonce + TTL **600 s**, verificado no callback para evitar troca de tenant.

- **`app/Support/PostLoginRedirect.php`** — Centraliza o redirect pós-autenticação (gestão vs portal vs escolha), usado pelo login por **senha** e pelo fluxo **Microsoft** após sessão válida.

- **`app/Services/PortalMicrosoftOAuthService.php`** — Monta URL de autorização Microsoft, `callbackRedirectUri()`, troca `code` por token e chama Graph `me` → `fetchProfileFromAuthorizationCode()` devolve `email` + `display_name`. Não grava utilizador.

- **`app/Services/MicrosoftPortalOnboardingService.php`** — Chave de sessão `microsoft_portal_onboarding` (TTL 900 s), `putPending` / `pendingPayload` / `forgetPending`, `completeRegistration()` com transação: `UserService::createUser`, role **Usuario**, `SectorService::resolveSectorIdByAreaName`, `TraineeProfile`.

- **`app/Http/Controllers/Auth/MicrosoftOAuthController.php`** — `redirect` e `callback`: integra repositório de aparência, estado OAuth, onboarding e `UserRepositoryInterface`.

- **`app/Http/Controllers/Auth/MicrosoftPortalOnboardingController.php`** — `show` (valida sessão pendente) e `store` (FormRequest + login + `PostLoginRedirect`).

- **`app/Http/Requests/Auth/StoreMicrosoftPortalOnboardingRequest.php`** — Valida `cargo`, `area`, `grupo` (obrigatórios, limites de tamanho); `authorize()` exige sessão de onboarding válida.

- **`resources/views/auth/microsoft-portal-complete.blade.php`** — Formulário guest para concluir cadastro após Microsoft.

- **`tests/Feature/Auth/MicrosoftOAuthPortalLoginTest.php`** — Callback com utilizador existente; fluxo completo onboarding → utilizador criado + metadata.

- **`database/migrations/2026_05_13_192311_add_portal_microsoft_oauth_to_portal_appearance_settings_table.php`** — Adiciona `portal_login_method` (default `password`) e `public_portal_slug` (nullable, único). *Nota histórica:* chegou a incluir colunas de OAuth na mesma migration; essas colunas foram **removidas** pela migration seguinte, mantendo credenciais só no `.env`.

- **`database/migrations/2026_05_13_194704_remove_microsoft_oauth_columns_from_portal_appearance_settings_table.php`** — Remove `microsoft_oauth_tenant_id`, `microsoft_oauth_client_id`, `microsoft_oauth_client_secret` da tabela (credenciais exclusivamente no `.env`).

### Inventário — arquivos **editados** (login Microsoft + onboarding)

- **`config/services.php`** — Chave `microsoft` com `redirect`, `tenant_id`, `client_id`, `client_secret` mapeados para `env()`.

- **`.env.example`** — Comentários com `MICROSOFT_OAUTH_*` e `MICROSOFT_REDIRECT_URI`.

- **`routes/auth.php`** — Rotas Microsoft, onboarding e rota `login/{portal_slug?}` com constraint no slug; import do `MicrosoftPortalOnboardingController`.

- **`app/Http/Controllers/Auth/AuthenticatedSessionController.php`** — Resolução de slug por rota, query `portal` e heurística de **único** portal Microsoft; injeção de repositório + `PortalMicrosoftOAuthService`; view `auth.login` recebe `portalLogin`.

- **`resources/views/auth/login.blade.php`** — Botão “Entrar com Microsoft”, formulário de senha condicional, aviso de URLs com slug / `?portal=`.

- **`app/Models/PortalAppearanceSetting.php`** — Fillable e cast de `portal_login_method`; sem colunas de secret no modelo após decisão `.env`-only.

- **`app/Repositories/Contracts/PortalAppearanceSettingRepositoryInterface.php`** / **`app/Repositories/Eloquent/PortalAppearanceSettingRepository.php`** — `findByPublicPortalSlug`, `findUniqueMicrosoftPortalAppearanceOrNull()` (single-tenant).

- **`app/Services/PortalAppearanceSettingService.php`** — Persistência de `portal_login_method` e `public_portal_slug` no `updateForCompany`; defaults em `modelForEdit`.

- **`app/Http/Requests/Settings/UpdatePortalAppearanceSettingRequest.php`** — Regras do modo de login + slug; `withValidator` exige `.env` Microsoft preenchido ao ativar modo Microsoft.

- **`resources/views/settings/portal-appearance/edit.blade.php`** — Secção “Acesso ao portal”: rádios password/Microsoft, slug, aviso de credenciais no `.env`, texto da redirect URI.

- **`app/Repositories/Contracts/UserRepositoryInterface.php`** / **`app/Repositories/Eloquent/UserRepository.php`** — `findByEmail()` para lookup global por e-mail (unicidade em `users.email`).

- **`tests/Feature/Auth/AuthenticationTest.php`** — Cobertura de `/login` com único portal Microsoft + `config()` em teste.

- **`tests/Feature/Settings/PortalAppearanceSettingTest.php`** — Campo `portal_login_method` nos PUTs; teste de erro se Microsoft ativo sem variáveis `.env`.

### Dependências e operação

- **Azure AD:** registar redirect URI compatível com `MICROSOFT_REDIRECT_URI` ou com `{APP_URL}/auth/microsoft/callback`.
- **Seeder:** a role **Usuario** e permissões de portal vêm do **`RoleCatalogSeeder`**; ambientes novos precisam desse catálogo para onboarding e import portal.
- **Comandos úteis após alterar `.env`:** `php artisan config:clear`.

---

## Correio (e-mail) — Mailjet, Markdown Laravel e recuperação de senha

**Registro desta documentação:** **2026-05-13** (referência local; detalhe fino em `git log` / `git diff`).

### Visão técnica

- O Laravel usa o **Symfony Mailer**; não é necessário pacote Composer da Mailjet para envio normal — basta o mailer **SMTP** com host `in-v3.mailjet.com` (credenciais = API Key + Secret Key da Mailjet).
- **`MAIL_MAILER`:** em desenvolvimento costuma ser `log` ou `array`; em produção com Mailjet, **`mailjet`** (definido em `config/mail.php` no array `mailers`).
- **Remetente global:** `MAIL_FROM_ADDRESS` e `MAIL_FROM_NAME` (validar domínio/remetente no painel Mailjet).
- **Marca no cabeçalho** dos e-mails gerados pelos componentes Markdown `<x-mail::*>`: `config/mail.php` → `brand.logo_url` (`MAIL_BRAND_LOGO_URL`). Se vazio e existir `public/images/Logo_FUEGO.svg`, o layout usa `asset()`; caso contrário mostra `APP_NAME` em texto.
- **Recuperação de senha:** o model `User` sobrescreve `sendPasswordResetNotification()` e envia **`App\Notifications\PasswordResetNotification`**, que estende a notificação base do Laravel e renderiza o Markdown **`resources/views/mail/password-reset.blade.php`**. A saudação usa o **nome** do utilizador (ou o **e-mail** se o nome estiver vazio).

### Fluxo — “Esqueci a senha”

1. O utilizador submete o e-mail em **`GET/POST /forgot-password`** (`PasswordResetLinkController`).
2. `Password::sendResetLink()` gera o token e chama **`$user->sendPasswordResetNotification($token)`**.
3. **`PasswordResetNotification::toMail()`** monta `MailMessage` com `->markdown('mail.password-reset', [...])` (URL de reset, `userName`, textos vindos de `config('password_reset_mail')`, minutos de expiração de `config('auth.passwords.*.expire')`).
4. O motor de Markdown do Laravel compõe o HTML com o tema em **`resources/views/vendor/mail/html/`** (layout, header, footer, painel, botão, CSS em `html/themes/default.css`) e a variante texto em `resources/views/vendor/mail/text/` quando aplicável.

### Pastas `resources/views/vendor/mail/`

São vistas **publicadas** do framework (`php artisan vendor:publish --tag=laravel-mail`). O nome **`vendor`** neste caminho é convenção Laravel para “overrides de pacotes”, **não** a pasta `vendor/` do Composer.

| Área | Ficheiros relevantes | Função |
|------|----------------------|--------|
| HTML | `html/layout.blade.php` | Estrutura geral do e-mail (tabelas, meta). |
| HTML | `html/message.blade.php` | Slots: **cabeçalho** (logo/nome), **corpo** (`$slot` do teu Markdown), **subcopy** opcional, **rodapé**. |
| HTML | `html/header.blade.php` | Linha do topo: link para `APP_URL` + conteúdo do slot (imagem ou texto). |
| HTML | `html/subcopy.blade.php` | Bloco **secundário** (texto pequeno); o template de reset **não** usa este slot hoje — o link alternativo está no `x-mail::panel` dentro de `mail/password-reset`. |
| HTML | `html/themes/default.css` | Estilos (botão preto, tipografia, painel, etc.). |
| Texto | `text/*` | Versão plain text para clientes que não renderizam HTML. |

### Variáveis de ambiente — transporte e marca

| Variável | Função |
|----------|--------|
| `MAIL_MAILER` | `log`, `array`, `smtp`, **`mailjet`**, etc. |
| `MAIL_FROM_ADDRESS` | Endereço “From” global. |
| `MAIL_FROM_NAME` | Nome “From” (ex.: `${APP_NAME}`). |
| `MAILJET_API_KEY` | Utilizador SMTP Mailjet. |
| `MAILJET_SECRET_KEY` | Senha SMTP Mailjet. |
| `MAILJET_SMTP_HOST` / `MAILJET_SMTP_PORT` | Opcional (padrão `in-v3.mailjet.com:587`). |
| `MAIL_BRAND_LOGO_URL` | URL **absoluta** HTTPS do logo no cabeçalho (recomendado em produção; evita `127.0.0.1` inacessível fora da máquina). |

### Variáveis de ambiente — texto do e-mail de reset (opcional)

Definidas em **`config/password_reset_mail.php`**; sobrescrevem defaults em PT.

| Variável | Função |
|----------|--------|
| `PASSWORD_RESET_MAIL_SUBJECT` | Assunto. |
| `PASSWORD_RESET_MAIL_INTRO` | Parágrafo principal (pode ter quebras de linha). |
| `PASSWORD_RESET_MAIL_BUTTON_LABEL` | Rótulo do botão. |
| `PASSWORD_RESET_MAIL_SUPPORT_LINE` | Linha opcional (ex.: suporte). |
| `PASSWORD_RESET_MAIL_SALUTATION` | Ex.: “Atenciosamente,”. |
| `PASSWORD_RESET_MAIL_TEAM_NAME` | Assinatura abaixo do salutation; vazio usa `APP_NAME`. |

### Testes e comandos

- **`phpunit.xml`** força `MAIL_MAILER=array` nos testes (e-mails não saem da aplicação).
- **`tests/Feature/Auth/PasswordResetTest.php`** assere **`PasswordResetNotification`** (não a classe base do framework).
- Após alterar `.env` ou `config/*`: **`php artisan config:clear`**.
- Teste manual com Mailjet: `php artisan tinker` e `Mail::raw(...)` com destinatário **RFC válido**; em contas de teste Mailjet, respeitar destinatários autorizados / domínio validado.

### Inventário — ficheiros **criados** (correio)

- **`config/password_reset_mail.php`** — Defaults e leitura `env()` para textos do reset.

- **`app/Notifications/PasswordResetNotification.php`** — `toMail()` com Markdown `mail.password-reset` e dados do utilizador.

- **`resources/views/mail/password-reset.blade.php`** — Corpo: “Olá, {nome}”, intro, botão, painel com expiração + URL em texto, suporte/saudação opcionais.

- **`resources/views/vendor/mail/`** (árvore publicada) — Tema HTML/texto dos e-mails Markdown (`layout`, `message`, `header`, `button`, `footer`, `themes/default.css`, etc.).

### Inventário — ficheiros **editados** (correio)

- **`config/mail.php`** — Mailer **`mailjet`** (SMTP); bloco **`brand.logo_url`** para o cabeçalho.

- **`app/Models/User.php`** — `sendPasswordResetNotification()` delega em **`PasswordResetNotification`**.

- **`.env.example`** — Blocos Mailjet, `MAIL_BRAND_LOGO_URL`, `PASSWORD_RESET_MAIL_*` comentados.

- **`tests/Feature/Auth/PasswordResetTest.php`** — Import da notificação da aplicação.

---

## Inventário — arquivos novos

**Data de referência do lote:** **2026-05-13** (última modificação local antes do versionamento definitivo no Git; após commit, use `git log --diff-filter=A --follow -- <arquivo>` para auditoria).

Os itens abaixo foram adicionados ao projeto para suportar participantes, portal, aparência e governança de roles.

### Migrações (`database/migrations/`)

- **`2026_05_13_120000_add_account_kind_to_users_table.php`** — Adiciona a coluna `account_kind` em `users` (string, default `staff`), persistindo se o usuário é da **equipe** (`staff`) ou **somente portal** (`portal_only`). É a base para o cadastro diferenciado entre quem opera o sistema e quem só consome treinamento no portal.

- **`2026_05_13_120100_create_trainee_profiles_table.php`** — Cria `trainee_profiles` com `user_id` (único, FK em cascata), `company_id` opcional e `metadata` JSON. Reserva um lugar para dados do **participante** (turma, matrícula, notas futuras) sem inflar a tabela `users`.

- **`2026_05_13_120000_create_portal_appearance_settings_table.php`** — Cria `portal_appearance_settings` com `company_id` único, caminhos de arquivo para favicon/logo/imagem de fundo e campos de cor em hex. Permite **white-label** do portal por tenant.

### Enums e contratos

- **`app/Enums/AccountKind.php`** — Enum string `STAFF` / `PORTAL_ONLY` com `label()` para exibição em formulários. Centraliza a semântica de tipo de conta em um só tipo.

- **`app/Enums/RoleContext.php`** — Enum `management` vs `portal`. Representa em qual **modo de interface** a sessão está após o login, evitando misturar rotas de gestão com rotas do portal.

- **`app/Contracts/Roles/CataloguedRoleDefinition.php`** — Interface estática para cada definição de role no catálogo: nome, contexto (`RoleContext`), lista de permissões e descrição. Padroniza como novos roles entram no sistema e no seeder.

### Catálogo de roles e seed

- **`app/Roles/RoleCatalog.php`** — Lista as classes de definição de role, expõe quais nomes de role do catálogo liberam **área de gestão** e quais liberam **portal**, e utilitários para listar permissões por role. É o “índice” de tudo que o `RoleCatalogSeeder` materializa no banco.

- **`app/Roles/Catalog/UsuarioRoleDefinition.php`** — Role **Usuario** com contexto **Portal** e permissões `portal.access` / `portal.home`. É o papel típico do **participante** que só precisa do portal.

- **`app/Roles/Catalog/LeituraRoleDefinition.php`** — Role de leitura na **gestão** (somente visualização), com permissões mínimas de menu conforme desenho do produto.

- **`app/Roles/Catalog/FinanceiroRoleDefinition.php`** — Role **Financeiro** em contexto **gestão**, com permissões de menus financeiros.

- **`app/Roles/Catalog/GestorOperacoesRoleDefinition.php`** — Role **Gestor Operações** em contexto **gestão**, com permissões amplas de operações (clientes, eventos, suprimentos, etc.).

- **`database/seeders/RoleCatalogSeeder.php`** — Percorre o `RoleCatalog`, cria/atualiza `Permission` e `Role` por empresa e associa as permissões. Garante que cada tenant tenha o mesmo **vocabulário** de roles e permissões alinhado ao código.

### Models

- **`app/Models/TraineeProfile.php`** — Model Eloquent do perfil do participante: fillable, cast de `metadata` para array, relacionamentos `user` e `company`. Ponto de extensão para dados de treinamento sem acoplar tudo a `User`.

- **`app/Models/PortalAppearanceSetting.php`** — Model da linha única de aparência por empresa (`company_id`), fillable com paths e cores. Usado pelo repositório e pelo tema do layout.

### Aparência do portal — persistência, regras e suporte

- **`app/Repositories/Contracts/PortalAppearanceSettingRepositoryInterface.php`** — Contrato (`findByCompanyId`, `updateOrCreateForCompany`) para manter o padrão Controller → Service → Repository.

- **`app/Repositories/Eloquent/PortalAppearanceSettingRepository.php`** — Implementação com Eloquent; consultas e escrita ficam isoladas do service.

- **`app/Services/PortalAppearanceSettingService.php`** — Orquestra leitura para layout (`themeForLayout`), modelo para edição (`modelForEdit`) e `updateForCompany`: validação já feita no FormRequest, upload no disco `public`, exclusão de arquivos antigos, transação `DB::transaction`, e integração com o normalizador de favicon quando aplicável.

- **`app/Support/PortalAppearanceTheme.php`** — Objeto imutável com cores e URLs de assets para injeção nas views. `fromModel` monta o tema a partir do registro; `urlForStoredPath` gera URL **relativa** `/storage/...` para evitar divergência entre `APP_URL` e o host real do navegador.

- **`app/Support/PortalFaviconNormalizer.php`** — Para JPEG/PNG/GIF/WebP, com **GD** disponível, gera PNG temporário **32×32** (padrão comum de favicon). ICO/SVG ou ausência de GD retornam “sem processamento” e o service grava o arquivo original.

- **`app/Policies/PortalAppearanceSettingPolicy.php`** — Autoriza `update` só para usuário da mesma `company_id` do registro e com permissão de menu de configuração do portal. Garante isolamento multi-empresa na personalização visual.

- **`app/Http/Requests/Settings/UpdatePortalAppearanceSettingRequest.php`** — Regras de validação (cores em hex 6 dígitos, arquivos opcionais com tamanho máximo, flags booleanas de remoção). `authorize()` amarra ao modelo por empresa.

- **`app/Http/Controllers/Settings/PortalAppearanceController.php`** — Ações `edit` e `update`: autorização via policy, delegação ao service, redirect com mensagem de sucesso. Sem regra de negócio no controller.

### Portal e autenticação (HTTP, middleware, layout)

- **`app/Http/Controllers/Portal/PortalHomeController.php`** — Página inicial autenticada do **portal** (participante). Ponto de entrada da experiência de treinamento após escolha de contexto.

- **`app/Http/Controllers/Auth/PostLoginContextController.php`** — Após login bem-sucedido, define `session('active_portal')` como `admin` ou `portal` conforme `AccountKind` e capacidades (ex.: quem é só portal vai direto ao portal; staff pode ir à gestão quando aplicável).

- **`app/Http/Controllers/Auth/PostLoginChooseController.php`** — Quando o usuário pode **ambos** os mundos, exibe a escolha e grava o contexto na sessão ao confirmar. Desacopla a decisão do `AuthenticatedSessionController`.

- **`app/Http/Middleware/EnsureManagementContext.php`** — Bloqueia ou redireciona acesso a rotas de **gestão** se a sessão não estiver no contexto administrativo. Evita que participantes acessem URLs de admin colando o path.

- **`app/Http/Middleware/EnsurePortalContext.php`** — Garante o inverso: rotas do **portal** exigem contexto portal na sessão, mantendo simetria de segurança de navegação.

- **`app/View/Components/PortalLayout.php`** — Componente de layout que resolve o tema (`PortalAppearanceSettingService`) e repassa `portalTheme` à view, para que logo/cores/favicon reflitam a configuração da empresa.

### Views Blade

- **`resources/views/layouts/admin.blade.php`** — Shell da área **de gestão** (substitui/evolui o antigo `layouts/app` neste fluxo). Agrupa sidebar/topbar administrativos e conteúdo.

- **`resources/views/layouts/portal.blade.php`** — Documento HTML do portal: variáveis CSS (`:root`), favicon condicional, `main` com cor ou imagem de fundo. Base visual do participante.

- **`resources/views/layouts/portal-sidebar.blade.php`** — Navegação lateral do portal (início, perfil, ir para gestão quando permitido), área de marca com logo centralizada e tamanho confortável.

- **`resources/views/layouts/portal-topbar.blade.php`** — Barra superior com identificação do usuário, ações e estilo alinhado ao tema do portal.

- **`resources/views/portal/home.blade.php`** — Conteúdo da home do portal (mensagem de boas-vindas / placeholder até existirem trilhas e questionários).

- **`resources/views/portal/profile/edit.blade.php`** — Edição de perfil no **contexto portal** (layout e rotas coerentes com o participante).

- **`resources/views/settings/portal-appearance/edit.blade.php`** — Formulário de **Aparência do portal**: upload de imagens, pré-visualização dos arquivos atuais, seletores de cor, checkboxes para remover assets, texto explicando favicon 32×32 e dependência do GD.

- **`resources/views/auth/post-login-choose.blade.php`** — Tela em que o usuário escolhe **Gestão** ou **Portal** quando ambos são permitidos.

### Testes automatizados

- **`tests/Feature/Settings/PortalAppearanceSettingTest.php`** — Garante que usuário sem permissão recebe 403 na edição de aparência; usuário com permissões corretas atualiza cores; com extensão **GD**, upload de PNG de favicon vira arquivo 32×32 no storage.

- **`tests/Feature/Layout/ManagementTopbarPortalLinkTest.php`** — Cobre o link “Portal” na gestão e regras de exibição conforme `AccessRules` / tipo de conta.

- **`tests/Feature/Profile/ProfileLayoutContextTest.php`** — Garante que a edição de perfil usa o layout esperado (gestão vs portal) conforme contexto da sessão.

- **`tests/Feature/Users/UserRolesAuthorizationTest.php`** — Autorização ao atribuir roles (quem pode alterar roles de quem).

- **`tests/Feature/Users/GlobalLegacyAdminUserVisibilityTest.php`** — Usuários com role global legada de admin não aparecem em listagens operacionais por empresa quando a regra de negócio exige ocultação — evita “vazar” contas especiais para gestores de tenant.

---

## Inventário — arquivos já existentes, porém alterados

Estes arquivos já estavam no repositório; foram **editados** para encaixar portal, contexto de sessão, tipo de conta, aparência, visibilidade de usuários e testes. A lista reflete o estado do `git diff` no ambiente de desenvolvimento.

### Documentação e dependências

- **`README.md` (raiz)** — Inclusão de link para a pasta `docs/` e a documentação da plataforma de treinamento, para quem clona o repo achar o histórico rapidamente.

- **`composer.json`** — No script `composer setup`, inclusão de `php artisan storage:link --force` para que `public/storage` aponte a `storage/app/public` após instalação; necessário para URLs `/storage/...` de logo, favicon e fundo.

### Rotas e bootstrap

- **`routes/web.php`** — Agrupamento de rotas do **portal** (middleware `auth`, contexto portal), rotas de **gestão** com `EnsureManagementContext`, rotas de **pós-login** (escolha de contexto), rotas de **configurações** (`settings/portal-appearance`), e coerência de nomes com policies. É o mapa de navegação entre os dois modos.

- **`bootstrap/app.php`** — Registro dos aliases dos middlewares `management.context` e `portal.context` (ou nomes equivalentes usados no projeto), para aplicá-los nos grupos de rotas.

### Enums e regras de acesso

- **`app/Enums/MenuPermission.php`** — Novo item de permissão para o menu de **configurações do portal** (ex.: `menu.settings.portal`), usado na policy e no sidebar para exibir “Aparência do portal” só quem pode.

- **`app/Support/AccessRules.php`** — Funções auxiliares: quem pode acessar o portal, quem pode editar aparência, e `shouldHideUsersWithGlobalLegacyAdminRole` para filtros de listagem. Centraliza decisões que antes poderiam espalhar-se nos controllers.

### Models

- **`app/Models/User.php`** — Cast de `account_kind` para `AccountKind`, relação `traineeProfile()`, e métodos auxiliares de capacidade (ex.: pode acessar gestão, portal) usados no login e nas views.

- **`app/Models/Company.php`** — Relação `portalAppearanceSetting()` (`hasOne`) para carregar tema da empresa quando necessário.

### Usuários (service, controller, requests, policy, repositórios)

- **`app/Services/UserService.php`** — Criação/atualização de usuário respeitando `account_kind`, provisionamento de perfil de trainee quando `portal_only`, e regras de negócio alinhadas ao cadastro da Hypera vs participante.

- **`app/Http/Controllers/UserController.php`** — Passa dados necessários às views de CRUD de usuário (incluindo opções de tipo de conta e visibilidade conforme policy).

- **`app/Http/Requests/Users/StoreUserRequest.php`** — Validação na criação de usuário, incluindo `account_kind` e campos condicionais (ex.: senha obrigatória para novo participante quando aplicável).

- **`app/Http/Requests/Users/UpdateUserRequest.php`** — Validação na edição, inclusive transição de tipo de conta quando permitido pela policy.

- **`app/Http/Requests/Users/UpdateUserRolesRequest.php`** — Validação ao sincronizar roles, respeitando quem pode atribuir quais papéis.

- **`app/Policies/UserPolicy.php`** — Regras de autorização atualizadas para criação/edição/visualização de usuários por empresa, tipo de conta e roles (evita participante gerenciar equipe, etc.).

- **`app/Repositories/Contracts/UserRepositoryInterface.php`** — Assinatura de métodos de listagem que passam a aceitar flag para **excluir** usuários com admin global legado das combos/listas.

- **`app/Repositories/Eloquent/UserRepository.php`** — Implementação de `allActiveByCompany` (e correlatos) com filtro opcional de exclusão, usada em equipamentos, setores, Uber, etc.

- **`app/Repositories/Contracts/RoleRepositoryInterface.php`** / **`app/Repositories/Eloquent/RoleRepository.php`** — Ajustes para listar roles por empresa e apoiar o fluxo de atribuição no novo modelo de permissões.

### Autenticação e perfil

- **`app/Http/Controllers/Auth/AuthenticatedSessionController.php`** — Após credenciais válidas, delega ao fluxo de **contexto** (post-login) em vez de assumir sempre o dashboard administrativo.

- **`app/Http/Controllers/Auth/RegisteredUserController.php`** — Registro público (se habilitado) alinhado a `company_id` / tipo de conta conforme política do projeto.

- **`app/Http/Middleware/EnsureCanAccessDashboard.php`** — Restrições para quem não deve cair no dashboard de gestão (ex.: `portal_only`), redirecionando para o portal quando for o caso.

- **`app/Http/Controllers/ProfileController.php`** — Escolhe layout/redirect de perfil conforme contexto ativo (`admin` vs `portal`), para a mesma rota de perfil não quebrar a experiência do participante.

### Providers

- **`app/Providers/AppServiceProvider.php`** — Binding da interface `PortalAppearanceSettingRepositoryInterface` para a implementação Eloquent.

- **`app/Providers/AuthServiceProvider.php`** — Registro da `PortalAppearanceSettingPolicy` (e demais policies necessárias ao módulo).

### Layouts e componentes Blade (gestão)

- **`app/View/Components/AppLayout.php`** — Passa `portalTheme` e flags como `canEnterManagement` para o shell administrativo quando o layout precisa refletir identidade do portal ou links de troca de contexto.

- **`resources/views/layouts/sidebar.blade.php`** — Seção **Configurações** com link para aparência do portal condicionado a `AccessRules::portalAppearanceSettings`.

- **`resources/views/layouts/topbar.blade.php`** — Link/ação para ir ao **Portal** quando o usuário tem permissão de acesso ao portal no contexto de gestão.

- **`resources/views/layouts/app.blade.php`** — **Removido** no working tree atual: o shell da gestão passou a ser `layouts/admin`, unificando sidebar/topbar e evitando manter dois layouts equivalentes. Quem ainda referenciasse `layouts/app` deve migrar para `x-app-layout` / `admin` conforme o padrão do projeto.

- **`resources/views/components/page/header.blade.php`** — Declaração explícita de `@props(['title'])` para compatibilidade com o uso `<x-page.header title="...">` nas novas telas.

- **`resources/views/components/partials/actions.blade.php`** — Pequenos ajustes de ações em listagens (ícones/links) alinhados ao restante do UI.

- **`resources/views/users/_form.blade.php`** — Campo **Tipo de conta** (`account_kind`), textos de ajuda e fluxo visual para equipe vs participante.

- **`resources/views/users/roles.blade.php`** — Atribuição de roles coerente com o catálogo e com as permissões que o gestor pode delegar.

- **`resources/views/profile/edit.blade.php`** — Encaminha ou compõe o fluxo de perfil conforme o layout ativo.

- **`resources/views/profile/partials/update-profile-information-form.blade.php`** — Ajustes de formulário de dados básicos do perfil.

- **`resources/views/profile/partials/delete-user-form.blade.php`** — Ajustes de exclusão de conta no contexto dos novos layouts.

### Seeders e factories

- **`database/seeders/InitialSetupSeeder.php`** — Passa a disparar o `RoleCatalogSeeder` (ou equivalente) para que ambientes novos já tenham permissões e roles consistentes com o portal.

- **`database/seeders/DatabaseSeeder.php`** — Ordem de chamada dos seeders atualizada para não quebrar dependências.

- **`database/factories/UserFactory.php`** — Estado padrão de `account_kind` (e campos relacionados) para testes e seeds refletirem o novo modelo.

### Módulos operacionais existentes (inventário, Uber)

Alterados para que listas de usuários (`allActiveByCompany`) **respeitem** `AccessRules::shouldHideUsersWithGlobalLegacyAdminRole`, evitando exibir contas globais em selects de responsável, motorista, etc.

- **`app/Http/Controllers/Inventory/EquipmentController.php`**
- **`app/Http/Controllers/Inventory/EquipmentAssignmentController.php`**
- **`app/Http/Controllers/Inventory/CorporateNumberAssignmentController.php`**
- **`app/Http/Controllers/Inventory/SectorController.php`**
- **`app/Http/Controllers/Uber/CorporateRideRequestController.php`**

### Testes

- **`tests/Feature/Auth/AuthenticationTest.php`** — Login, redirecionamento e contexto após autenticação (gestão vs portal).

- **`tests/Feature/Auth/RegistrationTest.php`** — Registro alinhado a empresa / tipo de conta quando aplicável.

- **`tests/Feature/ExampleTest.php`** — Ajuste pontual para o estado atual da aplicação (rota inicial ou smoke test), evitando falso vermelho na suíte.

---

## Próximos passos sugeridos (produto)

1. **Import de usuários** — job/command, validação, mapeamento de colunas, idempotência.
2. **Modelo de questionários no portal** — tentativas, correção ou pontuação, prazo; ligação opcional a `operation_events` quando o produto exigir.
3. **BI** — agregações, export, dashboards por empresa/evento (alinhado ao contrato `Company` → `OperationClient` → `OperationEvent` quando couber).

---

## Manutenção deste documento

Atualizar após cada entrega relevante: data, resumo no topo, novos arquivos na seção **novos**, e alterações relevantes na seção **alterados**. Secções temáticas longas (ex.: **Login Microsoft**, **Correio**) podem crescer no meio do documento com inventário próprio. Entregas **por data** e **Correção SCAN** ficam em [registro-entregas.md](./registro-entregas.md). Para o detalhe linha a linha, use sempre `git log` / `git diff` como fonte da verdade.
