# Legislação — Rotas e Controladores

> Rotas registradas em `v6/routes/web.php` (portal), `v6/routes/basepro.php`
> (CMS) e `v6/routes/api.php` (dados abertos). Os controllers do CMS são
> **genéricos**, guiados pelo *slug* `legislacao`.

---

## 1. Portal — `v6/routes/web.php:127-133`

```php
Route::prefix('legislacao')->group(function () {
    Route::get('/', 'Portal\LegislationController@getIndex')->name('legislacao.index');
    Route::get('/export', 'Portal\LegislationController@export')->name('legislacao.export');
    Route::get('/{slug}', 'Portal\LegislationController@contentShow')->name('conteudo.legislacao.list');
    Route::get('/{slug1}/{slug2}', 'Portal\LegislationController@contentView')->name('conteudo.legislacao.view');
    Route::post('/filtro', 'Portal\LegislationController@filtroGet')->name('conteudo.legislacao.fill');
});
```

| Método | URI | Nome | Controller@ação | Middleware | Comportamento |
|---|---|---|---|---|---|
| GET | `/legislacao` | `legislacao.index` | `Portal\LegislationController@getIndex` | `web`, `check.site.manutention` | 302 para `conteudo.legislacao.list` da 1ª subcategoria por `order`. |
| GET | `/legislacao/export` | `legislacao.export` | `Portal\LegislationController@export` | idem | Exporta CSV/XLS/TXT/PDF. **Rota exclusiva da v6.** |
| GET | `/legislacao/{slug}` | `conteudo.legislacao.list` | `Portal\LegislationController@contentShow` | idem | Lista as normas da subcategoria + filtros (ano/mês/texto). |
| GET | `/legislacao/{slug1}/{slug2}` | `conteudo.legislacao.view` | `Portal\LegislationController@contentView` | idem | Exibe a norma, anexos e PDF; incrementa `clicks`. |
| POST | `/legislacao/filtro` | `conteudo.legislacao.fill` | `Portal\LegislationController@filtroGET` | idem + `web` (CSRF) | Filtro consumido pelo JS do portal; devolve JSON. |

Rotas relacionadas:

| Método | URI | Nome | Papel |
|---|---|---|---|
| GET | `portal/{slug1}/{slug2}/{slug3}` | `conteudo.portal.view` | `Portal\PortalController@contentList` — com `slug = 'legislacao'` faz `redirect(route('legislacao.index'))` (`v6/app/Http/Controllers/Portal/PortalController.php:328`). Usado pelas views do layout `default`. |

## 2. CMS — `v6/routes/basepro.php`

Rotas genéricas do módulo de conteúdo (não há rota específica de legislação):

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| GET | `/cms/conteudos/{slug}` | `listar-conteudo` | `Prefecture\ContentController@genericIndex` |
| GET | `/cms/conteudos/cadastrar/{slug}` | `cadastrar-conteudo` | `Prefecture\ContentController@genericCreate` |
| GET | `/cms/conteudos/editar/{slug}/{id}` | `editar-conteudo` | `Prefecture\ContentController@genericEdit` |
| POST | `/cms/conteudo` | `conteudo.store` | `Prefecture\ContentController@store` |
| PUT | `/cms/conteudo/{id}` | `conteudo.update` | `Prefecture\ContentController@update` |
| DELETE | `/cms/conteudo/{id}` | `conteudo.destroy` | `Prefecture\ContentController@destroy` |
| GET | `/cms/categorias/{slug}` | `listar-categorias` | `Prefecture\CategoryContentController@genericIndex` |
| GET | `/cms/categorias/cadastrar/{slug}` | `cadastrar-categorias` | `Prefecture\CategoryContentController@genericCreate` |
| GET | `/cms/categorias/editar/{slug}/{id}` | `editar-categorias` | `Prefecture\CategoryContentController@genericEdit` |
| GET | `/cms/categories/{slug}` | `categories.show` | `Prefecture\CategoryContentController@genericShow` (JSON) |
| POST | `/cms/categories` | `categories.store` | `Prefecture\CategoryContentController@apiStore` (JSON) |
| PUT | `/cms/categories` | `categories.update` | `Prefecture\CategoryContentController@apiUpdate` (JSON) |
| DELETE | `/cms/categories` | `categories.destroy` | `Prefecture\CategoryContentController@apiDestroy` (JSON) |
| POST | `/cms/ajax/ajust-categorias` | `ajust.categorias` | `Prefecture\CategoryContentController@ajustOrder` (reordenação) |
| GET | `/cms/datatable/legislacao` | `datatable.legislacao` | `Prefecture\DatatableController@get_legislacao` (`v6/routes/basepro.php:419`) |
| POST | `/cms/conteudo/deleteAll` | `ajax.generic.content.deleteAll` | `Prefecture\ContentController@genericDeleteAll` |

Com `slug = 'legislacao'`:

- `ContentController@genericIndex` → `returnIndex('Legislação', 'legislacao')`
  (`v6/app/Http/Controllers/Prefecture/ContentController.php:106`).
- `ContentController@genericGenerate` → `returnData('Legislação', 'legislacao', null,
  ["categoryContentSelect" => $this->getAllItemsToCategoryContents($id_category, true)], $id)`
  (`v6/app/Http/Controllers/Prefecture/ContentController.php:267-270`).
- `CategoryContentController@genericIndex` → `case 'legislacao'`
  (`v6/app/Http/Controllers/Prefecture/CategoryContentController.php:91-110`).

### Middleware do CMS

| Middleware | Onde | Efeito |
|---|---|---|
| `check.route.permission` | aplicado no construtor de `ContentController` (`v6/app/Http/Controllers/Prefecture/ContentController.php:60`) | `CheckRoutePermission::hasAccessSegment()` monta `legislacao.index` / `legislacao.create` / `legislacao.edit` a partir do segmento da URL e chama `Sentinel::check()->hasAnyAccess([...])`. |
| `check.logged` | `App\Http\Middleware\CheckLogged` | Exige sessão Sentinel; senão redireciona para `dashboard.index`. |
| `csrf` | grupo `Route::group(['prefix' => 'cms', 'before' => 'csrf'])` | Token CSRF em todo o CMS. |

## 3. API de dados abertos — `v6/routes/api.php:26-28`

```php
Route::group(['prefix' => 'legislacoes', 'as' => 'legislacoes.'], function () {
    Route::get('/', 'Swagger\V1\Portal\LegislationController@index')->name('index');
});
```

| Método | URI | Nome | Controller@ação | Middleware |
|---|---|---|---|---|
| GET | `/api/v1/legislacoes` | `v1.legislacoes.index` | `Swagger\V1\Portal\LegislationController@index` | `api` |

Parâmetros aceitos (documentados no bloco `@OA\Get` do controller):

| Parâmetro | Formato | Efeito |
|---|---|---|
| `titulo` | string | Filtro por título (case/accent-insensitive via `whereRaw`). |
| `data_publicacao` | `dd/mm/yyyy` ou `dd/mm/yyyy_dd/mm/yyyy` | Período (ou data única). |
| `descricao` | string | Filtro por corpo do texto. |
| `limite` | integer (default 100 na doc) | `->limit()` no Eloquent — **só é aplicado se não for vazio**. |

## 4. Form Requests / validação

### `ContentRequest` — `case 'legislacao'` (`v6/app/Http/Requests/ContentRequest.php:274-286`)

```php
case 'legislacao':
    $rules = [
        "title" => "required|max:120",
        "publication_date" => "required",
        "category_contents_id" => "required|numeric",
        "text" => "required",
    ];
    if (Route::getCurrentRoute()->getName() === "conteudo.update") {
        $rules += [
            "slug" => 'required|max:200'
        ];
    }
    break;
```

| Regra | Campo | Observação |
|---|---|---|
| `required|max:120` | `title` | Limite bate com o `maxlength="120"` do input. |
| `required` | `publication_date` | Formatado `d/m/Y H:i` no formulário; convertido no `store()`. |
| `required|numeric` | `category_contents_id` | Deve ser uma subcategoria de legislação. |
| `required` | `text` | Corpo HTML (Summernote). |
| `required|max:200` | `slug` | **Só no update**; no create o slug é gerado pelo servidor. |

`ContentRequest::authorize()` retorna `true` — a autorização real é feita pelo
middleware `check.route.permission`.

### `Swagger\V1\Portal\LegislationRequest` — `v6/app/Http/Requests/Swagger/V1/Portal/LegislationRequest.php`

```php
public function rules()
{
    return [
        "titulo" => ["nullable", "string"],
        "data_publicacao" => ["nullable", "string", new FormatPeriodRule, new DatesValidationRule],
        "descricao" => ["nullable", "string"],
    ];
}
```

Falha de validação retorna **422** com o corpo
`{"errors": {"data_publicacao": ["mensagem de erro"]}}`
(`failedValidation` customizado).

### `CategoryContent\ApiStoreRequest` / `ApiUpdateRequest` / `ApiDestroyRequest`

Validam o JSON de criação/edição/exclusão de categorias usada pelo CMS
(arrastar-e-soltar). Arquivos:
`v6/app/Http/Requests/CategoryContent/ApiStoreRequest.php`,
`v6/app/Http/Requests/CategoryContent/ApiUpdateRequest.php`,
`v6/app/Http/Requests/CategoryContent/ApiDestroyRequest.php`.

## 5. API Resources / presenters

### `LegislationResource` — `v6/app/Http/Resources/Swagger/V1/Portal/LegislationResource.php`

```php
$publication = data_get($this, "publication_date") ? data_get($this, "publication_date")->format("d/m/Y H:i:s") : '';
$description = strip_tags(trim(data_get($this, "text")));
return [
    "titulo" => data_get($this, "title"),
    "data_publicacao" => $this->when($publication != '', $publication),
    "descricao" => $this->when($description != "", $description),
    "interacoes" => data_get($this, "clicks"),
    "slug" => data_get($this, "slug"),
    "arquivos" => ArchiveResource::collection($this->whenLoaded("archives"))
];
```

### `ShowResource` (categorias) — `v6/app/Http/Resources/CategoryContents/ShowResource.php`

```php
return [
    'id' => data_get($this, 'id'),
    'name' => data_get($this,'name'),
    'order' => (int) data_get($this, 'order'),
    'parentId' => (int) data_get($this, 'category_contents_id')
];
```

É esse formato que o JS `v6/public/assets/js/custom/contents/category-contents/legislacao/script.js`
consome (ele ordena por `parentId` e `order`).

### Presenters

`ContentPresenter` (`v6/app/Http/Presenters/ContentPresenter.php`) e
`CategoryContentPresenter` (`v6/app/Http/Presenters/CategoryContentPresenter.php`)
são aplicados via `PresentableTrait`; o CMS usa o presenter de `Content` para
formatar datas e ocorrências.

## 6. Permissões exigidas

| Permissão | Onde é exigida |
|---|---|
| `legislacao.index` | Menu do CMS (`MenuServiceProvider`), tela `listar-conteudo`, acesso à rota `categories.show`. |
| `legislacao.create` | Botões "Cadastrar Legislação" e "Categorias de Legislação" (`v6/resources/views/prefecture/contents/legislacao/index.blade.php:11,19`). |
| `legislacao.edit` | Botão de editar na grade e na tela de categorias. |
| `legislacao.delete` | Exclusão individual e em lote. |
| `legislacao.restore` / `legislacao.inactivate` | Colunas `active`/`inactivate` injetadas por `struct_views()` (`v6/app/Http/Controllers/Prefecture/DatatableController.php:504-520`). |

Papel `super-admin` passa por `Sentinel::inRole('super-admin')` e ignora a
checagem no `CheckRoutePermission`. O papel `administrador` possui todas as
permissões `*.index/.create/.edit/.delete/.show/.inactive/.inactivate/.restore`
de `legislacao` (verificado extraindo o JSON de `roles.permissions`).

## 7. Breadcrumbs — `v6/routes/breadcrumbs.php:102-118`

| Chave | Hierarquia | Link |
|---|---|---|
| `legislacao` | Home > Legislação | `route('legislacao.index')` |
| `category-legislacao` | Home > Legislação > [Categoria] | `route('conteudo.legislacao.list', [$category->slug])` |
| `view-legislacao` | Home > Legislação > [Categoria] > Norma | `route('conteudo.legislacao.view', [$content->categoryContents->slug, $content->slug])` |

> Nota: `view-legislacao` usa `route('conteudo.legislacao.view', [slug da
> categoria, slug do conteúdo])`, enquanto a rota real é
> `/{slug1}/{slug2}` — os nomes coincidem, mas a semântica do primeiro
> parâmetro é "categoria".

## 8. Diferenças de rotas v5 → v6

| Item | v5 | v6 |
|---|---|---|
| `GET /legislacao/export` (`legislacao.export`) | **não existe** | existe (`v6/routes/web.php:129`) |
| `Portal\LegislationController@export` | **não existe** | existe (`v6/app/Http/Controllers/Portal/LegislationController.php:94-140`) |
| `ExportsContentTrait` / `ContentsExport` / `exports/contents.blade.php` | **não existem** | existem na v6 |
| Demais rotas de legislação | idênticas | idênticas |

Comparação literal do bloco `legislacao` nas duas versões:

```php
// v5/routes/web.php:107-112
Route::prefix('legislacao')->group(function () {
    Route::get('/', 'Portal\LegislationController@getIndex')->name('legislacao.index');
    Route::get('/{slug}', 'Portal\LegislationController@contentShow')->name('conteudo.legislacao.list');
    Route::get('/{slug1}/{slug2}', 'Portal\LegislationController@contentView')->name('conteudo.legislacao.view');
    Route::post('/filtro', 'Portal\LegislationController@filtroGet')->name('conteudo.legislacao.fill');
});
```
