# Módulo {{ module }} (`{{ group }}/{{ module }}`)

> Contrato de leitura do módulo (ADR-004/ADR-014) — a primeira coisa que uma IA/humano lê antes de
> tocar no código. Gerado por `make:module`; mantenha-o sincronizado com a realidade.

## Propósito

<!-- 2–3 linhas: o que o domínio resolve e o que fica de fora. -->

- **Grupo:** `{{ group }}` (organizacional — **não** entra no token, ADR-014).
- **Áreas:** portal · CMS · API (ver "Rotas").
- **Ativação:** dado em `core_module_discovery_settings`; ligue/desligue com
  `php artisan core:module:activate {{ module }} [--disable]` (ADR-034).

## Estrutura (contrato físico)

```
src/modules/{{ group }}/{{ module }}/
├── ModuleProvider.php        # ÚNICO arquivo de declaração (register + config) — ADR-014
├── MODULE.md                 # este contrato
├── Database/
│   ├── Migrations/           # ADR-017: idempotentes; down()->backup_<tabela>_<ts>
│   ├── Models/               # Eloquent; conteúdo em jsonb (nunca HTML/CSS/flag de UI)
│   └── Seeders/              # demo apenas
├── Logic/
│   ├── Actions/              # 1 Action por arquivo (DI + span/log {{ module_lower }}.{ação})
│   ├── Controllers/          # invocáveis (sem closure — ADR-008)
│   ├── DTOs/                 # readonly, sem herança
│   └── Requests/             # FormRequest (validação)
├── Routes/{portal,cms,api}.php
├── Layout/Views/             # namespace = {{ module_lower }} (convenção do Core)
├── Lang/{pt_BR,en,es,fr}/    # strings do módulo (ADR-012)
└── Test/                     # unit/feature/contrato
```

## Token canônico (ADR-004)

**`{{ module_lower }}`** — toda classe, view, rota, migration, tabela, teste, slot e log do módulo
carrega esse prefixo. Verificação: `tools/name-check.sh --grep {{ module_lower }}` (≥1 Model, Action,
View, Migration e Test) e o `Adr004GrepFriendlyTest` na cadeia do `make check`.

## Dependências

- **Services** (import apenas de `Contracts`/`DTOs`/`Enums` — ADR-015): <!-- ex.: Media, RichText, Seo, Audit -->
- **Outros módulos:** **nunca** por import — só Events/Listeners nativos e portas do Core (ADR-013).
- **Tabelas:** `{{ table }}` (+ relações com o Core).

## Rotas

| Nome | Método | URI | Controller | Acesso |
|---|---|---|---|---|
| `{{ module_lower }}.index` | GET | `/{{ module_lower }}` | `{{ module }}WebController` | pública |
| `{{ module_lower }}.api.status` | GET | `/{{ module_lower }}/status` | `{{ module }}ApiController` | API |

<!-- Ajuste. Rotas nomeadas, sem closure (ADR-008).
     A área CMS nasce **sem rota** (`Routes/cms.php` só traz o exemplo comentado): crie o controller
     e a rota e só então acrescente a linha real aqui (ex.: `{{ module_lower }}.cms.index` — item de
     painel só aparece com rota real da área CMS). -->

## Permissões (ADR-033)

Declaradas em `ModuleProvider::config()` (`'permissions' => Permission::ACTIONS`) e aplicadas com
`Permission::authorize()`/`allows()` (**deny-by-default**): `{{ module_lower }}.{ver|editar|aprovar|arquivar}`.

## Eventos e observabilidade (ADR-024/ADR-035)

- **Emite:** `{{ module_lower }}.execute` via `log_event('{{ module_lower }}.execute', ['module' => …, 'action' => …])`
  e span `{{ module_lower }}.execute` (atributos `scalar|null`, **sem PII**).
- **Auditoria:** mutações entram na trilha pelo `GlobalAuditSubscriber` (ADR-035).
- **Consome:** <!-- listeners em CoreEvent/ quando houver -->

## Layout (ADR-016)

- View pública usa **um único** `<x-layout::portal>`; a do CMS usa `<x-layout::cms>` (só o `<main>`).
- Slots publicados (se houver): <!-- home.destaques, aside.relacionados -->

## Widgets (ADR-016)

- Widgets do módulo implementam `Core\Contracts\Ports\WidgetSource` e são registrados no
  `WidgetRegistry` do Core no `register()` — o perfil de destino referencia apenas o `id`
  (**embed permitido, import proibido**, ADR-013).
- Fragmento do widget em `Layout/Views/widgets/` (nunca view de página; sem shell — ADR-016).
- <!-- ex.: `demo.status` → `Widgets/StatusWidget.php` -->

## Comandos

```bash
php artisan core:module:activate {{ module }}     # ativa (data — ADR-034)
php artisan core:module:activate {{ module }} --disable
php artisan migrate                                # migrations por convenção
```

## Convenções (checklist)

- [ ] `declare(strict_types=1)`; DI (sem `new`/`resolve` no corpo); sem traits/abstract/generics.
- [ ] Nada de HTML/CSS/flag de UI no banco (`jsonb` — ADR-017).
- [ ] Logs JSON + `log_event` sem PII (ADR-024); mutação auditada (ADR-035).
- [ ] 4 locales no `Lang/` com paridade de chaves (`ModuleI18nTest`).
- [ ] A11y eMAG/WCAG 2.2 AA; alvos ≥ 2.75rem no mobile (ADR-025).

## Verificação

```bash
make exec cmd='bash tools/name-check.sh --grep {{ module_lower }}'
make exec cmd='php tools/arch-check' && make exec cmd='php tools/migration-check'
make check            # cadeia completa (lint, PHPStan max, gates, testes, cobertura)
make check E2E=1      # + e2e/axe/sniff/perf quando a UI muda
```
