# Lote — Baterias, escopo N-N e usuários multi-área/grupo (2026-05-22)

Documento para **commit** da working tree atual.

**Estado:** pendente commit em `main` (`git status`).

**Documentação índice:** [registro-entregas.md](./registro-entregas.md) · [README.md](./README.md)

---

## Não incluir no commit

| Caminho | Motivo |
|---------|--------|
| `.agents/`, `.claude/`, `skills-lock.json` | Config local de agentes/skills |
| `plataforma-treinamento-hypera.pptx` | Apresentação local |

---

## 1. Resumo do produto

| Área | O que entrou |
|------|----------------|
| **Listagem de baterias** | Coluna **Progresso**: %, barra, iniciaram / pendentes / total na bateria |
| **Importação XLSX (baterias)** | Várias baterias por planilha; múltiplos pares Área+Grupo; auto-criação de área/grupo |
| **Escopo N-N** | «VMA E VMR» = **duas áreas**; vários grupos na mesma bateria/usuário |
| **Import planilha** | Alternativas sem `A)`; correta 1–4 ou A–D; tags com `e` / `-` |
| **Detalhe da bateria** | Editar **nome** (PATCH) |
| **Criar / editar usuário** | Multiselect **várias áreas** e **vários grupos** |
| **Import usuários** | Mesma regra N-N da planilha (área composta, vários grupos) |
| **Portal** | Participante com qualquer combinação área+grupo válida vê a bateria publicada |
| **Correção** | `progressStatsByBatteryIds` implementado (evita fatal 500 na listagem) |

---

## 2. Escopo N-N (regra de negócio central)

### Interpretação na planilha e formulários

| Entrada | Significado |
|---------|-------------|
| Área `VMA E VMR` | Setores **VMA** + **VMR** (não um setor chamado «VMR e VMA») |
| Grupo `MARCAS E SG` | Grupos **MARCAS** e **SG** (por área, quando área composta) |
| Grupo `TO` + Área `VMA E VMR` | Grupo **TO** em VMA e em VMR |

Separador: ` e ` ou `-` (mesma lógica das tags). Implementado em `BatteryScopeLabels`.

### Tabelas (migration `2026_05_22_160000_add_training_scope_many_to_many`)

| Tabela | Relação |
|--------|---------|
| `question_battery_sectors` | Bateria ↔ N setores |
| `question_battery_training_groups` | Bateria ↔ N grupos |
| `user_sectors` | Usuário ↔ N setores |
| `user_training_groups` | Usuário ↔ N grupos |

**Compatibilidade:** `question_batteries.sector_id` / `training_group_id` e `users.sector_id` + `trainee_profiles.training_group_id` guardam o **primeiro** vínculo. Backfill na migration copia dados legados para os pivôs.

### Elegível para progresso / portal

Participante elegível para uma bateria se:

1. Ativo, mesma empresa  
2. Está em **≥1 área** da bateria (`users.sector_id` ou `user_sectors`)  
3. Está em **≥1 grupo** da bateria (`user_training_groups` ou `trainee_profiles`)

**Iniciou** = tem sessão em `questionnaire_participant_sessions` para aquela bateria (com escopo validado).

---

## 3. Progresso na listagem (`/training/question-batteries`)

| UI | Significado |
|----|-------------|
| Barra + **%** | `iniciaram / elegíveis × 100` |
| **X iniciaram** | Com sessão |
| **Y pendentes** | Elegíveis sem sessão |
| **de Z na bateria** | Total elegível |

Sem setor/grupo ou sem elegíveis → **—**.

```
QuestionBatteryController@index
  → QuestionBatteryService::paginateWithProgress()
  → QuestionBatteryRepository::progressStatsByBatteryIds()
  → datatable type: battery-progress
```

Atributos no model (não persistidos): `progress_eligible`, `progress_started`, `progress_pending`, `progress_percent`.

---

## 4. Editar nome da bateria

| Método | Rota | Nome |
|--------|------|------|
| PATCH | `/training/question-batteries/{question_battery}` | `training.question-batteries.update` |

Secção na view `show` → partial `_edit-title.blade.php`. Policy `update`.

---

## 5. Importação de baterias

| Cenário | Comportamento |
|---------|---------------|
| Vários pares Área/Grupo na planilha | **Uma bateria por par** (áreas/grupos parseados com N-N) |
| Área composta `VMR e VMA` | Pivô com setores VMR + VMA |
| Área/grupo inexistentes | `resolveOrCreate` (como import usuários) |
| 1 bateria criada | Redirect `show` |
| Várias baterias | Redirect `index` + resumo |
| Append | Exige **um único** par Área/Grupo na planilha |

**Support classes:** `BatteryQuestionAlternativeText`, `BatteryQuestionTags`, `BatteryScopeLabels`.  
**Service:** `QuestionBatteryScopeService` (resolve IDs, sync pivôs, match escopo).

---

## 6. Criar e editar usuário

### Tela (`resources/views/users/_form.blade.php`)

- **Áreas (setores)** — `<select multiple name="sector_ids[]">`
- **Grupos** — `<select multiple name="training_group_ids[]">` (optgroup por setor)
- Dica: Ctrl/⌘ para multiseleção

### Backend

| Camada | Ficheiro |
|--------|----------|
| Validação | `StoreUserRequest`, `UpdateUserRequest` + trait `NormalizesUserTrainingScopeInput` |
| Negócio | `UserService::syncParticipantTrainingScopes()` |
| Controller | `UserController@edit` carrega `sectors`, `trainingGroups` |

Ao salvar: sync `user_sectors`, `user_training_groups`; `trainee_profile` com primeiro grupo; `users.sector_id` = primeira área.

Grupos selecionados **incluem automaticamente** as áreas desses grupos na validação (`prepareForValidation`).

---

## 7. Importação de usuários (fila)

`UserBulkImportService` usa `QuestionBatteryScopeService::resolveScopeIdsForImport` + `attachParticipantScopesFromImport` — mesma regra N-N da planilha (colunas Área / Grupo).

---

## 8. Portal e policies

- `PortalQuestionnaireService::participantScopeCombinations()` — todas combinações área × grupo do usuário  
- `resolveBatteryForUser()` — primeira bateria publicada que bater  
- `QuestionBatteryPolicy` — match via escopo N-N  
- `QuestionBatteryRepository::findPublishedForParticipantScope()` — `whereHas` nos pivôs

---

## 9. Inventário completo para `git add`

### Novos (19 ficheiros de código + doc)

```
app/Http/Requests/Training/UpdateQuestionBatteryRequest.php
app/Http/Requests/Users/Concerns/NormalizesUserTrainingScopeInput.php
app/Services/QuestionBatteryService.php
app/Services/QuestionBatteryScopeService.php
app/Support/BatteryQuestionAlternativeText.php
app/Support/BatteryQuestionTags.php
app/Support/BatteryScopeLabels.php
database/migrations/2026_05_22_160000_add_training_scope_many_to_many.php
docs/lote-baterias-import-progresso-2026-05-22.md
resources/views/training/question-batteries/_edit-title.blade.php
tests/Unit/Repositories/QuestionBatteryRepositoryProgressStatsTest.php
tests/Unit/Support/BatteryQuestionAlternativeTextTest.php
tests/Unit/Support/BatteryQuestionTagsTest.php
tests/Unit/Support/BatteryScopeLabelsTest.php
```

### Alterados (24 ficheiros)

```
app/Http/Controllers/Training/QuestionBatteryController.php
app/Http/Controllers/UserController.php
app/Http/Requests/Users/StoreUserRequest.php
app/Http/Requests/Users/UpdateUserRequest.php
app/Models/QuestionBattery.php
app/Models/TraineeProfile.php
app/Models/User.php
app/Policies/QuestionBatteryPolicy.php
app/Repositories/Contracts/QuestionBatteryRepositoryInterface.php
app/Repositories/Eloquent/QuestionBatteryRepository.php
app/Services/PortalQuestionnaireService.php
app/Services/QuestionBatteryImportService.php
app/Services/UserBulkImportService.php
app/Services/UserService.php
docs/README.md
docs/registro-entregas.md
resources/views/components/list/datatable.blade.php
resources/views/training/question-batteries/import.blade.php
resources/views/training/question-batteries/index.blade.php
resources/views/training/question-batteries/show.blade.php
resources/views/users/_form.blade.php
routes/web.php
tests/Feature/Training/QuestionBatteryImportTest.php
tests/Feature/Users/UserTraineeGroupUpdateTest.php
```

### Comando sugerido para staging

```bash
git add \
  app/Http/Controllers/Training/QuestionBatteryController.php \
  app/Http/Controllers/UserController.php \
  app/Http/Requests/Training/UpdateQuestionBatteryRequest.php \
  app/Http/Requests/Users/ \
  app/Models/QuestionBattery.php \
  app/Models/TraineeProfile.php \
  app/Models/User.php \
  app/Policies/QuestionBatteryPolicy.php \
  app/Repositories/Contracts/QuestionBatteryRepositoryInterface.php \
  app/Repositories/Eloquent/QuestionBatteryRepository.php \
  app/Services/PortalQuestionnaireService.php \
  app/Services/QuestionBatteryImportService.php \
  app/Services/QuestionBatteryScopeService.php \
  app/Services/QuestionBatteryService.php \
  app/Services/UserBulkImportService.php \
  app/Services/UserService.php \
  app/Support/ \
  database/migrations/2026_05_22_160000_add_training_scope_many_to_many.php \
  docs/lote-baterias-import-progresso-2026-05-22.md \
  docs/README.md \
  docs/registro-entregas.md \
  resources/views/components/list/datatable.blade.php \
  resources/views/training/question-batteries/ \
  resources/views/users/_form.blade.php \
  routes/web.php \
  tests/Feature/Training/QuestionBatteryImportTest.php \
  tests/Feature/Users/UserTraineeGroupUpdateTest.php \
  tests/Unit/Repositories/QuestionBatteryRepositoryProgressStatsTest.php \
  tests/Unit/Support/
```

---

## 10. Deploy

```bash
php artisan migrate
php artisan test --filter='QuestionBatteryImportTest|QuestionBatteryRepositoryProgressStatsTest|BatteryQuestionAlternativeTextTest|BatteryQuestionTagsTest|BatteryScopeLabelsTest|UserTraineeGroupUpdateTest'
vendor/bin/pint --dirty
```

| Item | Notas |
|------|--------|
| Migration | Obrigatória antes de usar listagem/import/CRUD usuário com N-N |
| `npm run build` | Não necessário (só Blade) |
| Fila | Inalterada (import usuários continua em fila se já usava) |

### Smoke manual pós-deploy

1. **Baterias** — listagem com coluna Progresso  
2. **Import** — planilha com `VMR e VMA` + grupos distintos → 2 setores na bateria, não 1 «VMR e VMA»  
3. **Usuário** — criar com 2 áreas + 2 grupos; editar e conferir seleção  
4. **Portal** — login participante multi-escopo; bateria publicada aparece  
5. **Editar título** da bateria no detalhe  

### Baterias legadas

Registros antigos com setor único «VMR e VMA» **não** são divididos automaticamente. Reimportar ou ajustar manualmente se precisar do N-N.

---

## 11. Testes automatizados

### Feature

| Classe | Testes relevantes |
|--------|-------------------|
| `QuestionBatteryImportTest` | Import multi-bateria, tags, alternativas, progresso index, PATCH título, áreas compostas |
| `UserTraineeGroupUpdateTest` | Update grupo, limpar, **create multi área/grupo** |

### Unit

| Classe | Cobertura |
|--------|-----------|
| `BatteryScopeLabelsTest` | Parse `VMA E VMR`, chave de agrupamento |
| `BatteryQuestionAlternativeTextTest` | Prefixo + letra correta |
| `BatteryQuestionTagsTest` | Tags `e` / `-` |
| `QuestionBatteryRepositoryProgressStatsTest` | eligible / started / pending |

---

## 12. Mensagem de commit sugerida

```
feat(training): baterias com progresso, import N-N e usuários multi-área/grupo

- Progresso na listagem (%, barra, iniciaram/pendentes)
- Import multi-bateria; VMA E VMR como duas áreas; pivôs N-N
- CRUD usuário com sector_ids[] e training_group_ids[]
- Edição de título da bateria; normalização alternativas/tags na planilha
```

---

## 13. Checklist antes do push

- [ ] `git add` conforme secção 9 (sem `.agents/`, `.claude/`, `.pptx`, `skills-lock.json`)
- [ ] `php artisan migrate` em dev/staging
- [ ] Testes da secção 10 verdes
- [ ] `vendor/bin/pint --dirty`
- [ ] Smoke manual secção 10
