# Carta de Serviços — Rotas e Controllers

## 1. Rotas do CMS — `v6/routes/service_letter.php`

O arquivo é carregado por `require` dentro do grupo `Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)` de `v6/routes/basepro.php:35`, na linha **512**. Todos os prefixos abaixo são, portanto,prefixados por `/cms`.

O grupo raiz do arquivo aplica `middleware => 'check.logged'`:

```php
Route::group(['prefix' => 'carta-de-servicos', 'as' => 'carta-de-servicos.', 'middleware' => 'check.logged'], function () {
```

Cada controller, além disso, registra `check.route.permission` no construtor (`$this->middleware('check.route.permission');`), que exige **login via Sentinel** e permissão por rota (ver §4).

### 1.1 Configurações

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| GET | `/cms/carta-de-servicos/configuracoes` | `carta-de-servicos.config.index` | `Prefecture\ServiceLetter\ServiceLetterConfigController@index` |
| PUT | `/cms/carta-de-servicos/configuracoes` | `carta-de-servicos.config.update` | `Prefecture\ServiceLetter\ServiceLetterConfigController@update` |

### 1.2 Assuntos

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| GET | `/cms/carta-de-servicos/assuntos` | `carta-de-servicos.assuntos.index` | `ServiceLetterSubjectController@index` |
| GET | `/cms/carta-de-servicos/assuntos/criar` | `carta-de-servicos.assuntos.create` | `…@create` |
| POST | `/cms/carta-de-servicos/assuntos` | `carta-de-servicos.assuntos.store` | `…@store` |
| GET | `/cms/carta-de-servicos/assuntos/{id}/edit` | `carta-de-servicos.assuntos.edit` | `…@edit` |
| PUT | `/cms/carta-de-servicos/assuntos/{id}` | `carta-de-servicos.assuntos.update` | `…@update` |
| DELETE | `/cms/carta-de-servicos/assuntos` | `carta-de-servicos.assuntos.destroy` | `…@destroy` |
| POST | `/cms/carta-de-servicos/assuntos/delete-all` | `carta-de-servicos.assuntos.destroyAll` | `…@destroyAll` |

> **Padrão inconsistente:** o `destroy` usa `DELETE` com o id no **corpo** (`$request->input('id')`), não na URL — não é `Route::resource`.

### 1.3 Serviços

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| GET | `/cms/carta-de-servicos/servicos` | `carta-de-servicos.servicos.index` | `ServiceLetterController@index` |
| GET | `/cms/carta-de-servicos/servicos/criar` | `carta-de-servicos.servicos.create` | `…@create` |
| POST | `/cms/carta-de-servicos/servicos` | `carta-de-servicos.servicos.store` | `…@store` |
| GET | `/cms/carta-de-servicos/servicos/{id}/edit` | `carta-de-servicos.servicos.edit` | `…@edit` |
| PUT | `/cms/carta-de-servicos/servicos/{id}` | `carta-de-servicos.servicos.update` | `…@update` |
| DELETE | `/cms/carta-de-servicos/servicos` | `carta-de-servicos.servicos.destroy` | `…@destroy` |
| POST | `/cms/carta-de-servicos/servicos/delete-all` | `carta-de-servicos.servicos.destroyAll` | `…@destroyAll` |
| POST | `/cms/carta-de-servicos/servicos/set-status` | `carta-de-servicos.servicos.setStatus` | `…@setStatus` |
| POST | `/cms/carta-de-servicos/servicos/set-destaque` | `carta-de-servicos.servicos.setDestaque` | `…@setDestaque` |

> `setStatus` e `setDestaque` **não são consumidos por nenhum blade ou JS** do módulo (verificado por grep em `resources/views/prefecture/service-letter/` e `public/assets/js/custom/contents/service-letter/`). Rotas funcionais sem caller — ver `08-GAPS-E-DIVIDAS.md`.

### 1.4 Canais de atendimento

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| POST | `/cms/carta-de-servicos/canais` | `carta-de-servicos.canais.store` | `ServiceLetterChannelController@store` |
| PUT | `/cms/carta-de-servicos/canais/{id}` | `carta-de-servicos.canais.update` | `…@update` |
| DELETE | `/cms/carta-de-servicos/canais/{id}` | `carta-de-servicos.canais.destroy` | `…@destroy` |

### 1.5 Etapas

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| POST | `/cms/carta-de-servicos/etapas` | `carta-de-servicos.etapas.store` | `ServiceLetterStepController@store` |
| PUT | `/cms/carta-de-servicos/etapas/{id}` | `carta-de-servicos.etapas.update` | `…@update` |
| DELETE | `/cms/carta-de-servicos/etapas/{id}` | `carta-de-servicos.etapas.destroy` | `…@destroy` |
| POST | `/cms/carta-de-servicos/etapas/order` | `carta-de-servicos.etapas.order` | `…@updateOrder` |

> A rota `etapas.order` **não é chamada por nenhum JS** — o `modulos.js` não implementa sortable. Ver `08-GAPS-E-DIVIDAS.md`.

### 1.6 Documentos necessários

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| POST | `/cms/carta-de-servicos/documentos` | `carta-de-servicos.documentos.store` | `ServiceLetterDocumentController@store` |
| PUT | `/cms/carta-de-servicos/documentos/{id}` | `carta-de-servicos.documentos.update` | `…@update` |
| DELETE | `/cms/carta-de-servicos/documentos/{id}` | `carta-de-servicos.documentos.destroy` | `…@destroy` |

### 1.7 Anexos

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| POST | `/cms/carta-de-servicos/anexos` | `carta-de-servicos.anexos.store` | `ServiceLetterAttachmentController@store` |
| PUT | `/cms/carta-de-servicos/anexos/{id}` | `carta-de-servicos.anexos.update` | `…@update` |
| DELETE | `/cms/carta-de-servicos/anexos/{id}` | `carta-de-servicos.anexos.destroy` | `…@destroy` |

### 1.8 PDFs

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| GET | `/cms/carta-de-servicos/pdfs` | `carta-de-servicos.pdfs.index` | `ServiceLetterPdfController@index` |
| POST | `/cms/carta-de-servicos/pdfs/gerar` | `carta-de-servicos.pdfs.generate` | `…@generate` |
| GET | `/cms/carta-de-servicos/pdfs/{id}/download` | `carta-de-servicos.pdfs.download` | `…@download` |
| GET | `/cms/carta-de-servicos/pdfs/{id}/visualizar` | `carta-de-servicos.pdfs.stream` | `…@stream` |
| DELETE | `/cms/carta-de-servicos/pdfs/{id}` | `carta-de-servicos.pdfs.destroy` | `…@destroy` |

### 1.9 Responsáveis

| Método | URI | Nome | Controller@ação |
|---|---|---|---|
| POST | `/cms/carta-de-servicos/responsaveis` | `carta-de-servicos.responsaveis.store` | `ServiceLetterController@storeResponsavel` |
| PUT | `/cms/carta-de-servicos/responsaveis/{id}` | `carta-de-servicos.responsaveis.update` | `ServiceLetterController@updateResponsavel` |
| DELETE | `/cms/carta-de-servicos/responsaveis/{id}` | `carta-de-servicos.responsaveis.destroy` | `ServiceLetterController@destroyResponsavel` |

### 1.10 DataTables (fora do grupo `check.logged`)

```php
Route::group(['prefix' => 'datatable', 'as' => 'datatable.'], function () {
    Route::get('/carta_de_servicos', 'Prefecture\DatatableController@get_service_letter_services_list')->name('servicos');
    Route::get('/carta_de_servicos_assuntos', 'Prefecture\DatatableController@get_service_letter_subjects_list')->name('assuntos');
});
```

> **Sobre a proteção dessas rotas:** o `DatatableController` do CMS (`v6/app/Http/Controllers/Prefecture/DatatableController.php`) **não** define construtor próprio, logo herda o de `Prefecture\Controller` (`v6/app/Http/Controllers/Prefecture/Controller.php:20-24`), que registra `check.route.permission`. Esse middleware, para uma rota cujo nome contém o segmento `datatable`, retorna "liberado" na checagem de acesso (`$openRoutes` de `v6/app/Http/Middleware/CheckRoutePermission.php` inclui `datatable`) — **mas ainda exige usuário logado** (a mesma middleware redireciona para `login` quando `Sentinel::check()` é falso). Portanto: **autenticado = acessa; anônimo = redirecionado para login**. O que falta é a checagem de **permissão específica** de recurso, inexistente para o módulo Carta de Serviços.

## 2. Rotas do Portal — `v6/routes/web.php:53-56`

Dentro do grupo `Route::middleware(['check.site.manutention'])` (`v6/routes/web.php:7`), middleware `web` aplicado em `RouteServiceProvider::mapWebRoutes`.

| Método | URI | Nome | Controller@ação | View |
|---|---|---|---|---|
| GET | `/carta-de-servicos` | `carta-servicos.index` | `Portal\ServiceLetterController@index` | `…{modelo}/carta-servicos.index` (ou redirect) |
| GET | `/carta-de-servicos/pdf` | `carta-servicos.pdf` | `Portal\ServiceLetterController@pdf` | download do arquivo |
| GET | `/carta-de-servicos/{slug}` | `carta-servicos.show` | `Portal\ServiceLetterController@show` | `…{modelo}/carta-servicos.show` |
| GET | `/carta-de-servicos/{slug}/{slug2}` | `carta-servicos.view` | `Portal\ServiceLetterController@view` | `…{modelo}/carta-servicos.view` |

Ordem crítica: `/carta-de-servicos/pdf` vem **antes** de `/carta-de-servicos/{slug}`, senão "pdf" seria interpretado como slug.

O template é resolvido por `getTemplateActived()` (`v6/app/Http/Controllers/Admin/Controller.php:39`), que lê `configurations.key = 'layout'` (valor atual no banco: `modelo1`) e valida que `portal.layouts.{layout}.home.index` existe — **abortando com 404** caso contrário. Só `modelo1..modelo6` têm `home/index`; `default` e `newsbit` não têm e seriam rejeitados.

## 3. Controllers — validações

O módulo **não usa Form Requests** (não há `v6/app/Http/Requests/Prefecture/ServiceLetter/`). Toda validação é inline.

| Controller@ação | Regras |
|---|---|
| `ServiceLetterController@store` / `@update` | `nome` required\|string\|max:255<br>`secretaria_id` required\|exists:contents,id<br>`menu_acesso_rapido` required\|in:cidadao,empresa,servidor<br>`tipo_exibicao` required\|in:pagina,link_externo |
| `ServiceLetterSubjectController@store` / `@update` | `nome` required\|string\|max:255 |
| `ServiceLetterChannelController@store` | `service_letter_id` required\|exists:service_letters,id<br>`tipo` required\|in:online,telefone,presencial |
| `ServiceLetterChannelController@update` | **nenhuma** |
| `ServiceLetterStepController@store` | `service_letter_id` required\|exists:service_letters,id<br>`titulo` required\|string\|max:255<br>`numero` required\|integer |
| `ServiceLetterStepController@update` | **nenhuma** |
| `ServiceLetterDocumentController@store` | `service_letter_id` required\|exists:service_letters,id<br>`nome` required\|string\|max:255 |
| `ServiceLetterDocumentController@update` | **nenhuma** |
| `ServiceLetterAttachmentController@store` | `service_letter_id` required\|exists:service_letters,id<br>`arquivo` required\|file\|max:10240 (10 MB) |
| `ServiceLetterAttachmentController@update` | **nenhuma** |
| `ServiceLetterController@storeResponsavel` / `@updateResponsavel` | Validação **customizada** via `Validator::make` dentro de `validateResponsavel()`:<br>• tudo vazio → `telefone` required<br>• sem e-mail **e** sem telefone → `nome` e `cargo` required<br>• demais casos → sem regras |
| `ServiceLetterConfigController@update` | **nenhuma** |
| `ServiceLetterPdfController@generate` | **nenhuma** (valida apenas se existe config e se há serviços publicados) |

> **Armadilha verificada:** os controllers de update não validam nada. Um `PUT` com corpo vazio zera campos. Além disso, `ServiceLetterChannelController@update` aceita `tipo` arbitrário — mas como `$request->only([...])` não inclui `tipo`… na verdade **inclui** (`'tipo'` está na lista), logo um `tipo` inválido é persistido sem validação.

## 4. Permissões exigidas

### 4.1 Middleware `check.route.permission`

`v6/app/Http/Middleware/CheckRoutePermission.php` — compara o **nome da rota** com as permissões do usuário Sentinel. Regras relevantes:

- Usuário `super-admin` passa sempre.
- Rotas cujo nome contenha `login`, `logout`, `datatable`, `dashboard`, `error`, `ajax`, `info-request-status-update` são liberadas (segmento "aberto").
- Rotas cujo nome contenha `edit`/`create`/`delete` mapeiam para `update`/`store`/`destroy`.
- Fallback: `hasAnyAccess([$nomeDaRota, $segmentoDerivadoDoUrl])`, onde o segmento é `Request::segment(3) . '.index'` (ou `.create`/`.edit` conforme `segment(3)` seja `cadastrar`/`editar`).

Para o módulo da Carta de Serviços, o nome da rota é `carta-de-servicos.servicos.index` etc. — os segmentos contêm `carta-de-servicos`, `servicos`, `index`. Nenhum está na lista de "abertos", e o segmento derivado (`Request::segment(3)`) resolve para o **terceiro** segmento da URL, que é `servicos` / `assuntos` / `canais` / `etapas` / `documentos` / `anexos` / `pdfs` / `responsaveis` — **não** `carta-de-servicos`. Consequência: as permissões esperadas seriam `carta-de-servicos.servicos.index` ou `servicos.index`.

### 4.2 Permissões no código das views

Os blades usam `has_access([...])` com o helper `v6/app/Library/Helper/appHelpers.php:22`:

| Chave usada nas views | Onde |
|---|---|
| `carta-de-servicos.index` | botão "Gerar PDF" e "Configurações" em `services/index.blade.php`; botão "Gerar Novo PDF" em `pdfs/index.blade.php` |
| `carta-de-servicos.create` | botão "Cadastrar Serviço" / "Cadastrar Assunto" |
| `carta-de-servicos.delete` | exclusão em lote (serviços e assuntos) |
| `carta-de-servicos.destroy` | botão de excluir PDF em `pdfs/index.blade.php` |

> **Inconsistência verificada:** a chave `carta-de-servicos.destroy` **não existe** em nenhum outro lugar do módulo; a permissão de fato usada pelo controller é derivada do nome da rota (`carta-de-servicos.pdfs.destroy` → `destroy`). As tabelas `permissions` do banco contêm apenas slugs genéricos (`index`, `create`, `edit`, `delete`, `inactive`, `inactivate`, `restore`, `show`, `publish` — 9 registros), e as permissões efetivas dos papéis são chaves compostas no JSON da coluna `roles.permissions` (ex.: `"noticias.index": true`). **Nenhum papel do banco possui chaves `carta-de-servicos.*`** — verificado: o papel `administrador` (único com permissões) não contém nenhuma chave `carta-de-servicos`, `ouvidoria` nem `ombudsman`. Na prática, **só `super-admin` enxerga os botões do módulo** até que as permissões sejam cadastradas manualmente.

### 4.3 Matriz de acesso prática

| Ação | Quem consegue (no estado atual do banco) |
|---|---|
| Acessar `/cms/carta-de-servicos/**` | `super-admin` (passa pelo `check.route.permission`) ou quem tenha `carta-de-servicos.<segmento>.<verbo>` |
| Ver as listagens DataTable | qualquer usuário **logado** (a rota `datatable.*` é tratada como aberta na checagem de permissão, mas o `check.route.permission` exige login) |
| Portal público | qualquer visitante (só `check.site.manutention`) |

## 5. Audit trail (activity log)

Todos os controllers CMS registram eventos com `spatie/laravel-activitylog`:

| Evento | Registro |
|---|---|
| Listagem de serviços | `'Listagem de serviços'` |
| Listagem de assuntos | `'Listagem de assuntos'` |
| Visualização de configurações | `'Visualização de configurações'` |
| Cadastro/edição/exclusão de serviço | `'Cadastro de serviço'`, `'Edição de serviço'`, `'Exclusão de serviço'` |
| Cadastro/edição/exclusão de assunto | idem, com "assunto" |
| Canal | `'Cadastro de canal'`, `'Atualização de canal'` |
| Etapa | `'Cadastro de etapa'`, `'Atualização de etapa'` |
| Documento | `'Cadastro de documento'`, `'Atualização de documento'` |
| Anexo | `'Cadastro de anexo'` (apenas no store — update e destroy **não** registram) |
| PDF | `'Geração de PDF'` |

## 6. Breadcrumbs — `v6/routes/breadcrumbs.php:463-483`

```php
Breadcrumbs::register('carta-servicos', function ($breadcrumbs) {
    $breadcrumbs->push('Carta de Serviços', route('carta-servicos.index'));
});
Breadcrumbs::register('carta-servicos-show', function ($breadcrumbs, $subject) { … });
Breadcrumbs::register('carta-servicos-view', function ($breadcrumbs, $service) { … });
```

## 7. Endpoints JSON do CMS (contrato de resposta)

| Endpoint | Sucesso | Erro |
|---|---|---|
| POST/PUT/DELETE canais, etapas, documentos, anexos | `{"success": true, "message": "…", "data": {…}}` | `{"success": false, "errors": ["…"]}` com HTTP 500 |
| POST responsáveis | `{"success": true, "message": "…", "data": {…}}` | `{"success": false, "errors": [...]}` com HTTP **422** (falha de validação) ou 500 |
| DELETE serviços/assuntos | `{"success": "Serviço excluído com sucesso."}` | `{"success": false, "errors": [...]}` 500 |
| POST `*/delete-all` | `{"success": true}` | `{"success": false}` 500 |
| POST `servicos/set-status`, `set-destaque` | redirect + flash | redirect + flash de erro |

> **Divergência de contrato verificada:** `storeResponsavel` devolve **422** com `errors` em caso de validação, enquanto os demais endpoints devolvem **500**. O JS trata os dois (`xhr.status === 422` → mostra erros; senão → toast genérico), mas a semântica difere entre recursos.
