# PLATAFORMA — Rotas e Controllers

> Fonte: `v6/routes/*.php` + `php artisan route:list` no container
> `v6_6f22e1bd-app-1`. Contagens: **928 rotas** registradas.

---

## 1. Mapa por arquivo de rotas

| Arquivo | Linhas | Ocorrências de `Route::` | Registradas? | Prefixo efetivo |
|---|---|---|---|---|
| `v6/routes/web.php` | 232 | 139 | Sim (`RouteServiceProvider::mapWebRoutes`) | `/` (raiz) |
| `v6/routes/basepro.php` | 548 | 417 | Sim (`RouteServiceProvider::mapBaseproRoutes`) | `/cms` (dentro do arquivo) |
| `v6/routes/api.php` | 32 | 11 | Sim (`RouteServiceProvider::mapApiRoutes`) | `/api/v1` |
| `v6/routes/service_letter.php` | 85 | 51 | Sim, via `require` em `basepro.php:512` | `/cms/carta-de-servicos` |
| `v6/routes/prefecture.php` | 15 | 2 | **Não** — arquivo 100% comentado | — |
| `v6/routes/breadcrumbs.php` | 506 | 0 (`Breadcrumbs::register`) | Sim, via `diglactic/laravel-breadcrumbs` | n/a |
| `v6/routes/channels.php` | 16 | 0 | Sim (`BroadcastServiceProvider`) | n/a |
| `v6/routes/console.php` | 18 | 0 | Sim (Laravel) | n/a |

Comparação v5 → v6 (`Route::` por arquivo):

| Arquivo | v5 | v6 | Δ |
|---|---|---|---|
| `web.php` | 121 | 139 | +18 |
| `basepro.php` | 444 | 417 | −27 |
| `api.php` | 11 | 11 | 0 |
| `prefecture.php` | 2 | 2 | 0 (comentado nas duas) |
| `service_letter.php` | — (não existe) | 51 | +51 (módulo novo) |
| `breadcrumbs.php` | idem | idem | — |

Distribuição final por camada (via `route:list`):

| Camada | Rotas |
|---|---|
| CMS (`/cms/**`) | **695** |
| Portal + rotas auxiliares | **228** |
| API (`/api/v1/**`) | **5** |
| **Total** | **928** |

Por método HTTP: `GET|HEAD` 561, `POST` 206, `DELETE` 82, `PUT` 79, `PATCH` 58.

---

## 2. `v6/routes/web.php` — Portal público

### 2.1 Estrutura

```php
Route::get('/selos', 'Portal\SelosController@index')->name('selos.index');

Route::middleware(['check.site.manutention'])->group(function () {
    Route::get('/', 'Portal\PortalController@index')->name('home');
    …
});
```

Apenas `/selos` fica **fora** do grupo de manutenção em `web.php`
(`v6/routes/web.php:6`).

Atenção ao nome duplicado `static.show`: ele é declarado em **dois arquivos** —
`v6/routes/web.php` (`GET /pagina/{slug}`) e `v6/routes/basepro.php:548`
(`GET /{slug}`). O `basepro.php` declara essa rota **fora** do grupo `cms` e
**fora** do grupo `check.site.manutention`, e o comentário no arquivo registra
o motivo: *"Statics (obs: chamada na basepro para evitar conflito de slugs)"*.
Como as duas rotas têm o mesmo nome, a **última registrada vence**. Ordem em
`v6/app/Providers/RouteServiceProvider.php:36-46`: `mapApiRoutes()` →
`mapWebRoutes()` → `mapBaseproRoutes()`. Logo o CMS registra por último e
**sobrescreve** o portal.

Verificado no container `v6_6f22e1bd-app-1`:

```
static.show          => http://localhost/x              (o /{slug} do basepro)
transparencia.index  => http://localhost/portal-transparencia
```

**Consequência prática:** `route('static.show')` gera `/<slug>` (catch-all), e
não `/pagina/<slug>`. A rota `/pagina/{slug}` continua acessível por URL
direta, mas **não é gerada por nenhum link do sistema** — o que significa que
o padrão `pagina/…` é inacessível por navegação programática.

### 2.2 Domínios de URL do portal

| Domínio | Controlador principal | Rotas |
|---|---|---|
| Home | `Portal\PortalController@index` | 1 |
| Notícias | `Portal\NewsController` | 5 (`noticia.index`, `noticia.show`, `noticia-por-categoria.index`, `noticia-categoria.index`, `busca.por.categoria`) |
| Conteúdo genérico (`portal/{slug}`) | `Portal\PortalController@contentList` | 4 |
| Transparência fiscal | `Portal\TransparencyController` | 9 (4 aliases de índice + 5 de detalhe) |
| Diário oficial | `Portal\DiarioController` | 7 (inclui `filtro`, `search-pdf`, `auditoria`) |
| Licitações | `Portal\BiddingsController`, `Portal\BiddingOldController` | 6 |
| Contratos | `Portal\ContractController` | 3 |
| Legislação | `Portal\LegislationController` | 5 |
| Carta de serviços | `Portal\ServiceLetterController` | 4 |
| Requerimentos / diárias | `Portal\RequerimentoController`, `Portal\BuscaDiariasController` | 6 |
| Câmara (sessões, vereadores, mandatos, atas, pauta, TV, comitê) | `Portal\SessionsController`, `CityCouncilorController`, `MandatesController`, `AtasEPontosController`, `PautaDoDiaController`, `TvCamaraController`, `ComiteDeInvestimentosController` | 17 |
| Ouvidoria | `Portal\OmbudsmanController` | 4 (inclui 2 Ajax) |
| Institutional (`a-instituicao`, `a-cidade`, `orgaos`, `secretarias`) | `Portal\PrefectureController`, `AboutController`, `SecretariaController` | 7 |
| Formulários dinâmicos | `Portal\DynamicForms\*` | 5 |
| Emendas parlamentares | `Portal\EmendaParlamentarController` | 6 |
| VIAP | `Portal\ViapPortalController` | 3 |
| Manutenção / sitemap / busca | `Portal\PortalController`, `SitemapController` | 8 |
| Ajax diversos | `Portal\PortalController`, `TransparencyController`, `HelperController` | 8 |
| Estáticos (contato, acessibilidade, Glossário, telefones, etc.) | vários | 22 |

### 2.3 Padrão de rotas "alias"

Várias URLs apontam para a **mesma** action com o **mesmo** nome de rota.
Exemplo real (`v6/routes/web.php:184-193`):

```php
Route::get('/transparencia-fiscal', 'Portal\TransparencyController@index')->name('transparencia.index');
Route::get('/transparencia', 'Portal\TransparencyController@index')->name('transparencia.index');
Route::get('/portal-da-transparencia', 'Portal\TransparencyController@index')->name('transparencia.index');
Route::get('/portal-transparencia', 'Portal\TransparencyController@index')->name('transparencia.index');
```

**Risco:** em Laravel, registrar 2 rotas com o **mesmo** nome sobrescreve a
entrada anterior no `RouteCollection`. O `route('transparencia.index')` aponta
para a **última** registrada (`/portal-transparencia`). A ordem importa e é
frágil — a v7 deve usar um único nome e `Route::redirect` para os aliases, ou
um redirect controller.

### 2.4 Rotas Ajax do portal

| Rota | Nome | Uso |
|---|---|---|
| `POST getContentByAno` | `get.content.ano` | Filtro por ano |
| `POST getContentByMes` | `get.content.mes` | Filtro por mês |
| `POST /AjaxGetanswers` | `get.answers` | Respostas de transparência (JSON) |
| `POST /ajax/get-answers/ouvidoria` | `get.ombudsman.answers` | Respostas da ouvidoria |
| `POST /ajax/new-message/ouvidoria` | `new.message.ombudsman` | Envio de manifestação |
| `POST /contato-ajax/enviar` | `contato-ajax.enviar` | Contato |
| `POST send-survey-ajax` | `send.survey.ajax` | Enquete |
| `POST /noticias-ajax/{categorySlug}` | `noticia-categoria.index` | Paginação de notícias |
| `POST /legislacao/filtro` | `conteudo.legislacao.fill` | Filtro de legislação |
| `POST /diario-oficial/filtro` | `portal-diario.fill` | Filtro do diário |
| `GET /emendas-parlamentares/json` | `portal.emendas-parlamentares.json` | DataTables |

---

## 3. `v6/routes/basepro.php` — CMS `/cms`

### 3.1 Estrutura do arquivo

```php
Route::get('/welcome', 'Admin\MainController@index')->name('welcome');
Route::get('/init',   'Admin\MainController@initApp')->name('init');
Route::get('/error',  'Admin\MainController@error')->name('error');
Route::get('/login',  'Admin\LoginController@login')->name('login');
Route::post('/login', 'Admin\LoginController@postLogin')->name('postLogin');
Route::post('/logout','Admin\LoginController@logout')->name('logout');
…
Route::group(['prefix' => 'error'], function () { … });

Route::group(['prefix' => 'cms', 'before' => 'csrf'], function () {
    …
    require __DIR__ . '/service_letter.php';
});
```

- **12** `Route::group` e **58** `Route::resource` no arquivo.
- O grupo `cms` usa `'before' => 'csrf'` — chave **do Laravel 4**, ignorada no
  Laravel 5.5 (a proteção CSRF vem do middleware group `web`).
- `v6/routes/basepro.php:520` abre um grupo `prefix => 'prefecture'` **vazio**.

### 3.2 Rotas abertas (fora do grupo `cms`)

| Rota | Nome | Controller |
|---|---|---|
| `GET /welcome` | `welcome` | `Admin\MainController@index` |
| `GET /init` | `init` | `Admin\MainController@initApp` |
| `GET /error` | `error` | `Admin\MainController@error` |
| `GET /login` | `login` | `Admin\LoginController@login` |
| `POST /login` | `postLogin` | `Admin\LoginController@postLogin` |
| `POST /logout` | `logout` | `Admin\LoginController@logout` |
| `GET /logout` | (sem nome) | `Admin\LoginController@logout` + `abort(404)` |
| `GET /activate/{email}/{activation_code}` | `activate` | `Admin\ActivationController@activate` |
| `GET password/reset` | `password.request` | `Admin\ForgotPasswordController@showLinkRequestForm` |
| `POST password/email` | `password.email` | `Admin\ForgotPasswordController@sendResetLinkEmail` |
| `GET password/reset/{token}` | `password.reset` | `Admin\ResetPasswordController@showResetForm` |
| `POST password/reset` | (sem nome) | `Admin\ResetPasswordController@reset` |
| `GET /error/403\|404\|500\|503` | `error.403`… | `Admin\ErrorsController` |

### 3.3 Middleware do CMS

| Middleware | Onde é aplicado | Efeito |
|---|---|---|
| `check.logged` | 23 rotas em `basepro.php` + 1 grupo em `service_letter.php` | `CheckLogged`: se `!Sentinel::check()` → `redirect()->route('dashboard.index')` |
| `check.route.permission` | **36 controllers** (construtor) | `CheckRoutePermission`: valida `hasAnyAccess()` pelo **nome da rota**; super-admin passa direto |
| `csrf` (implícito) | grupo `web` | `VerifyCsrfToken` |

`v6/app/Http/Middleware/CheckRoutePermission.php:20-40`:

```php
if (Sentinel::check()) {
    if (!Sentinel::inRole('super-admin')) {
        if (!$this->hasAccess()) {
            flash('Você não tem autorização para acessar esta funcionalidade!')->warning();
            return redirect()->back();
        }
    }
}
if (!Sentinel::check()) {
    return redirect()->route('login');
}
```

### 3.4 Recursos REST do CMS (58 `Route::resource`)

Agrupados por área (prefixo do resource, controller):

| Prefixo | Controller |
|---|---|
| `usuario`, `perfil`, `address`, `state`, `cidade`, `recurso`, `permissao` | `Admin\*` |
| `log` | `Admin\ActivityLogController` |
| `menu`, `menu-item`, `link` | `Prefecture\MenuController`, `MenuItemController`, `LinkController` |
| `categoria-conteudo`, `conteudo`, `configuracoes`, `pagina-estatica` | `Prefecture\CategoryContentController`, `ContentController`, `ConfigurationController`, `StaticPageController` |
| `respostas-lai`, `answer`, `status`, `status-category` | `Prefecture\InformationsRequestController`, `AnswerController`, `StatusController`, `StatusCategoryController` |
| `unidade-gestora` | `Prefecture\ManagementUnitController` |
| `tipo-licitacao`, `modalidade-licitacao`, `licitacao`, `contrato`, `proposta`, `proponentes` | `Prefecture\Bidding\*`, `ContractController`, `ProposalController`, `ProponentController` |
| `chamados-da-ouvidoria`, `tipos-de-chamado`, `assuntos`, `setores-orgaos`, `respostas-prontas`, `formas-de-resposta`, `origens-de-chamado` | `Prefecture\Ombudsman*Controller` |
| `viap` | `Prefecture\ViapController` |
| `emendas-parlamentares`, `emendas-parlamentares-categorias` | `Prefecture\EmendaParlamentar\*` |
| `e-vacina` | `Prefecture\EvacinaController` |
| `formularios-dinamicos` (grupo) | `Prefecture\DynamicForms\Questionnaire\QuestionnaireController` |
| `questionarios-preenchidos` (grupo) | `Prefecture\DynamicForms\FilledQuestionnaire\FilledQuestionnaireController` |

### 3.5 `v6/routes/service_letter.php` — módulo novo da v6

- Prefixos: `carta-de-servicos` + subgrupos `assuntos`, `servicos`, `canais`,
  `etapas`, `documentos`, `anexos`, `pdfs`, `responsaveis`.
- Middleware: `check.logged` no grupo raiz; `check.route.permission` herdado
  dos controllers (7 controllers de `ServiceLetter`).
- 2 rotas de DataTable fora do grupo `cms` (prefixo `datatable`):
  `GET /datatable/carta_de_servicos` e `GET /datatable/carta_de_servicos_assuntos`.

### 3.6 `v6/routes/prefecture.php` — arquivo morto

```php
//Route::group(['prefix' => 'prefecture', 'before' => 'csrf'], function () {
//
//    Route::resource('menu', 'Prefecture\MenuController');
//
//
//});
```

100% comentado em ambas as versões. Deve ser **removido** na v7 (não é
migrado).

---

## 4. `v6/routes/api.php` — API de dados abertos

```php
Route::group(["prefix" => 'v1', 'as' => 'v1.'], function () {
    Route::group(['prefix' => 'secretarias', 'as' => 'secretarias.'], function () {
        Route::get('/', 'Swagger\V1\Portal\SecretariaController@index')->name('index');
    });
    … orgaos, legislacoes, noticias, publicacoes …
});
```

| URI final | Nome | Controller | Resource |
|---|---|---|---|
| `/api/v1/secretarias` | `v1.secretarias.index` | `Swagger\V1\Portal\SecretariaController` | `SecretariaResource` |
| `/api/v1/orgaos` | `v1.orgaos.index` | `Swagger\V1\Portal\OrgaoController` | `OrgaoResource` |
| `/api/v1/legislacoes` | `v1.legislacoes.index` | `Swagger\V1\Portal\LegislationController` | `LegislationResource` |
| `/api/v1/noticias` | `v1.noticias.index` | `Swagger\V1\Portal\NewController` | `NewResource` |
| `/api/v1/publicacoes` | `v1.publicacoes.index` | `Swagger\V1\Portal\PublicationController` | `PublicationResource` |

- Middleware: grupo `api` = `throttle:60,1` + `bindings`.
- Documentação OpenAPI por anotações `@OA\Get` nos controllers; geração por
  `v6/app/Console/Commands/GenerateSwagger.php` →
  `php ./vendor/bin/openapi --bootstrap ./swagger-bootstrap/variables.php --output ./public/swagger.yml ./app/Http/Controllers/Swagger/V1`.
- Há também `GET /api-dados-abertos` no portal
  (`v6/routes/web.php:14`, `Swagger\SwaggerController@index`) servindo a UI.

---
