---
id: MOD-_EXEMPLOS-EXEMPLOAPI
tipo: modulo
onda: 1
status: concluido
unidades: [MOD-_EXEMPLOS-EXEMPLOAPI]
fontes:
  - V2/Modules/_Exemplos/ExemploAPI/
depende_de: [CORE-BOOT, MOD-INDEX]
consumidores: [GRP-_EXEMPLOS]
criticidade: baixa
dono: BootNavigation
---

# _Exemplos/ExemploAPI

> Módulo de referência: quatro arquivos, nenhuma tabela, nenhuma persistência. Serve como contrato de forma para novos módulos V2 e como caso real de módulo restrito ao suporte.

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Expõe uma tela CMS somente-leitura com dois registros fictícios e uma action de escrita que valida e redireciona sem gravar. |
| Entrada | `GET /cms/exemplo-api`; `POST /cms/exemplo-api/demo-store` (`titulo`, `descricao`). |
| Saída | HTML no layout `v2-cms::layouts.app`; `302` para `v2.exemplo-api.index` com `success`; `403` para quem não é suporte/super-admin. |
| Efeitos colaterais | Nenhum no banco; nenhum log; apenas flash de sessão. O grupo `_Exemplos` é descoberto e ocupa o namespace de view `v2-exemploapi`. |
| Particularidade central | É o único módulo do sistema que combina **duas camadas de restrição por e-mail** (provider + controller) e ainda permite `super-admin`. |
| Como se integra | Rota `v2.exemplo-api.index`; view `v2-exemploapi::cms.index`; chave de permissão `ExemploAPI`; grupo `_Exemplos`. |
| Se removido, quebra | Nada em runtime; perde-se o template de referência citado por `V2/README.md:28` e `V2/Modules/README.md:24`. |
| Superfície pública | `ExemploAPICmsController@index`, `@demoStore`; `ModuleProvider::getConfig()`. |

## 1. Efeito

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

- **Suporte (`config('v2.support.email')`) e `super-admin`**: abrir `/cms/exemplo-api`, ver uma tabela com `Exemplo de Registro 1` (`ativo`) e `Exemplo de Registro 2` (`rascunho`), um alerta informativo e um botão “Novo Exemplo”.
- **Demais usuários com papel Sentinel**: qualquer action do controller responde `403 Forbidden. ExemploAPI is restricted to suporte@maxima.inf.br.` (AJAX/JSON) ou redireciona para `v2.cms.dashboard` com `Acesso negado. O módulo ExemploAPI é restrito ao suporte técnico.` (`ExemploAPICmsController.php:48-53`).
- **Ninguém** consegue gravar: `demoStore` não persiste nada; o `TODO` está explícito (`:104-107`).

### 1.2 O que muda no sistema

| Efeito | Onde é produzido | Fonte |
|---|---|---|
| Namespace `v2-exemploapi` registrado | `strtolower('ExemploAPI')` no boot | `V2/Core/Providers/V2ServiceProvider.php:100,123` |
| Entrada no menu `_Exemplos` (só suporte/super-admin) | `ModuleProvider::getConfig()['restricted']` | `ModuleProvider.php:29`; `SidebarComposer.php:51-57` |
| Rotas CMS com `module.active:ExemploAPI` | `mapModuleRoutes` | `V2/Core/Providers/V2ServiceProvider.php:304` |
| Middleware de restrição por e-mail | construtor do controller | `ExemploAPICmsController.php:35-55` |
| Flash `success` após `demo-store` | redirect | `:109-110` |
| Nenhuma escrita em banco | ausência de model/create | `:104-107` |

### 1.3 Contrato observável

| Entrada | Resultado |
|---|---|
| `GET /cms/exemplo-api` autenticado como suporte | `200`, tabela com 2 linhas, flash opcional |
| `GET /cms/exemplo-api` sem autenticação | `302` para `v2.cms.login` (via `CmsAuthenticate`) |
| `GET /cms/exemplo-api` autenticado sem papel | `302` para `v2.cms.login` com erro de papel (via `CmsAuthenticate`) |
| `GET /cms/exemplo-api` autenticado com papel, não-suporte | `302` para `v2.cms.dashboard`, flash `error` do construtor |
| `POST /cms/exemplo-api/demo-store` com `titulo` vazio | `302` de volta com erro `O campo título é obrigatório.` |
| `POST /cms/exemplo-api/demo-store` com `titulo` > 255 | erro de validação padrão do Laravel para `max:255` |
| `POST /cms/exemplo-api/demo-store` válido com permissão `read` | `403 Acesso negado. Ação restrita a operadores com permissão de escrita.` |
| `POST /cms/exemplo-api/demo-store` válido com `write` | `302` para `v2.exemplo-api.index` + flash `Registro de exemplo criado com sucesso!` |

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

- Não cria, edita, lista ou apaga registros reais: `index` devolve array literal (`:66-77`) e `demoStore` não grava (`:104-107`).
- Não tem `Models/`, `Migrations/`, `Routes/web.php`, `Routes/api.php` nem `Views/portal/`.
- Não tem formulário: o botão “Novo Exemplo” não abre modal nem envia POST; `demo-store` não é alcançável pela UI (`Views/cms/index.blade.php:33-43`).
- Não registra auditoria: o comentário com `$this->activityLogger->log(...)` está comentado e `BaseController` **não** expõe `$activityLogger` (`V2/Core/Controllers/BaseController.php:15-23`).
- Não é “API CRUD”, apesar do README do grupo (`V2/Modules/_Exemplos/README.md:10`).

## 2. Abordagem

### 2.1 Estratégia

```mermaid
flowchart LR
  A[GET /cms/exemplo-api] --> B[web + module.active:ExemploAPI]
  B --> C[CmsAuthenticate -> V2PermissionMiddleware]
  C --> D[Controller __construct: email suporte ou super-admin]
  D -->|ok| E[index: array literal]
  E --> F[v2-exemploapi::cms.index]
  F --> G[layout v2-cms::layouts.app]
  A2[POST demo-store] --> B
  B --> C --> D
  D --> H[v2_can ExemploAPI write]
  H -->|false| I[abort 403]
  H -->|true| J[validate titulo/descricao]
  J --> K[redirect v2.exemplo-api.index + success]
```

O módulo demonstra seis convenções: namespace com grupo, provider com `restricted`, rota com prefixo e nome, controller herdando `BaseController`, `v2_can` para escrita e view com `@v2can` + acessibilidade.

### 2.2 Mecânica passo a passo

1. **Boot**: `V2/Modules/_Exemplos` é o grupo; `ExemploAPI` é o módulo; `V2\Modules\_Exemplos\ExemploAPI\ModuleProvider` é instanciado, `group` sobrescrito para `_Exemplos`, view namespace `v2-exemploapi`, rotas CMS mapeadas com `web` + `module.active:ExemploAPI` (`V2/Core/Providers/V2ServiceProvider.php:92-124,304`).
2. **Requisição**: o grupo `web` aplica sessão/CSRF/CmsAiToken; `CmsAuthenticate` exige Sentinel e ao menos um papel e delega a `V2PermissionMiddleware` (`CmsAuthenticate.php:21-44`).
3. **RBAC**: `V2PermissionMiddleware` casa `cms/exemplo-api*` contra `active_pattern`; `_Exemplos` não é `SUPPORT`, então aplica a permissão do módulo `ExemploAPI`; suporte e `super-admin` passam (`V2PermissionMiddleware.php:40-72`).
4. **Construtor**: repete a checagem por e-mail/role e barra o restante (`:35-55`).
5. **`index`**: devolve array literal e a view (`:64-82`).
6. **`demoStore`**: checa `v2_can('ExemploAPI','write')`, valida e redireciona (`:89-111`).
7. **View**: estende `v2-cms::layouts.app`, define breadcrumbs, ações e conteúdo; aplica `@v2can('ExemploAPI','write')` em dois pontos (`index.blade.php:24-43,136-143`).

### 2.3 Trechos de código pivô

```php
// V2/Modules/_Exemplos/ExemploAPI/ModuleProvider.php:20-39 — contrato de menu e restrição.
public function getConfig(): array
{
    return [
        'title' => '[DEV] Exemplo API',
        'description' => 'Módulo de referência para novos módulos. Restrito ao suporte.',
        'icon' => 'bi bi-compass',
        'position' => 1,
        'active_pattern' => 'cms/exemplo-api*',
        'restricted' => config('v2.support.email'),
        'subModulesLevel1' => [
            [
                'title' => 'Listagem',
                'icon' => 'bi bi-list-task',
                'route' => 'v2.exemplo-api.index',
                'position' => 1,
                'subModulesLevel2' => []
            ]
        ]
    ];
}
```

`active_pattern` é a chave que permite ao `V2PermissionMiddleware` identificar o módulo de uma requisição CMS; sem ele, o RBAC não casa e a rota é tratada como “fora de módulo” (`V2PermissionMiddleware.php:40-48,74-77`).

```php
// V2/Modules/_Exemplos/ExemploAPI/Routes/cms.php:20-31 — prefixo, auth e nomes explícitos.
Route::group([
    'prefix' => 'cms/exemplo-api',
    'middleware' => [\V2\Core\Middleware\CmsAuthenticate::class],
], function () {

    // Leitura — visível para quem tem permissão 'read' ou 'write'
    Route::get('/', 'ExemploAPICmsController@index')
        ->name('v2.exemplo-api.index');

    // Escrita — protegida por v2_can('ExemploAPI', 'write') no controller
    Route::post('/demo-store', 'ExemploAPICmsController@demoStore')
        ->name('v2.exemplo-api.demo-store');
});
```

O prefixo `cms/exemplo-api` não vem do provider; se o grupo fosse alterado para `prefix => ''`, a rota passaria a responder em `/`. O `name` é usado pelo `ModuleProvider` e pelo redirect.

```php
// V2/Modules/_Exemplos/ExemploAPI/Controllers/ExemploAPICmsController.php:39-54 — segunda camada.
$this->middleware(function ($request, $next) {
    $user = \Sentinel::getUser();

    // Super-admin (papel V1) ou suporte passam
    if ($user && ($user->email === config('v2.support.email') || $user->inRole('super-admin'))) {
        return $next($request);
    }

    // Demais usuários são barrados
    if ($request->ajax() || $request->wantsJson()) {
        return response('Forbidden. ExemploAPI is restricted to ' . config('v2.support.email') . '.', 403);
    }

    return redirect()->route('v2.cms.dashboard')
        ->with('error', 'Acesso negado. O módulo ExemploAPI é restrito ao suporte técnico.');
});
```

Middleware inline no construtor: roda antes de toda action, inclusive `demoStore`. A mensagem AJAX concatena o e-mail configurado — muda com `V2_SUPPORT_EMAIL`.

```php
// V2/Modules/_Exemplos/ExemploAPI/Controllers/ExemploAPICmsController.php:92-110 — RBAC fino e validação.
if (!\v2_can('ExemploAPI', 'write')) {
    abort(403, 'Acesso negado. Ação restrita a operadores com permissão de escrita.');
}

// Validação no estilo Laravel 5.5 (compatível com PHP 7.2)
$this->validate($request, [
    'titulo' => 'required|string|max:255',
    'descricao' => 'nullable|string|max:1000',
], [
    'titulo.required' => 'O campo título é obrigatório.',
]);

// TODO: lógica de persistência aqui
// ...
return redirect()->route('v2.exemplo-api.index')
    ->with('success', 'Registro de exemplo criado com sucesso!');
```

`safeOnly` não é usado: `$request` inteiro não é passado ao model porque não há model. A mensagem de erro de `titulo` é a única customizada; `descricao` usa o texto padrão do Laravel.

```blade
{{-- V2/Modules/_Exemplos/ExemploAPI/Views/cms/index.blade.php:24-43 — seções do layout e @v2can --}}
@section('page-breadcrumbs')
    <nav aria-label="Breadcrumb">
        <ol class="breadcrumb bg-transparent p-0 mb-0 small">
            <li class="breadcrumb-item"><a href="{{ route('v2.cms.dashboard') }}" class="text-decoration-none">Dashboard</a></li>
            <li class="breadcrumb-item active" aria-current="page">Exemplos</li>
        </ol>
    </nav>
@endsection

@section('page-actions')
    @v2can('ExemploAPI', 'write')
    <button type="button"
            class="btn btn-success fw-bold d-inline-flex align-items-center gap-1"
            aria-label="Criar novo registro de exemplo"
            title="Criar novo registro">
        <i class="bi bi-plus-circle" aria-hidden="true"></i>
        Novo Exemplo
    </button>
    @endv2can
@endsection
```

O botão não tem `data-bs-target` nem `onclick`; é um placeholder de UI. `aria-label` e `aria-hidden` nos ícones são o padrão de acessibilidade que o exemplo quer transmitir.

```blade
{{-- V2/Modules/_Exemplos/ExemploAPI/Views/cms/index.blade.php:104-126 — tabela acessível (cabeçalho e primeira linha) --}}
<table class="table table-striped table-hover align-middle mb-0"
       aria-label="Listagem de registros de exemplo">
    <caption class="visually-hidden">Relação de registros do módulo Exemplo API</caption>
    <thead class="table-light">
        <tr>
            <th scope="col" style="width: 5%;" class="text-center">#</th>
            <th scope="col" style="width: 30%;">Título</th>
            ...
```

`caption` visualmente oculto, `scope="col"` e `aria-label` na tabela são os três requisitos de acessibilidade que o exemplo demonstra; nenhum é validado automaticamente.

### 2.4 Decisões e restrições

- **Duas camadas de restrição** porque o `restricted` só age na sidebar e o RBAC por módulo poderia ser concedido a qualquer usuário; o construtor garante o e-mail.
- **Super-admin como exceção** porque o papel V1 precisa administrar tudo (`:42-43`).
- **Validação no controller** (não em FormRequest) para manter o exemplo copiável em PHP 7.2/Laravel 5.5 sem classes extras.
- **Sem persistência** de propósito: o exemplo não deve criar tabela fictícia em instalações reais.

## 3. Particularidades

### 3.1 O que só existe aqui

- **Único módulo do grupo `_Exemplos`** e o único documentado como “template vivo”.
- **Mensagem AJAX 403 em inglês** (`Forbidden. ExemploAPI is restricted to ...`) convivendo com mensagens de UI em português.
- **`active_pattern` em formato string** (`'cms/exemplo-api*'`), enquanto a maioria dos módulos usa array (`V2/Modules/Publicacoes/Noticias/ModuleProvider.php:40`).
- **View de referência com o próprio namespace escrito no rodapé**: `View: v2-exemploapi::cms.index` (`index.blade.php:155-162`), o que confirma a regra `strtolower`.

### 3.2 Invariantes

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| RN-EXEMPLOAPI-01 | Acesso total apenas para `config('v2.support.email')` ou `super-admin`. | Demais usuários: 403/redirect. | `ExemploAPICmsController.php:42-53` |
| RN-EXEMPLOAPI-02 | `demo-store` exige `v2_can('ExemploAPI','write')`. | Perfil `read` recebe 403. | `:92-94` |
| RN-EXEMPLOAPI-03 | `titulo` é obrigatório, string, máx. 255; `descricao` é opcional, máx. 1000. | Erro customizado só para `titulo.required`. | `:97-102` |
| RN-EXEMPLOAPI-04 | Namespace de view = `v2-exemploapi`. | Qualquer outro nome falha. | `V2ServiceProvider.php:100,123` |
| RN-EXEMPLOAPI-05 | Não há escrita em banco. | `demo-store` é um no-op. | `:104-107` |
| RN-EXEMPLOAPI-06 | O grupo `_Exemplos` é injetado pelo diretório. | Mover o módulo muda o menu. | `V2ServiceProvider.php:109` |

### 3.3 Casos-limite

- **Usuário suporte sem papel Sentinel**: `CmsAuthenticate` barra antes do construtor, pois exige `roles()->count() > 0` (`CmsAuthenticate.php:30-41`). O e-mail de suporte não bypassa a checagem de papel.
- **`super-admin` sem e-mail de suporte**: passa no construtor; na sidebar, `restricted` também permite super-admin (`SidebarComposer.php:53`).
- **`demo-store` com `descricao` > 1000**: erro de validação padrão, sem mensagem customizada.
- **CSRF ausente no POST**: `web` aplica `VerifyCsrfToken`; a resposta é `419`.
- **Chamada AJAX ao `index`**: o construtor retorna 403 texto puro; não há JSON do `index`.
- **Módulo desativado em `v2_module_flags`**: `module.active` responde antes do controller com `404 Modulo desativado.` (HTML) ou 404 JSON (AJAX), sem redirect (`ModuleAccessMiddleware.php:46-62`).
- **View com `@@v2can`**: a linha 87 do Blade escapa a diretiva (`@@v2can`) dentro do bloco informativo; renderiza literalmente `@v2can`, não executa.

### 3.4 Armadilhas de reimplementação

1. **Copiar `restricted` sem trocar a chave** — o módulo novo fica restrito ao suporte.
2. **Copiar o construtor** — módulo comum passa a exigir e-mail de suporte ou super-admin.
3. **Copiar o comentário `$this->activityLogger`** — a propriedade não existe em `BaseController`; o código comentado não funciona se descomentado.
4. **Assumir que `position` ordena** — a sidebar usa `title` (`SidebarComposer.php:85-90`).
5. **Usar `v2-exemplo-api::`** — o namespace real é `v2-exemploapi`; o comentário do arquivo de rotas induz ao erro.

### 3.5 Divergências documentação × código

| Documento | Afirma | Código |
|---|---|---|
| `V2/Modules/_Exemplos/README.md:10` | "Módulo de exemplo baseado em API CRUD" | Só rotas CMS; nenhuma API, nenhum CRUD (`Routes/cms.php:20-31`) |
| `V2/Modules/_Exemplos/README.md:18-19` | restrito "independentemente do perfil" | `super-admin` passa (`:43`; `SidebarComposer.php:53`) |
| `Routes/cms.php:18` | view namespace `v2-{snake_case_module}` | Real `v2-exemploapi` (`ExemploAPICmsController.php:79`) |
| `Views/cms/index.blade.php:87` | doc embarcada usa `@@v2can` | Escapa a diretiva; é texto informativo, não exemplo executável |
| `Controllers/...:16` | "PHP 7.2 compatível" | `composer.json:7` exige `>=7.4` |

## 4. Integrabilidade

### 4.1 Superfície pública

| Símbolo | Tipo | Assinatura | Precondição | Consumidores |
|---|---|---|---|---|
| `ExemploAPICmsController@index` | action | `index(): View` | Suporte/super-admin + rota autenticada | Rota `v2.exemplo-api.index` |
| `ExemploAPICmsController@demoStore` | action | `demoStore(Request $request): RedirectResponse` | `v2_can('ExemploAPI','write')` | Rota `v2.exemplo-api.demo-store` |
| `ModuleProvider::getConfig` | provider | `(): array` | — | `V2ServiceProvider`, `SidebarComposer` |

### 4.2 Rotas e contratos HTTP

| Tipo | Método | URI | Nome | Middleware | Controller@ação | Permissão | Efeito | Fonte |
|---|---|---|---|---|---|---|---|---|
| grupo | — | `cms/exemplo-api` | — | `CmsAuthenticate` | — | — | — | `Routes/cms.php:20-23` |
| endpoint | GET | `/cms/exemplo-api` | `v2.exemplo-api.index` | `web`, `module.active:ExemploAPI`, `CmsAuthenticate`, construtor | `ExemploAPICmsController@index` | `ExemploAPI:read|write` (ou suporte) | Renderiza 2 registros fictícios | `:26-27` |
| endpoint | POST | `/cms/exemplo-api/demo-store` | `v2.exemplo-api.demo-store` | idem + `v2_can` | `ExemploAPICmsController@demoStore` | `ExemploAPI:write` | Valida e redireciona sem gravar | `:30-31` |

Payload de `demo-store` (form-urlencoded): `titulo` (obrigatório, string ≤255), `descricao` (opcional, string ≤1000), `_token` (CSRF obrigatório). Resposta de sucesso: `302` `Location: /cms/exemplo-api` + flash `success`. Erros: `403` (permissão), `419` (CSRF), `302` com `errors` (validação).

### 4.3 Contrato de UI/Blade

| Elemento | Contrato | Fonte |
|---|---|---|
| Layout | `@extends('v2-cms::layouts.app', ['title' => 'Exemplo API — Referência para Devs'])` | `index.blade.php:20-22` |
| Seções | `page-breadcrumbs`, `page-actions`, `content` | `:24,33,45` |
| Variável | `$items`: array de `['titulo','descricao','status']` | `:79-81` |
| Diretiva | `@v2can('ExemploAPI','write')` esconde botões de escrita | `:34,136` |
| Classes CSS | Bootstrap 5 (`card`, `table`, `badge`, `btn`) + Bootstrap Icons | `:1-3` |
| A11y | `aria-label` em nav/botões/tabela, `aria-hidden` em ícones, `caption.visually-hidden`, `scope="col"` | `:24-31,104-114` |
| Flash | `session('success')` e `session('error')` | `:58-72` |

Não há JS próprio, IDs de DOM, eventos ou estado persistido. A view funciona sem JavaScript (exceto o `data-bs-dismiss` do alerta).

### 4.4 Receitas de uso

**Adaptar para um módulo real (checklist do grupo)**:

```text
1. cp -r V2/Modules/_Exemplos/ExemploAPI V2/Modules/MeuGrupo/MeuModulo
2. Substituir namespace V2\Modules\_Exemplos\ExemploAPI -> V2\Modules\MeuGrupo\MeuModulo
3. Trocar prefixo cms/exemplo-api -> cms/meu-modulo
4. Trocar nomes v2.exemplo-api.* -> v2.meu-modulo.*
5. Trocar @v2can('ExemploAPI') e v2_can('ExemploAPI') pela chave do novo módulo
6. Trocar v2-exemploapi:: por v2-meumodulo::
7. Remover 'restricted' e o construtor de restrição
8. Trocar o array literal por model/migration
```

Fonte do procedimento: `V2/Modules/_Exemplos/README.md:21-27`.

**Reaproveitar apenas a view como template**:

```blade
@extends('v2-cms::layouts.app', ['title' => 'Meu Módulo'])
@section('content')
    <div class="card shadow-sm border-0"><div class="card-body p-4">
        @if(session('success'))
            <div class="alert alert-success" role="alert">{{ session('success') }}</div>
        @endif
        {{-- tabela com caption/aria-label/scope --}}
    </div></div>
@endsection
```

### 4.5 Anti-uso

1. **Usar `ExemploAPI` como chave de permissão de outro módulo** — o RBAC continuaria liberando apenas quem tem permissão no módulo de exemplo.
2. **Deixar `restricted` em módulo produtivo** — ele desaparece da sidebar para todos, exceto suporte/super-admin (`SidebarComposer.php:51-57`).
3. **Reaproveitar o construtor em módulo com API** — o middleware inline responde `403` texto puro e não define `Content-Type: application/json`; integrações externas podem quebrar.

## 5. Dados e modelo de domínio

Não se aplica: o módulo não define migrations, models, tabelas ou relacionamentos. `$items` é um array em memória (`ExemploAPICmsController.php:66-77`).

## 6. Fluxos e estados

```mermaid
stateDiagram-v2
  [*] --> NaoAutenticado
  NaoAutenticado --> Login: CmsAuthenticate (302)
  Login --> SemPapel: credenciais válidas sem role
  SemPapel --> Login: logout + erro de papel
  Login --> AutenticadoComPapel: role >= 1
  AutenticadoComPapel --> Bloqueado: e-mail != suporte e !super-admin
  Bloqueado --> Dashboard: redirect + flash error
  AutenticadoComPapel --> Listagem: suporte ou super-admin
  Listagem --> Listagem: POST válido (flash success)
```

Não há estado persistido; o “estado” é a sessão (flash e autenticação Sentinel).

## 7. UI CMS

- **Tela única**: `GET /cms/exemplo-api`, permissão `ExemploAPI:read` (ou suporte/super-admin), layout `v2-cms::layouts.app`.
- **Objetivo**: demonstrar listagem, estado vazio, badges de status, ações condicionadas por permissão, breadcrumb e flash.
- **Estados**: listagem com 2 itens (sempre), vazio (`empty($items)` nunca é verdadeiro com o array literal, mas o bloco existe para cópia), sucesso/erro via flash.
- **Colunas**: `#` (5%), `Título` (30%), `Descrição` (45%), `Status` (10%), `Ações` (10%).
- **Status**: `ativo` → badge `bg-success` “Ativo”; qualquer outro → `bg-warning` “Rascunho” (`index.blade.php:123-133`).
- **Ações**: botão editar só com `write`; sem handler (placeholder).
- **Acessibilidade**: ver §4.3; nenhum `aria-live` para flash — a mensagem não é anunciada automaticamente a leitores de tela.

## 8. UI Portal

Não se aplica: o módulo não registra `Routes/web.php`, não tem views de portal e não publica conteúdo. A única superfície pública afetada é indireta — o grupo `_Exemplos` é descoberto no boot e ocupa o namespace de view `v2-exemploapi`, mas nenhuma página do portal o referencia (`V2/Core/Providers/V2ServiceProvider.php:121-124`).

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

| Item | Valor/efeito | Fonte |
|---|---|---|
| `config('v2.support.email')` | Define quem passa no provider, sidebar e controller; default `suporte@maxima.inf.br` | `config/v2.php:5`; `ModuleProvider.php:29`; `ExemploAPICmsController.php:43` |
| `restricted` | Valor = e-mail de suporte; sidebar oculta para outros (exceto super-admin) | `SidebarComposer.php:51-57` |
| `active_pattern` | `cms/exemplo-api*`, usado pelo RBAC | `ModuleProvider.php:27`; `V2PermissionMiddleware.php:41-42` |
| Permissão | Chave `ExemploAPI`, ações `read`/`write` | `:92`; `index.blade.php:34` |
| `module.active:ExemploAPI` | Flag em `v2_module_flags`; ausência = ativo | `V2ServiceProvider.php:304`; `ModuleFlagService.php:93-94` |
| `position` | 1; ignorado na ordenação da sidebar | `ModuleProvider.php:26` |
| `group` | `_Exemplos`, injetado | `V2ServiceProvider.php:109` |

## 10. Observabilidade, erros e limites

- **Logs**: nenhum no módulo. O 403 AJAX não é logado.
- **Auditoria**: nenhuma; o exemplo comentado usaria `activity_log`, mas não está ativo.
- **HTTP**: `403` texto puro (`Forbidden. ExemploAPI is restricted to <email>.`), `403` HTML do `abort`, `419` CSRF, `302` para dashboard/login, `200` na listagem.
- **Limites**: `titulo` 255, `descricao` 1000; sem paginação (array fixo); sem upload.

## 11. Seeds e dados iniciais

Não se aplica: não há seeder, migration ou dado inicial. Os registros exibidos são literais no controller.

## 12. Lacunas

- **LG-EXEMPLOAPI-01** — Não existe teste automatizado para o módulo; o comportamento de 403/redirect não é verificado por `phpunit`. Onde procurar: `tests/` e `tests-e2e/`.
- **LG-EXEMPLOAPI-02** — Não foi possível determinar se o botão “Novo Exemplo” deveria abrir um modal não implementado; não há `data-bs-target` nem JS no módulo.
- **LG-EXEMPLOAPI-03** — `description` e `position` do `getConfig()` não têm consumidor identificado na sidebar; pode haver consumidor em views não cobertas por este documento. [I]

## 13. Checklist de paridade

- [ ] `PC-EXEMPLOAPI-01` — `GET /cms/exemplo-api` como suporte deve retornar 200 com 2 linhas. Fonte: `ExemploAPICmsController.php:66-81`.
- [ ] `PC-EXEMPLOAPI-02` — Usuário autenticado sem papel deve ser deslogado e redirecionado ao login. Fonte: `CmsAuthenticate.php:30-41`.
- [ ] `PC-EXEMPLOAPI-03` — Usuário com papel, não-suporte, deve ir ao dashboard com `Acesso negado. O módulo ExemploAPI é restrito ao suporte técnico.` Fonte: `:52-53`.
- [ ] `PC-EXEMPLOAPI-04` — AJAX de usuário barrado deve receber 403 com o texto que inclui o e-mail de suporte. Fonte: `:48-49`.
- [ ] `PC-EXEMPLOAPI-05` — `POST demo-store` com `titulo` vazio deve falhar com `O campo título é obrigatório.` Fonte: `:97-102`.
- [ ] `PC-EXEMPLOAPI-06` — `POST demo-store` com usuário `read` deve abortar 403. Fonte: `:92-94`.
- [ ] `PC-EXEMPLOAPI-07` — `POST demo-store` válido não deve criar nenhum registro. Fonte: `:104-107`.
- [ ] `PC-EXEMPLOAPI-08` — Menu deve exibir `[DEV] Exemplo API` apenas para suporte/super-admin. Fonte: `ModuleProvider.php:23,29`; `SidebarComposer.php:51-57`.
- [ ] `PC-EXEMPLOAPI-09` — View deve resolver em `v2-exemploapi::cms.index`. Fonte: `:79`.
- [ ] `PC-EXEMPLOAPI-10` — Botão de escrita não deve aparecer sem `write`. Fonte: `index.blade.php:34,136`.

## 14. Evidências consultadas

- `V2/Modules/_Exemplos/ExemploAPI/ModuleProvider.php:1-41`
- `V2/Modules/_Exemplos/ExemploAPI/Routes/cms.php:1-32`
- `V2/Modules/_Exemplos/ExemploAPI/Controllers/ExemploAPICmsController.php:1-112`
- `V2/Modules/_Exemplos/ExemploAPI/Views/cms/index.blade.php:1-166`
- `V2/Modules/_Exemplos/README.md:1-27`
- `V2/Core/Providers/V2ServiceProvider.php:100,109,123,304`
- `V2/Core/Middleware/CmsAuthenticate.php:18-45`
- `V2/Core/Middleware/V2PermissionMiddleware.php:22-99`
- `V2/Core/Middleware/ModuleAccessMiddleware.php:40-66`
- `V2/Core/ViewComposers/SidebarComposer.php:33-66`
- `V2/Core/Controllers/BaseController.php:1-23`
- `config/v2.php:4-11`
