---
id: MOD-INSTITUCIONAL-HISTORIA-ROTAS
tipo: modulo
onda: 4
status: concluido
unidades: [MOD-INSTITUCIONAL-HISTORIA]
fontes:
  - V2/Modules/Institucional/Historia/
depende_de: [MOD-INSTITUCIONAL-HISTORIA, CORE-BOOT, CORE-AUTH, CORE-CMS, CORE-RUNTIME]
consumidores: [UI-CMS, UI-PORTAL, SVC-SEO]
criticidade: media
dono: InstitucionalA
---

# Historia — rotas e contratos HTTP

> Anexo de [`spec.md`](spec.md). Detalha o grupo CMS (11 endpoints), o grupo público e a
> pilha de middleware real — inclusive a duplicação de `web`, o RBAC pelo nome do módulo e
> o efeito de `module.active`. Todas as rotas estão declaradas em arrays `Route::get/post`
> com `as`/`uses`; não há `api.php`.

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Expõe 11 ações CMS autenticadas sob `/cms/historia` e 1 ação pública em `/historia`, com CSRF nas escritas e RBAC por módulo. |
| Entrada | Método + URI; `{id}` UUID; query `search`, `page`; corpo conforme §5. |
| Saída | HTML/JSON/redirect conforme a ação; `404` para `{id}` inexistente; `401/403` em AJAX sem auth/permissão; `500` no POST de config. |
| Efeitos colaterais | Escritas em `v2_historia_milestones` e `v2_portal_configs`, `activity_log`, cache Redis, logs de warning/error. |
| Particularidade central | O grupo CMS declara `web` embora o provider já o aplique; o array final contém `web` duas vezes (merge recursivo), enquanto o grupo público também declara `web` sobre a pilha do provider. |
| Como se integra | Nomes `v2.historia.*`; controller namespace `V2\Modules\Institucional\Historia\Controllers` derivado do diretório pelo `V2ServiceProvider`. |
| Se removido, quebra | Todas as rotas nomeadas usadas pelas views e pelo sitemap deixam de existir (`route()` lança `InvalidArgumentException`). |
| Superfície pública | Contrato HTTP descrito abaixo; não há endpoints de API. |

## 1. Efeito

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

- Acessar a lista, o formulário de criação, a edição, o preview e as configurações (GET).
- Criar, atualizar (PUT/POST), arquivar (DELETE), reordenar (POST) e ativar/desativar (POST).
- Abrir `/historia` sem autenticação.

### 1.2 O que muda no sistema

| Rota | Efeito |
|---|---|
| `store` | INSERT + cache + activity log + flash |
| `update` | UPDATE + cache + activity log + flash |
| `destroy` | soft delete + cache + activity log + flash |
| `reorder` | N UPDATEs de `sort_order` + cache |
| `toggleActive` | UPDATE + cache + activity log |
| `updateConfig` | 3 `updateOrInsert` + TypeError (500) |
| `index/edit/preview/config` | leitura; `edit` faz 1 query extra de mídia quando numérico |
| `portal.index` | leitura + cache de página |

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

| Rota | Sucesso | Falha |
|---|---|---|
| `GET /cms/historia` | `200` HTML | `302` login; `302` dashboard (sem permissão); `404 Modulo desativado.` (módulo off) |
| `GET /cms/historia/novo` | `200` HTML | idem |
| `POST /cms/historia/novo` | `302` índice | `302` back com erros; `403`/`302` sem `write` |
| `GET /cms/historia/editar/{id}` | `200` HTML | `404` id inexistente |
| `PUT/POST /cms/historia/editar/{id}` | `302` edição | `404`; `302` back com erros |
| `DELETE /cms/historia/{id}` | `302` índice | `404` |
| `POST /cms/historia/reorder` | `200 {"success":true}` | `419` CSRF; `403` read-only (AJAX) |
| `POST /cms/historia/{id}/toggle` | `200` JSON | `404`; `419` |
| `GET /cms/historia/preview/{id}` | `200` HTML standalone | `404` |
| `GET /cms/historia/configuracoes` | `200` HTML | — |
| `POST /cms/historia/configuracoes` | grava e devolve `500` | `419`; `403` |
| `GET /historia` | `200` HTML | `503` manutenção |

### 1.4 O que estas rotas NÃO fazem

- Não expõem API JSON para o portal (só dois endpoints AJAX internos do CMS).
- Não têm throttle, assinatura, token ou versão.
- Não têm rota de restauração de arquivados.
- Não têm rota de detalhe público por marco.
- Não têm `where()` de constraint: `{id}` aceita qualquer string e delega ao `findOrFail`.

## 2. Abordagem

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

```mermaid
flowchart LR
  P[V2ServiceProvider] -->|web + module.active:Historia + namespace| G1[cms/historia group]
  P -->|web + Maintenance + page.cache + public.cache| G2[web.php group /historia]
  G1 -->|web + CmsAuthenticate| R11[11 endpoints]
  G2 --> R1[GET /historia]
  R11 --> RBAC[V2PermissionMiddleware por active_pattern]
```

### 2.2 Mecânica passo a passo

1. `mapModuleRoutes` resolve o namespace do controller a partir de
   `V2\Modules\{grupo}\{modulo}\Controllers` (`V2ServiceProvider.php:293-295`).
2. `Routes/cms.php` é agrupado com `web` + `module.active:Historia`
   (`V2ServiceProvider.php:298-306`); o arquivo declara seu próprio grupo com `web` +
   `CmsAuthenticate` + prefixo `cms/historia` (`Routes/cms.php:3-6`).
3. `CmsAuthenticate` verifica `Sentinel::check()` e exige ≥1 papel; então delega a
   `V2PermissionMiddleware` (`CmsAuthenticate.php:19-45`).
4. `V2PermissionMiddleware` percorre `v2.modules.configs`, casa `active_pattern`
   `cms/historia*`, e aplica `none/read/write` (`V2PermissionMiddleware.php:33-101`).
5. A ação executa; respostas de escrita são redirect+flash; AJAX devolve JSON.
6. `Routes/web.php` é agrupado pelo provider com manutenção + page.cache + public.cache;
   o arquivo adiciona outro `web` ao grupo (`Routes/web.php:3-9`).

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

- **Arrays de rota com `as`/`uses`** em vez de `Route::resource`: controle explícito de
  `reorder`, `toggle` e `preview`, que não são recursos `[C]` (`Routes/cms.php:7-60`).
- **Prefixo `cms/historia` sem chave de permissão na rota**: RBAC é derivado do
  `active_pattern`, não de um middleware `can` `[C]`.
- **`match(['PUT','POST'])`**: permite envio de form com `_method=PUT` e também POST direto
  (compatibilidade) `[C]` (`Routes/cms.php:27-30`).
- **Rotas literais depois de parametrizadas**: `/preview/{id}` e `/configuracoes` vêm depois
  de `DELETE /{id}` e `POST /{id}/toggle`; não há colisão porque os métodos/segmentos
  diferem, mas a ordem é frágil se alguém adicionar `GET /{id}` `[I]`.
- **Portal separado em `web.php`**: mantém as páginas públicas fora do RBAC e sujeitas a
  cache `[C]`.

### 2.4 Trechos de código pivô

```php
// V2/Modules/Institucional/Historia/Routes/cms.php:3-10 — grupo e primeira rota
Route::group([
    'middleware' => ['web', \V2\Core\Middleware\CmsAuthenticate::class],
    'prefix'     => 'cms/historia',
], function () {
    Route::get('/', [
        'as'   => 'v2.historia.cms.milestones.index',
        'uses' => 'MilestoneCmsController@index',
    ]);
```

`web` aqui é redundante com o grupo do provider; não há `module.active` explícito no
arquivo (vem do provider).

```php
// V2/Core/Providers/V2ServiceProvider.php:297-307 — pilha aplicada pelo provider
if (File::exists($cmsRoutes)) {
    Route::middleware(['web', 'module.active:' . $moduleName])
        ->namespace($controllerNamespace)
        ->group($cmsRoutes);
}
```

O `moduleName` é o nome do diretório (`Historia`); a flag correspondente em
`v2_module_flags` pode ser `Historia`, `historia` ou `historia` após `pascalToSnake`.

```php
// V2/Modules/Institucional/Historia/Routes/cms.php:27-45 — atualização e ações AJAX
Route::match(['PUT', 'POST'], '/editar/{id}', [
    'as'   => 'v2.historia.cms.milestones.update',
    'uses' => 'MilestoneCmsController@update',
]);

Route::delete('/{id}', [
    'as'   => 'v2.historia.cms.milestones.destroy',
    'uses' => 'MilestoneCmsController@destroy',
]);
```

`DELETE /cms/historia/configuracoes` também casaria `/{id}` (destroy) se enviado; a tela
não faz isso.

```php
// V2/Modules/Institucional/Historia/Routes/web.php:3-9 — grupo público
Route::group([
    'middleware' => ['web'],
], function () {
    Route::get('/historia', [
        'as'   => 'v2.historia.portal.index',
        'uses' => 'HistoriaPortalController@index',
    ]);
});
```

O provider já aplicou `web`, `MaintenanceMiddleware`, `page.cache`, `public.cache`; o grupo
adiciona `web` novamente.

### 2.5 Algoritmos, consultas e custos

- 12 declarações efetivas (11 CMS + 1 portal) + 2 grupos.
- `GET /cms/historia` → 1 count + 1 select paginado; `edit` → 1 select + 0/1 select de mídia.
- `reorder` → N updates; `toggle` → 2–3 queries.
- `POST config` → 3 `updateOrInsert` (cada um SELECT+UPDATE/INSERT no Postgres via query
  builder) + 1 chamada de log que falha.
- `/historia` → cache de 5 min evita queries em HIT.

## 3. Particularidades

### 3.1 O que só existe aqui

- **Dois endpoints AJAX com contratos JSON diferentes** (reorder e toggle).
- **`match(['PUT','POST'])`** para atualização.
- **Configuração no mesmo grupo CMS**, sem rota de leitura pública de config.

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

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| `RN-HISTORIA-ROTAS-01` | Nomes de rota seguem `v2.historia.cms.milestones.*` e `v2.historia.portal.index`. | Views e sitemap dependem desses nomes. | `Routes/cms.php:8-59`; `Routes/web.php:7` |
| `RN-HISTORIA-ROTAS-02` | Escritas exigem `web` (CSRF) + `CmsAuthenticate` + permissão `write`. | POST/PUT/DELETE de usuário `read` são bloqueados pelo middleware. | `V2PermissionMiddleware.php:101-125` |
| `RN-HISTORIA-ROTAS-03` | `module.active:Historia` é aplicado só ao grupo CMS. | Portal continua acessível com o módulo desativado. | `V2ServiceProvider.php:299-306,215-219` |
| `RN-HISTORIA-ROTAS-04` | `{id}` não tem regex; `findOrFail` devolve 404. | Qualquer string chega ao banco como comparação de UUID. | `MilestoneCmsController.php:116,139,183,226,268` |
| `RN-HISTORIA-ROTAS-05` | `reorder` não valida corpo; ausência de `ids` devolve sucesso sem efeito. | Chamada vazia é idempotente e silenciosa. | `MilestoneCmsController.php:207-215` |
| `RN-HISTORIA-ROTAS-06` | Toggle e reorder devolvem JSON mesmo para navegador (sem negociação de conteúdo). | A view usa `$.ajax` e espera JSON. | `MilestoneCmsController.php:215,253-257` |
| `RN-HISTORIA-ROTAS-07` | O grupo CMS declara `web` além do provider; no merge final `web` aparece duplicado. | Sessão/CSRF são processados duas vezes. | `Routes/cms.php:4`; `V2ServiceProvider.php:299`; `RouteGroup.php:31-35` |

### 3.3 Casos-limite e comportamentos surpreendentes

- **CSRF em AJAX**: sem `_token`, o `VerifyCsrfToken` responde `419` (página de erro), não
  JSON.
- **Usuário `read` em AJAX**: recebe `403 Forbidden. Write actions are not allowed for your profile.`
  (`V2PermissionMiddleware.php:116-124`).
- **Módulo desativado**: o middleware decide o redirect pelo prefixo `cms`; como o route
  group não usa `prefix='cms'` (usa `cms/historia`), `getPrefix()` é `cms/historia` e cai em
  `abort(404, 'Modulo desativado.')` — não no redirect documentado no docblock
  (`ModuleAccessMiddleware.php:52-61`).
- **Slash final**: `GET /cms/historia/` casa `cms/historia` (Laravel normaliza).
- **`GET /cms/historia/novo` vs `/{id}`**: não há GET parametrizado; `/novo` é literal.
- **`POST /cms/historia/reorder` vs `/{id}/toggle`**: segmentos diferentes; sem colisão.
- **`DELETE /cms/historia/{id}` com id `reorder`**: tentaria apagar um UUID inválido → 404.
- **Cache do portal**: HIT/MISS é observável no header `X-Cache-Status`; CMS nunca é
  cacheado por `page.cache` (padrão `cms*`).

### 3.4 Armadilhas de reimplementação

- **Registrar `/historia` como `Route::get('/{slug}')`** ou depois do fallback: o catch-all
  capturaria a URL.
- **Adicionar `web` de novo ao migrar para outro provider**: duplica middleware e pode
  alterar o ciclo de sessão.
- **Usar `Route::resource`** e perder `reorder/toggle/preview`: os nomes e contratos
  (`success`, `is_active`, `message`) fazem parte do JS.
- **Esquecer `match(['PUT','POST'])`**: forms com `_method=PUT` deixam de funcionar se só
  houver `PUT`.
- **Confiar no redirect de módulo desativado**: na prática é `404`.

### 3.5 Divergências documentação × código e dívidas

- **Docblock de `ModuleAccessMiddleware`** promete redirect para desativados em rotas CMS;
  a condição compara `getPrefix()==='cms'`, que não vale para `cms/historia` (`:57-60`).
- **`web` duplicado** no grupo CMS e no grupo público.
- **Sem throttle** apesar de endpoints de escrita.
- **Sem API**: qualquer integração externa precisa de sessão de navegador.

## 4. Integrabilidade

### 4.1 Superfície pública

| Símbolo | Tipo | Assinatura | Precondição | Consumidores |
|---|---|---|---|---|
| `v2.historia.cms.milestones.*` | nomes de rota | ver §5 | sessão + permissão | views do módulo |
| `v2.historia.portal.index` | nome de rota | `GET /historia` | — | view, sitemap, CMS “Ver página” |
| `reorder` | contrato JSON | request `ids[]` → `{success:true}` | `write` | `index.blade.php` |
| `toggle` | contrato JSON | request vazio → `{success,is_active,message}` | `write` | `index.blade.php` |

### 4.2 Contrato de UI/JS

- As rotas são consumidas por `route()` no Blade; nenhuma URL é hardcoded.
- `buildToggleUrl` usa a rota com placeholder `__ID__` (`index.blade.php:150-152`).
- CSRF via `_token` no corpo de ambos os AJAX; `csrf_field()` nos forms.
- Não há `Accept: application/json` explícito; o servidor sempre devolve JSON nessas ações.

### 4.3 Ordem e ciclo de vida

1. Provider registra rotas CMS antes do fallback.
2. `module.active` consulta `v2_module_flags` (cache estático por request).
3. `CmsAuthenticate` → `V2PermissionMiddleware` (composição explícita, não via kernel).
4. `page.cache`/`public.cache` só no grupo web.

### 4.4 Receitas de uso

```php
// Gerar URLs no Blade sem hardcode
route('v2.historia.cms.milestones.edit', ['id' => $milestone->id]);
route('v2.historia.portal.index');
```

```http
POST /cms/historia/reorder HTTP/1.1
Content-Type: application/x-www-form-urlencoded
X-CSRF-TOKEN: ...

_token=...&ids[]=uuid-3&ids[]=uuid-1&ids[]=uuid-2
```

Resposta: `200 {"success":true}`; ordens 1,2,3 na sequência enviada.

```http
POST /cms/historia/9f1c.../toggle HTTP/1.1
Content-Type: application/x-www-form-urlencoded

_token=...

HTTP/1.1 200 OK
{"success":true,"is_active":false,"message":"Marco \"Título\" desativado com sucesso!"}
```

### 4.5 Anti-uso

- **Chamar `reorder` sem CSRF** esperando JSON: recebe `419` HTML.
- **Assumir `204`/redirect** em reorder/toggle: o contrato é `200` JSON.
- **Usar `POST /cms/historia/{id}`** para atualizar: não existe rota; use `/editar/{id}`.

### 4.6 Custos e limites da integração

- Sem throttle: um cliente autenticado pode disparar N updates por segundo.
- `reorder` O(n) updates; `toggle` O(n) decremento em massa.
- Cache público só no portal; CMS sempre dinâmico.
- `{id}` sem regex pode gerar comparações inúteis em UUID.

## 5. Rotas e contratos HTTP

Ver tabela completa em [`spec.md`](spec.md) §5. Resumo do payload/resposta:

| Ação | Payload | Validação | Resposta |
|---|---|---|---|
| `store` | `period`, `title`, `description`, `image_id`, `sort_order`, `is_active` | `required|string|max:100`, `required|string|max:255`, `nullable|string`, `nullable|string|max:36`, `nullable|integer|min:0`, `nullable|boolean` | `302` índice |
| `update` | idem | idem | `302` edição |
| `reorder` | `ids[]` | nenhuma | JSON |
| `toggleActive` | — | — | JSON |
| `updateConfig` | `historia_seo[title]`, `[description]`, `[keywords]` | nenhuma | `500` |
| `index` | `search`, `page` | — | HTML |
| `preview` | `{id}` | — | HTML |

Não há `FormRequest`; validação inline via `$this->validate()`.

## 6. Dados e modelo de domínio

As rotas escrevem em `v2_historia_milestones` e `v2_portal_configs`; ver [`dados.md`](dados.md).

## 7. Fluxos e estados

```mermaid
sequenceDiagram
  participant B as Browser
  participant CA as CmsAuthenticate
  participant PM as V2PermissionMiddleware
  participant C as MilestoneCmsController
  B->>CA: POST /cms/historia/{id}/toggle
  CA->>CA: Sentinel::check + roles>0
  CA->>PM: handle
  PM->>PM: match active_pattern cms/historia*
  alt permissao write
    PM->>C: toggleActive(id)
    C-->>B: 200 JSON
  else read
    PM-->>B: 403 Forbidden. Write actions are not allowed for your profile.
  end
```

## 8. UI CMS

As rotas servem as views descritas em [`cms.md`](cms.md). Não há outra interface.

## 9. UI Portal

A rota pública serve a view descrita em [`portal.md`](portal.md).

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

- `active_pattern = ['cms/historia*']`; chave de permissão `Historia`.
- `module.active:Historia` no grupo CMS (provider).
- Sem env específico; `cache.prefix` afeta a limpeza.
- Suporte (`config('v2.support.email')`) e `super-admin` ignoram a checagem de permissão
  (`V2PermissionMiddleware.php:74-79`).

## 11. Observabilidade, erros e limites

- `404` (ModelNotFound/`abort`), `419` (CSRF), `401`/`403` (AJAX), `302` (login/permissão),
  `500` (config), `503` (manutenção).
- Logs: warning de cache, error de renderização, warning de permissão negada
  (`[V2 > MIDDLEWARE > Permission] …`).
- Sem throttle; sem rate limit; sem idempotência explícita (reorder é naturalmente
  idempotente para a mesma lista).

## 12. Seeds e dados iniciais

Não se aplica: nenhuma rota é criada por seed. O seeder de marcos usa o model diretamente.

## 13. Lacunas

- `LG-HISTORIA-ROTAS-01` — Comportamento exato do `VerifyCsrfToken` em AJAX (JSON vs HTML)
  não foi exercitado; depende da configuração do kernel.
- `LG-HISTORIA-ROTAS-02` — Não há teste de integração das rotas do módulo; a duplicação de
  `web` não foi observada em runtime.
- `LG-HISTORIA-ROTAS-03` — Nome real da flag em produção (`Historia` vs `historia`) não é
  possível determinar sem consultar `v2_module_flags`.

## 14. Checklist de paridade

- [ ] `PC-HISTORIA-ROTAS-01` — 11 rotas CMS com prefixo `cms/historia` e nomes exatos.
- [ ] `PC-HISTORIA-ROTAS-02` — Grupo CMS sob `web`, `module.active:Historia`,
      `CmsAuthenticate` e RBAC `Historia`.
- [ ] `PC-HISTORIA-ROTAS-03` — `PUT|POST` em `/editar/{id}`; `DELETE /{id}`;
      `POST /reorder`; `POST /{id}/toggle`; `GET /preview/{id}`;
      `GET|POST /configuracoes`.
- [ ] `PC-HISTORIA-ROTAS-04` — Portal `GET /historia` sob manutenção + page cache +
      public cache, sem auth.
- [ ] `PC-HISTORIA-ROTAS-05` — `reorder` responde `{"success":true}`; `toggle` responde
      `success`, `is_active` e `message` exatos.
- [ ] `PC-HISTORIA-ROTAS-06` — `404` para id inexistente em edit/update/destroy/toggle/preview.
- [ ] `PC-HISTORIA-ROTAS-07` — Módulo desativado bloqueia CMS (404) e não bloqueia portal.

## 15. Evidências consultadas

- `V2/Modules/Institucional/Historia/Routes/cms.php:1-61`; `Routes/web.php:1-10`.
- `V2/Core/Providers/V2ServiceProvider.php:203-224,293-307`.
- `V2/Core/Middleware/CmsAuthenticate.php:1-46`;
  `V2/Core/Middleware/V2PermissionMiddleware.php:1-137`;
  `V2/Core/Middleware/ModuleAccessMiddleware.php:1-67`;
  `V2/Core/Services/ModuleFlagService.php:82-115`.
- `V2/Modules/Institucional/Historia/Controllers/MilestoneCmsController.php:37-377`.
- `vendor/laravel/framework/src/Illuminate/Routing/RouteGroup.php:20-36`.
