---
id: MOD-PUBLICACOES-AGENDA-ROTAS
tipo: modulo
onda: 4
status: concluido
unidades: [MOD-PUBLICACOES-AGENDA]
fontes:
  - V2/Modules/Publicacoes/Agenda/
depende_de: [CORE-BOOT, CORE-CMS, CORE-AUTH, CORE-RUNTIME]
consumidores: [UI-CMS, UI-PORTAL]
criticidade: media
dono: Agenda
---

# Agenda — rotas e contratos HTTP

> Contrato observável das 8 rotas (6 CMS, 2 públicas): middleware, permissão, cache,
> validação, parâmetros, respostas de sucesso/erro e mensagens exatas. Semântica de
> negócio em [`spec.md`](spec.md); telas em [`cms.md`](cms.md) e [`portal.md`](portal.md).

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Expõe o CRUD de eventos no CMS (com RBAC por módulo) e a agenda pública cacheada. |
| Entrada | `GET/POST/PUT/DELETE /cms/agenda*`; `GET /agenda` e `GET /agenda/{slug}`; query `filter`, `search`, `date`, `week`, `month`, `page`. |
| Saída | HTML, `302` com flash/erros, `403`, `404`, `401`, `500`. |
| Efeitos colaterais | Cache de página/HTTP no portal; escrita em `v2_eventos`, índice, feed e auditoria. |
| Particularidade central | O grupo CMS recebe `module.active:Agenda` do provider e RBAC automático por `active_pattern`; o grupo público não recebe nenhum dos dois. |
| Como se integra | Nomes `v2.agenda.*`; controllers em `V2\Modules\Publicacoes\Agenda\Controllers`; redirects por `route()`. |
| Se removido, quebra | `/agenda` cai no catch-all `/{slug}` (BasicPage) ou 404; dashboard/home perdem links. |
| Superfície pública | 8 nomes de rota e os contratos de query abaixo. |

## 1. Efeito

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

- Operador com `Agenda:write`: criar, editar e arquivar.
- Operador com `Agenda:read`: listar e abrir formulários (GET).
- Visitante: listar/filtrar/buscar e abrir o detalhe de um evento publicado.

### 1.2 O que muda no sistema

| Efeito | Onde é produzido | Fonte |
|---|---|---|
| Escrita em `v2_eventos` + efeitos (busca/RSS/auditoria/cache) | `store`, `update`, `destroy` | `EventosCmsController.php:77-285` |
| `Cache-Control: public, max-age=60, must-revalidate` + `Vary: Accept-Encoding` | `public.cache` | `V2/Core/Middleware/PublicCacheMiddleware.php:86-98` |
| HTML em Redis por 5 min com `X-Cache-Status` | `page.cache` | `V2/Core/Middleware/PageCacheMiddleware.php:37-66` |
| `404 Modulo desativado.` | `module.active:Agenda` | `V2/Core/Middleware/ModuleAccessMiddleware.php:44-62` |
| `302` + flash de RBAC | `V2PermissionMiddleware` | `V2PermissionMiddleware.php:97-121` |

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

| Rota | Sucesso | Falha |
|---|---|---|
| `GET /cms/agenda` | `200` HTML com paginator | `302` login (sem sessão); `302` dashboard (RBAC `none`); `404 Modulo desativado.` (flag off) |
| `GET /cms/agenda/novo` | `200` HTML | idem |
| `POST /cms/agenda/novo` | `302 v2.agenda.cms.index` + modal | `302` back com `$errors` (validação); `419` CSRF; `403`/`302` RBAC; `404 Modulo desativado.` (flag off) |
| `GET /cms/agenda/editar/{id}` | `200` HTML | `404` inexistente; `500` UUID malformado `[I]` |
| `PUT/POST /cms/agenda/editar/{id}` | `302` para a própria edição + `Evento atualizado com sucesso!` | `302` back + `$errors`; `404`; `403`/`302` |
| `DELETE /cms/agenda/{id}` | `302 v2.agenda.cms.index` + `Evento "X" arquivado com sucesso!` | `404`; `403`/`302` |
| `GET /agenda` | `200` HTML, 12 por página | `404` só se a rota não existir (não ocorre); flag não bloqueia |
| `GET /agenda/{slug}` | `200` HTML | `404` se slug inexistente/não publicado |

### 1.4 O que este contrato NÃO faz

- Não há API JSON, AJAX, download, exportação ou endpoint de autocomplete.
- Não há throttle nem rate limit.
- Não há verificação de `is_active` no detalhe público.
- Não há endpoint de restauração de arquivados.
- Não há `per_page`, `sort` ou `order` configuráveis.

## 2. Abordagem

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

```mermaid
flowchart LR
  P[provider] -->|web + module.active| CMSFILE[Routes/cms.php]
  P -->|web + maintenance + page.cache + public.cache| WEBFILE[Routes/web.php]
  CMSFILE --> CA[CmsAuthenticate]
  CA --> PM[V2PermissionMiddleware]
  PM --> CT[CMS controllers]
  WEBFILE --> PT[Portal controllers]
  CT --> M[(Evento)]
  PT --> M
```

### 2.2 Mecânica passo a passo

1. O provider descobre o módulo e injeta middlewares por arquivo (`V2ServiceProvider.php:292-307`).
2. `Routes/cms.php` adiciona `CmsAuthenticate` e prefixo `cms/agenda` (`Routes/cms.php:3-5`).
3. `CmsAuthenticate`: sem sessão → `302` login/`401`; sem papel → logout + `302` login com erro; com papel → `V2PermissionMiddleware` (`CmsAuthenticate.php:18-45`).
4. `V2PermissionMiddleware`: casa `cms/agenda*` com `Agenda`, aplica `read` (só GET/HEAD) ou `write` (tudo) (`V2PermissionMiddleware.php:43-127`).
5. `module.active:Agenda` já rodou antes (ordem: `web`, `module.active`, depois o grupo do arquivo) e pode ter abortado com `404`/redirect (`V2ServiceProvider.php:303-306`).
6. Rotas públicas passam por `MaintenanceMiddleware` (503 se manutenção), `page.cache` e `public.cache`, nessa ordem (`V2ServiceProvider.php:297-300`).
7. Controllers executam; redirects usam nomes de rota, nunca URL literal (`EventosCmsController.php:158,259,284`).

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

- **Prefixo no arquivo, não no provider** `[C]`: o provider não aplica prefixo por módulo CMS; cada arquivo declara o seu (`cms/agenda`). É a convenção de todos os módulos.
- **`CmsAuthenticate` no arquivo** `[C]`: o provider não injeta autenticação; quem não declarar fica público (caso do Gallery).
- **RBAC por convenção** `[C]`: não há `can:` na rota; a chave vem do `active_pattern` do `ModuleProvider` (`ModuleProvider.php:18`).
- **Portal sem flag** `[C]`: `module.active` só é aplicado ao arquivo `cms.php`.
- **`Route::match(['PUT','POST'])`** `[C]`: aceita formulário com `_method=PUT` e POST direto, mantendo um único nome (`Routes/cms.php:27-30`).

### 2.4 Trechos de código pivô

```php
// V2/Modules/Publicacoes/Agenda/Routes/cms.php:7-22 — declaração literal das rotas CMS
Route::get('/', [
    'as'   => 'v2.agenda.cms.index',
    'uses' => 'EventosCmsController@index',
]);

Route::get('/novo', [
    'as'   => 'v2.agenda.cms.create',
    'uses' => 'EventosCmsController@create',
]);

Route::post('/novo', [
    'as'   => 'v2.agenda.cms.store',
    'uses' => 'EventosCmsController@store',
]);

Route::get('/editar/{id}', [
    'as'   => 'v2.agenda.cms.edit',
    'uses' => 'EventosCmsController@edit',
]);
```

Não há `where()` no `{id}`: qualquer string chega ao `findOrFail`, e o Postgres decide entre `404` e `500` `[C]`.

```php
// V2/Modules/Publicacoes/Agenda/Routes/web.php:3-16 — grupo público completo
Route::group([
    'middleware' => ['web'],
], function () {

    Route::get('/agenda', [
        'as'   => 'v2.agenda.portal.index',
        'uses' => 'EventosPortalController@index',
    ]);

    Route::get('/agenda/{slug}', [
        'as'   => 'v2.agenda.portal.show',
        'uses' => 'EventosPortalController@show',
    ]);
});
```

O `web` é redundante com o provider, mas inofensivo; `{slug}` não tem regex, então aceita qualquer segmento (inclusive `%20`) `[C]`.

```php
// V2/Core/Providers/V2ServiceProvider.php:292-307 — como os dois arquivos são montados
$webRoutes = $modulePath . '/Routes/web.php';
$cmsRoutes = $modulePath . '/Routes/cms.php';
$apiRoutes = $modulePath . '/Routes/api.php';
$controllerNamespace = "V2\\Modules\\{$groupName}\\{$moduleName}\\Controllers";

if (File::exists($webRoutes)) {
    Route::middleware(['web', \V2\Core\Middleware\MaintenanceMiddleware::class, 'page.cache', 'public.cache'])
        ->namespace($controllerNamespace)
        ->group($webRoutes);
}

if (File::exists($cmsRoutes)) {
    Route::middleware(['web', 'module.active:' . $moduleName])
        ->namespace($controllerNamespace)
        ->group($cmsRoutes);
}
```

A ausência de `api.php` em Agenda significa que nenhum grupo de API é registrado `[C]`.

```php
// V2/Modules/Publicacoes/Agenda/Controllers/EventosCmsController.php:79-95 — validação única para store e update
$this->validate($request, [
    'title'           => 'required|string|max:255',
    'image_id'        => 'nullable|string|max:36',
    'description'     => 'nullable|string',
    'event_date_start' => 'required|date',
    'event_date_end'  => 'nullable|date|after_or_equal:event_date_start',
    'location'        => 'nullable|string|max:255',
    'organizer'       => 'nullable|string|max:255',
    'target_audience' => 'nullable|string|max:255',
    'topics_list'     => 'nullable|string',
    'schedule'        => 'nullable|array',
    'schedule.*.time'      => 'nullable|string',
    'schedule.*.activity'  => 'nullable|string',
    'schedule.*.speaker'   => 'nullable|string',
    'is_active'       => 'nullable|boolean',
    'published_at'    => 'nullable|date',
]);
```

Não há mensagens customizadas: as mensagens são as do `validation.php` do Laravel, em pt-BR se o arquivo de idioma existir `[I]`.

```php
// V2/Core/Middleware/V2PermissionMiddleware.php:101-118 — read bloqueia escrita
if ($userPermission === 'read') {
    // Se for requisição de leitura (GET, HEAD), permite
    if ($request->isMethod('GET') || $request->isMethod('HEAD')) {
        return $next($request);
    }

    // Bloqueia tentativas de gravação (POST, PUT, DELETE, PATCH)
    Log::warning(sprintf(
        '[V2 > MIDDLEWARE > Permission] Tentativa de gravação negada para o usuário %s no módulo %s (%s).',
        $user->email,
        $currentModuleKey,
        $request->fullUrl()
    ));

    if ($request->ajax() || $request->wantsJson()) {
        return response('Forbidden. Write actions are not allowed for your profile.', 403);
    }

    return redirect()->back()
```

O `403` só ocorre em requisição AJAX/JSON, que a Agenda não usa; na prática o operador `read` vê um redirect com mensagem de erro `[C]`.

### 2.5 Algoritmos, consultas e custos

- **CMS index**: 1 count + 1 select paginado (15) + 1 query de relação por card só se `getImageUrl()` for chamado (lazy) — na listagem **não** há `with('image')`, então até 15 queries extras `[C]` (`EventosCmsController.php:60`; `Views/cms/index.blade.php:62-79`).
- **Portal index**: `with('image')` evita N+1; 1 count + 1 select (12) + 1 select de mídias `[C]`.
- **Detalhe**: 1 select por slug + 1 select da mídia (lazy) `[C]`.
- **Cache**: HIT devolve HTML sem tocar no banco; MISS grava por 5 min com headers originais, exceto `Set-Cookie` (`PageCacheMiddleware.php:45-60`).
- **`public.cache`**: aplica só em 2xx GET/HEAD, não AJAX, e respeita `Cache-Control` já definido `[C]`.

## 3. Particularidades

### 3.1 O que só existe aqui

- Dois grupos com pilhas de middleware completamente diferentes para o "mesmo" recurso.
- Rotas públicas sem nome de permissão e sem throttle.
- `PUT` e `POST` no mesmo nome de rota de update.

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

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| RN-AGENDA-19 | `module.active:Agenda` só existe no grupo CMS | Portal continua no ar com módulo desligado | `V2ServiceProvider.php:297-306` |
| RN-AGENDA-20 | RBAC deriva de `active_pattern=['cms/agenda*']` | Renomear o prefixo desliga o RBAC silenciosamente | `ModuleProvider.php:18`; `V2PermissionMiddleware.php:43-51` |
| RN-AGENDA-21 | `read` = GET/HEAD; qualquer outro método exige `write` | `POST` de busca (se existisse) exigiria write | `V2PermissionMiddleware.php:101-127` |
| RN-AGENDA-22 | Rotas públicas: `page.cache` antes de `public.cache` | HIT não recalcula headers de `public.cache` no Redis, mas eles foram gravados no MISS | `V2ServiceProvider.php:298`; `PageCacheMiddleware.php:45-60` |
| RN-AGENDA-23 | Só GET/HEAD são cacheados; AJAX nunca | Sem cache para chamadas XHR | `PageCacheMiddleware.php:68-86` |

### 3.3 Casos-limite e comportamentos surpreendentes

- **`/cms/agenda` sem barra final** e **com barra**: ambos casam `/`, pois o grupo prefixa; `/cms/agenda/` pode gerar redirect 301 do servidor `[I]`.
- **Método errado** (ex.: `POST /cms/agenda/editar/{id}`) é aceito de propósito; `DELETE` em `/cms/agenda/novo` casa a rota `/{id}` e tenta deletar o "novo" `[C]` (rota `DELETE /{id}` não tem constraint).
- **`GET /cms/agenda/{id}`** não existe: 404/405, não abre detalhe.
- **`page.cache` inclui query string** (`fullUrl`): cada combinação de filtro/busca/página tem sua própria entrada no Redis `[C]`.
- **Flash dentro de HTML cacheado**: um aviso de filtro inválido pode ficar 5 min para todos os visitantes daquela URL `[I]`.
- **Manutenção** retorna 503 no portal; o CMS não tem `MaintenanceMiddleware` `[C]`.
- **CSRF**: token obrigatório em POST/PUT/DELETE; erro `419` sem mensagem amigável.

### 3.4 Armadilhas de reimplementação

- Não colocar `active_pattern` como string única com `|`; o Agenda usa array corretamente (`ModuleProvider.php:18`).
- Não mover o prefixo `cms/agenda` para o provider sem atualizar `active_pattern`, ou o RBAC para de casar.
- Não aplicar `module.active` no grupo público se o requisito é manter `/agenda` online com módulo desligado.
- Não cachear POST/DELETE (o middleware já impede) nem cachear respostas com `Set-Cookie`.
- Não remover o `match(['PUT','POST'])` sem migrar o formulário (`Views/cms/edit.blade.php:20-22` envia `_method=PUT`).

### 3.5 Divergências e dívidas

- `Routes/web.php` repete `middleware => ['web']`, já aplicado pelo provider (redundância inofensiva).
- Não há constraint de UUID no path, gerando `500` em vez de `404` `[I]`.
- Não há rate limit em rotas que executam `ilike` e `KEYS` no cache.
- O `public.cache` documenta "até 5 minutos" no comentário, mas aplica 60 s (`PublicCacheMiddleware.php:18-22,86`).

## 4. Integrabilidade

### 4.1 Superfície pública

| Símbolo | Tipo | Assinatura | Precondição | Consumidores |
|---|---|---|---|---|
| `v2.agenda.cms.index` | rota | GET `/cms/agenda` | `Agenda:read` | menu/breadcrumb, dashboard |
| `v2.agenda.cms.create` | rota | GET `/cms/agenda/novo` | `Agenda:read` | sidebar, dashboard |
| `v2.agenda.cms.store` | rota | POST `/cms/agenda/novo` | `Agenda:write` | formulário |
| `v2.agenda.cms.edit` | rota | GET `/cms/agenda/editar/{id}` | `Agenda:read` | listagem |
| `v2.agenda.cms.update` | rota | PUT/POST `/cms/agenda/editar/{id}` | `Agenda:write` | formulário |
| `v2.agenda.cms.destroy` | rota | DELETE `/cms/agenda/{id}` | `Agenda:write` | listagem |
| `v2.agenda.portal.index` | rota | GET `/agenda` | pública | menu do portal, SEO, sitemap |
| `v2.agenda.portal.show` | rota | GET `/agenda/{slug}` | pública | cards, feed, `getPublicUrl()` |

### 4.2 Contrato de UI/JS

- Formulários CMS exigem `csrf_field()`; o de edição adiciona `method_field('PUT')` (`Views/cms/edit.blade.php:20-22`).
- O botão de arquivar é um `<form method="POST">` com `method_field('DELETE')` e `confirm()` inline (`Views/cms/index.blade.php:111-120`).
- Não há chamada AJAX própria; o único AJAX é o do componente de mídia.

### 4.3 Ordem e ciclo de vida

1. Provider registra as rotas depois do core; o fallback `/{slug}` é registrado por último (`V2ServiceProvider.php:131-137`), garantindo que `/agenda` explícita vença.
2. Middlewares executam na ordem declarada: `web` → `module.active` (CMS) → `CmsAuthenticate` → `V2PermissionMiddleware` → controller.
3. `page.cache`/`public.cache` só no grupo público.
4. Alterar nome/prefixo de rota quebra `ModuleProvider::getConfig()['route']`, o dashboard e o feed (`getPublicUrl()`).

### 4.4 Receitas de uso

**Criar um link para a listagem no CMS:**

```php
<a href="{{ route('v2.agenda.cms.index') }}">Agenda</a>
```

**Consumir o detalhe público a partir de um slug:**

```php
$url = route('v2.agenda.portal.show', ['slug' => $evento->slug]);
```

**Chamar a agenda filtrando o próximo mês:**

```http
GET /agenda?month=2026-11 HTTP/1.1
Host: exemplo.test
```

Resposta: `200` com cards de novembro/2026, `Cache-Control: public, max-age=60, must-revalidate`.

**Arquivar via HTTP (equivalente ao botão):**

```http
DELETE /cms/agenda/3f2c... HTTP/1.1
X-CSRF-TOKEN: <token>
Cookie: <sessão Sentinel>
```

### 4.5 Anti-uso

- **Chamar `route('v2.agenda.cms.index')` como visitante**: a rota existe, mas o middleware redireciona para login; não use como verificação de disponibilidade.
- **Confiar em `module.active` para esconder `/agenda`**: o grupo público não tem o middleware; use a flag apenas para o CMS.
- **Postar em `/cms/agenda/editar/{id}` sem `_method`**: cai no `match` e funciona, mas o formulário canônico usa `PUT`; misturar os dois em testes mascara diferenças de CSRF/redirect.

### 4.6 Custos e limites

- 15 itens/página no CMS, 12 no portal.
- Sem throttle; cada MISS do portal executa queries com `ilike`.
- `KEYS page:*` a cada escrita CMS.
- Respostas do portal podem ser servidas do Redis sem tocar no PHP (HIT).
- Sitemap separado, com cache de 6 h e sem filtro de publicação (`SeoPortalController.php:80-84,215-233`).

## 5. Rotas e contratos HTTP

### 5.1 Tabela normativa

| # | Tipo | Método | URI | Nome | Middleware (ordem) | Controller@ação | Permissão | Request | Resposta | Efeito | Fonte |
|---|---|---|---|---|---|---|---|---|---|---|---|
| R-AGENDA-00 | grupo | — | `cms/agenda` | — | `web`; `module.active:Agenda` | — | `Agenda` | — | — | agrupa e condiciona à flag | `Routes/cms.php:3-5`; `V2ServiceProvider.php:303-306` |
| R-AGENDA-01 | endpoint | GET | `/cms/agenda` | `v2.agenda.cms.index` | grupo + `CmsAuthenticate` + RBAC | `EventosCmsController@index` | `Agenda:read` | `filter`, `search`, `page` | `200` HTML 15/página | lista eventos, status por `published_at` | `Routes/cms.php:7-10`; `:39-62` |
| R-AGENDA-02 | endpoint | GET | `/cms/agenda/novo` | `v2.agenda.cms.create` | idem | `@create` | `Agenda:read` | — | `200` HTML | formulário vazio | `Routes/cms.php:12-15`; `:68-71` |
| R-AGENDA-03 | endpoint | POST | `/cms/agenda/novo` | `v2.agenda.cms.store` | idem + CSRF | `@store` | `Agenda:write` | 17 campos validados | `302` index + modal; `302` back com erros | cria + busca/RSS/auditoria/cache | `Routes/cms.php:17-20`; `:77-159` |
| R-AGENDA-04 | endpoint | GET | `/cms/agenda/editar/{id}` | `v2.agenda.cms.edit` | idem | `@edit` | `Agenda:read` | `{id}` UUID | `200` HTML; `404`; `500` se não-UUID | carrega evento + tópicos em texto | `Routes/cms.php:22-25`; `:165-183` |
| R-AGENDA-05 | endpoint | PUT/POST | `/cms/agenda/editar/{id}` | `v2.agenda.cms.update` | idem + CSRF | `@update` | `Agenda:write` | mesmos 17 campos | `302` para a edição + flash | atualiza + efeitos | `Routes/cms.php:27-30`; `:187-259` |
| R-AGENDA-06 | endpoint | DELETE | `/cms/agenda/{id}` | `v2.agenda.cms.destroy` | idem + CSRF | `@destroy` | `Agenda:write` | `{id}` UUID | `302` index + flash | soft delete + remoção de índices | `Routes/cms.php:32-35`; `:266-285` |
| R-AGENDA-07 | grupo | — | `/agenda` | — | `web`; `MaintenanceMiddleware`; `page.cache`; `public.cache` | — | pública | — | — | cache e manutenção | `Routes/web.php:3-5`; `V2ServiceProvider.php:297-300` |
| R-AGENDA-08 | endpoint | GET | `/agenda` | `v2.agenda.portal.index` | grupo público | `EventosPortalController@index` | pública | `date`, `week`, `month`, `search`, `page` | `200` HTML 12/página | consulta + SEO por filtro | `Routes/web.php:7-10`; `:33-103` |
| R-AGENDA-09 | endpoint | GET | `/agenda/{slug}` | `v2.agenda.portal.show` | grupo público | `@show` | pública | `{slug}` livre | `200` HTML; `404` | detalhe + JSON-LD + renderer | `Routes/web.php:12-15`; `:110-139` |

### 5.2 Parâmetros de query (público)

| Parâmetro | Formato aceito | Default | Efeito |
|---|---|---|---|
| `date` | `Y-m-d` válido (`hasFormat`) | — | filtra o dia; vence os demais |
| `week` | `Y-m-d` válido | — | filtra domingo–sábado da data |
| `month` | `Y-m` válido | — | filtra o mês (`-01` acrescentado) |
| `search` | string livre | `''` | `ilike` em título/local/organizador |
| `page` | inteiro | 1 | paginação de 12 |

Precedência: `date` > `week` > `month` > `upcoming`; sem filtro válido, aplica `upcoming` e (se havia parâmetro inválido) dispara flash de aviso (`EventosPortalController.php:42-60`).

### 5.3 Payload do CMS

| Campo | Tipo | Obrigatório | Regra | Observação |
|---|---|---|---|---|
| `title` | string | sim | `required|string|max:255` | gera slug na criação |
| `image_id` | string | não | `nullable|string|max:36` | id da galeria; `image_id_alt` é ignorado |
| `description` | string | não | `nullable|string` | texto/AST |
| `event_date_start` | data/hora | sim | `required|date` | picker envia `Y-m-d H:i:s` |
| `event_date_end` | data/hora | não | `after_or_equal:event_date_start` | |
| `location` | string | não | `max:255` | |
| `organizer` | string | não | `max:255` | |
| `target_audience` | string | não | `max:255` | vira categoria do RSS |
| `topics_list` | string | não | uma linha = item | convertido em JSON |
| `schedule` | array | não | `schedule.*.{time,activity,speaker}` strings | linha vazia persiste |
| `is_active` | `0|1` | não | `boolean` | só `'1'` vira true |
| `published_at` | `Y-m-d` | não | `date` | vazio = rascunho |

### 5.4 Erros e mensagens

| Situação | Código | Corpo/mensagem |
|---|---|---|
| Sem sessão (HTML) | `302` | redirect `v2.cms.login` |
| Sem sessão (AJAX) | `401` | `Unauthorized.` |
| Sem papel | `302` | `Acesso negado. Seu usuário não possui permissões administrativas.` em `$errors` |
| RBAC `none` | `302` | `Acesso negado. Seu perfil não possui permissão para acessar o módulo: Agenda` |
| RBAC `read` em escrita | `302` back | `Operação negada. Seu perfil possui apenas permissão de leitura para o módulo: Agenda` |
| Flag off (CMS) | `404` | `Modulo desativado.` |
| Flag off (AJAX) | `404` | `{"error":"Modulo desativado: Agenda","code":404}` |
| Flag off (portal, se aplicado) | `404` | `Modulo desativado.` |
| Validação | `302` back | `$errors` padrão do Laravel (não renderizado no layout) |
| CSRF | `419` | página padrão |
| Slug não publicado | `404` | página padrão |
| Sitemap/manutenção | `503` | página de manutenção (portal) |

## 6. Dados e modelo de domínio

Nenhuma tabela própria de rota. As rotas escrevem em `v2_eventos` e disparam efeitos descritos em [`dados.md`](dados.md).

## 7. Fluxos e estados

```mermaid
sequenceDiagram
  participant B as Browser CMS
  participant R as Router
  participant M as Middlewares
  participant C as EventosCmsController
  B->>R: POST /cms/agenda/novo + CSRF
  R->>M: web -> module.active:Agenda -> CmsAuthenticate
  M->>M: RBAC Agenda:write
  M->>C: store(Request)
  C-->>B: 302 /cms/agenda + created_item
```

## 8. UI CMS

As rotas CMS estão descritas em [`cms.md`](cms.md); aqui só o contrato HTTP.

## 9. UI Portal

As rotas públicas e os parâmetros de SEO estão em [`portal.md`](portal.md).

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

- `active_pattern=['cms/agenda*']` é o elo entre rota e RBAC (`ModuleProvider.php:18`).
- `module.active:Agenda` usa `ModuleFlagService::isActive('Agenda')`, que retorna `true` sem linha (`ModuleFlagService.php:75-92`).
- `read`/`write` vêm de `v2_roles.permissions_v2` (`PermissionService.php:66-104`).
- `super-admin` e `v2.support.email` ignoram o RBAC (`V2PermissionMiddleware.php:64-67`).
- Não há throttle configurável.

## 11. Observabilidade, erros e limites

- `V2PermissionMiddleware` loga `Log::warning` em `none`, `read`+escrita e grupo Support (`V2PermissionMiddleware.php:77-83,109-114`).
- `ModuleAccessMiddleware` não loga, apenas responde.
- `PageCacheMiddleware` loga warning quando o Redis falha e serve sem cache (`PageCacheMiddleware.php:62-64`).
- `EventosCmsController` loga cada etapa com prefixo `[Agenda]`.
- Limites: 15/12 por página; sem timeout próprio; `KEYS` pode bloquear o Redis.

## 12. Seeds e dados iniciais

Não se aplica: rotas não dependem de seeds. A ausência de linha em `v2_module_flags` é o default documentado em §10.

## 13. Lacunas

- **LG-AGENDA-10** — não há constraint de UUID no path CMS; o comportamento `500` é inferido do tipo da coluna. Onde procurar: logs de produção.
- **LG-AGENDA-11** — não foi possível confirmar o comportamento de `/cms/agenda/` (barra final) no servidor web (Apache/Nginx). Onde procurar: `.htaccess`/vhost.
- **LG-AGENDA-12** — não há indicação de rate limit externo (WAF/CDN). Onde procurar: configuração de infraestrutura.

## 14. Checklist de paridade

- [ ] `PC-AGENDA-06` — `GET /agenda` devolve 12 futuros publicados+ativos ordenados asc.
- [ ] `PC-AGENDA-07` — `date` inclui passado do dia.
- [ ] `PC-AGENDA-08` — `week` usa domingo–sábado.
- [ ] `PC-AGENDA-09` — `month` cobre o mês inteiro.
- [ ] `PC-AGENDA-22` — `GET /cms/agenda` retorna 302 login sem sessão e 302 dashboard com RBAC `none`.
- [ ] `PC-AGENDA-23` — `read` permite GET e bloqueia POST/PUT/DELETE com a mensagem exata.
- [ ] `PC-AGENDA-24` — flag off bloqueia `/cms/agenda*` e mantém `/agenda`.
- [ ] `PC-AGENDA-25` — `PUT` e `POST` em `/cms/agenda/editar/{id}` têm o mesmo efeito.
- [ ] `PC-AGENDA-26` — `DELETE /cms/agenda/novo` tenta arquivar o id `novo` (404/500), não apaga a listagem.
- [ ] `PC-AGENDA-27` — `page.cache` devolve `X-Cache-Status: HIT` na segunda requisição idêntica e não cacheia AJAX.

## 15. Evidências consultadas

- `V2/Modules/Publicacoes/Agenda/Routes/cms.php:1-37`.
- `V2/Modules/Publicacoes/Agenda/Routes/web.php:1-16`.
- `V2/Core/Providers/V2ServiceProvider.php:87-137,292-316`.
- `V2/Core/Middleware/CmsAuthenticate.php:18-45`.
- `V2/Core/Middleware/V2PermissionMiddleware.php:18-137`.
- `V2/Core/Middleware/ModuleAccessMiddleware.php:18-66`.
- `V2/Core/Middleware/PageCacheMiddleware.php:13-92`.
- `V2/Core/Middleware/PublicCacheMiddleware.php:23-134`.
- `V2/Modules/Publicacoes/Agenda/Controllers/EventosCmsController.php:39-305`.
- `V2/Modules/Publicacoes/Agenda/Controllers/EventosPortalController.php:33-141`.
