# Vereadores e Mandatos — Rotas, Controllers e Autorização

> Módulo `vereadores` (id 43). Rotas do portal em `v6/routes/web.php`; do CMS em
> `v6/routes/basepro.php`.

---

## 1. Rotas do portal (público)

Todas estão dentro do grupo `Route::middleware(['check.site.manutention'])`
(`v6/routes/web.php:8`), que bloqueia o portal quando a configuração
`pagina_em_manutencao` está ativa.

| Método | URI | Nome | Controller@action | Linha |
|---|---|---|---|---|
| GET | `/vereadores` | `vereadores` | `Portal\CityCouncilorController@index` | `v6/routes/web.php:140` |
| GET | `/vereadores/{id}` | `vereador` | `Portal\CityCouncilorController@show` | `v6/routes/web.php:141` |
| GET | `/mandatos/` | `portal.mandatos` | `Portal\MandatesController@index` | `v6/routes/web.php:142` |
| GET | `/mandatos/{category}` | `portal.mandato` | `Portal\MandatesController@show` | `v6/routes/web.php:143` |

Observações:

- `{id}` é **numérico e sequencial** (`id` do banco), não slug. Trocar o `id` no slug
  quebra links externos e atalhos.
- `{category}` é o **slug** da `mandate_categories` (`/mandatos/legislaturas`).
- `MandatesController@show` faz `MandateCategory::where('slug',$slug)->first()` sem
  validar existência; com slug inválido, `$category` é `null` e o breadcrumb
  (`Breadcrumbs::view(..., $category->name, ...)`) pode gerar erro.

### 1.1 Breadcrumbs

`v6/routes/breadcrumbs.php:380-401`:

| Chave | Hierarquia |
|---|---|
| `vereadores` | Home > Vereadores |
| `vereador` | Home > Vereadores > {nome} |
| `mandatos` | Home > Mandatos (aponta para `route('portal.mandatos','publicacoes')`) |
| `mandato` | Home > Mandatos > {categoria} |

## 2. Rotas do CMS (`/cms/...`)

O grupo é `Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)`
(`v6/routes/basepro.php:36`). Todos os controllers em `App\Http\Controllers\Prefecture`
herdam de `Prefecture\Controller`, cujo construtor aplica
`$this->middleware('check.route.permission')` (`v6/app/Http/Controllers/Prefecture/Controller.php:22`).

| Método | URI (prefixo `/cms`) | Nome | Controller@action | Linha |
|---|---|---|---|---|
| GET | `/vereadores` | `vereadores.index` | `Prefecture\CityCouncilorController@index` | `v6/routes/basepro.php:214` |
| GET | `/vereadores/create` | `vereadores.create` | `…@create` | idem (resource) |
| POST | `/vereadores` | `vereadores.store` | `…@store` | idem |
| GET | `/vereadores/{id}` | `vereadores.show` | `…@show` | idem |
| GET | `/vereadores/{id}/edit` | `vereadores.edit` | `…@edit` | idem |
| PUT | `/vereadores/{id}` | `vereadores.update` | `…@update` | idem |
| DELETE | `/vereadores/{id}` | `vereadores.destroy` | `…@destroy` | idem |
| GET | `/mandatos` | `mandatos.index` | `Prefecture\MandateController@index` | `v6/routes/basepro.php:215` |
| GET/POST/PUT/DELETE | `/mandatos[...]` | `mandatos.*` | `…@create/@store/@edit/@update/@destroy` | idem |
| GET | `/vereadores-do-mandato/{id}` | `vereadores-do-mandato.edit` | `MandateController@editOrdainingCouncilMembers` | `v6/routes/basepro.php:216` |
| GET | `/categorias-mandatos` | `categorias-mandatos.index` | `Prefecture\MandateCategoryController@index` | `v6/routes/basepro.php:217` |
| GET/POST/PUT/DELETE | `/categorias-mandatos[...]` | `categorias-mandatos.*` | `…@create/@store/@edit/@update/@destroy` | idem |
| POST | `/vereadores/atribuir-mandato` | `atribuir.mandato` | `CityCouncilorController@setMandates` | `v6/routes/basepro.php:218` |
| POST | `/vereadores/excluir-mandato` | `drop.mandato` | `CityCouncilorController@dropMandates` | `v6/routes/basepro.php:219` |
| POST | `/ajax/ajust-vereadores-mandato` | `ajust.vereadores-mantado` | `MandateController@ajustOrder` | `v6/routes/basepro.php:160` |
| POST | `/ajax/ajax-delete-files-vereadores` | `ajax.delete.files.vereadores` | `CityCouncilorController@ajaxDeleteFiles` | `v6/routes/basepro.php:164` |

Observações:

- Os dois últimos ficam no sub-grupo `Route::group(['prefix' => 'ajax'], ...)`
  (`v6/routes/basepro.php:150`).
- O nome `ajust.vereadores-mantado` tem **typo** ("mantado" em vez de "mandato"); é
  o identificador usado no JavaScript da tela de ordenação.
- `atribuir.mandato` e `drop.mandato` usam nomes **não-dotted**, quebrando o padrão
  `<recurso>.<ação>` usado pelo middleware de permissão.
- Não há rota de API JSON específica de vereadores. O consumo por outros módulos é
  feito direto no model (`CityCouncilor::where('status', true)`) em
  `v6/app/Http/Controllers/Prefecture/ContentController.php:204`,
  `v6/app/Http/Controllers/Prefecture/ViapController.php:129` e
  `v6/app/Http/Controllers/Portal/ViapPortalController.php:68`.

## 3. Controllers do portal

### 3.1 `Portal\CityCouncilorController`

`v6/app/Http/Controllers/Portal/CityCouncilorController.php`

```php
public function index(): View
{
    $contents = CityCouncilor::with('archives', 'mandates')->where('status', true)->get();
    $title = 'Vereadores';

    return view("portal.layouts.{$this->getTemplateActived()}.informacoes-instituicao.vereadores.index", compact('title', 'contents'));
}

public function show(int $id): View
{
    $content = CityCouncilor::findOrFail($id);
    $title = 'Vereador - '. $content->name;

    return view("portal.layouts.{$this->getTemplateActived()}.informacoes-instituicao.vereadores.show", compact('title', 'content'));
}
```

Ponto de atenção: `show()` **não** filtra por `status` — um vereador desativado continua
acessível pelo link direto `/vereadores/{id}`.

### 3.2 `Portal\MandatesController`

`v6/app/Http/Controllers/Portal/MandatesController.php`

- `index()`: `MandateCategory::all()` → view de categorias.
- `show(string $slug)`: `MandateCategory::where('slug',$slug)->first()` +
  `Mandate::whereHas('categories', function ($query) use ($slug) { $query->where('slug', '=', $slug); })->get()`.
  **Não filtra `status`** dos mandatos — mandatos inativos também são listados.
- `create/store/edit/update/destroy`: métodos vazios (só `//`) — código morto.

## 4. Controllers do CMS

### 4.1 `Prefecture\CityCouncilorController`

`v6/app/Http/Controllers/Prefecture/CityCouncilorController.php`

| Método | Comportamento |
|---|---|
| `__construct(CityCouncilor, Mandate)` | Chama `menuClicks('vereadores')` e guarda as dependências. |
| `index()` | `$this->councilor->all()`; log `Listou Vereadores`; view `prefecture.councilmen.index`. |
| `create()` | `$this->mandate->all()->pluck('name','id')`; log `Acessou criação de veredores`; view `prefecture.councilmen.create`. |
| `store(CityCouncilorRequest)` | `DB::transaction`: gera `slug` (`str_slug($name)`, com sufixo `'_'.str_random(5)` se duplicado), `status` default `'0'`, cria o vereador, `sync()` de mandatos, upload da foto e criação do `Archive`. Flash e redirect para `vereadores.index`. |
| `show($id)` | Carrega o vereador, o `archive` e os mandatos **ativos** (`Mandate::where('status',true)->get()`); view `prefecture.councilmen.show`. |
| `edit($id)` | Carrega vereador + archive + lista de mandatos; log `Acessou edição de veredores`. |
| `update(Request,$id)` | `DB::transaction`: `update($dataForm)` (sem regenerar slug), `sync()` de mandatos, upload de foto. **Usa `Request` cru, não `CityCouncilorRequest`** — logo, a edição não valida `name`/`party`. |
| `destroy($id)` | `$mandatos = count($vereador->mandates);` — se `!= 0`, recusa com flash de erro; senão `$vereador->delete()` (soft delete). |
| `setMandates(Request)` | Valida `mandates_id` e `role` com `Validator::make` e cria a linha em `councilmen_mandates`. |
| `dropMandates(Request)` | `CouncilmenMandates::find($request->id)` e `delete()` (**remoção física**, pois o model não usa `SoftDeletes`). |
| `ajaxDeleteFiles(Request)` | `Storage::delete("/content/vereadores/{id}/".$archive->name)`, apaga o `Archive` e zera `councilmen.archives_id`. |

### 4.2 `Prefecture\MandateController`

`v6/app/Http/Controllers/Prefecture/MandateController.php`

| Método | Comportamento |
|---|---|
| `__construct(Mandate, MandateCategory)` | `menuClicks('vereadores')` — **o mesmo slug de telemetria do módulo de vereadores**, mesmo para a tela de mandatos. |
| `index()` | `$this->mandate->with('councilmen')->get()`; log `Listou Mandatos`. |
| `create()` | Lista categorias (`pluck('name','id')`); log `Acessou criação de mandato`. |
| `store(MandateRequest)` | `slug` com sufixo aleatório se duplicado; `status` default `'0'`; log `Cadastrou de mandato`. |
| `edit(int $id)` | Carrega mandato + categorias. |
| `update(MandateRequest,$id)` | Mesma geração de slug do `store` — **não exclui o próprio registro em edição da verificação de duplicidade**. |
| `destroy(int $id)` | `$category->delete()` (soft delete) + log `Exclusão de mandato`. |
| `editOrdainingCouncilMembers($id)` | `CouncilmenMandates::with('councilmen')->where('mandates_id',$id)->orderBy('order','asc')->get()`; view `prefecture.mandates.edit-council-members`. |
| `ajustOrder(Request)` | Lê `sortable_data` e renumera `order` de 1 em 1; responde `response()->json(1, 200)`. |

### 4.3 `Prefecture\MandateCategoryController`

`v6/app/Http/Controllers/Prefecture/MandateCategoryController.php` — CRUD padrão:
`index` (log `Listou Categorias de mandatos`), `create`, `store` (slug único com
sufixo aleatório), `edit`, `update`, `destroy` (soft delete + log).

## 5. Form Requests e validações

| Request | Regras | Mensagens |
|---|---|---|
| `CityCouncilorRequest` (`v6/app/Http/Requests/CityCouncilorRequest.php`) | `name` → `required|max:190`; `party` → `required|max:190`; `archives_id` → `image` | "O campo nome é obrigatório.", "O campo nome não pode ultrapassar mais que 190 caracteres.", "O campo partido é obrigatório.", "Selecione uma imagem válida." |
| `MandateRequest` (`v6/app/Http/Requests/MandateRequest.php`) | `mandate_categories_id` → `required|numeric`; `name` → `required|max:190`; `start_year` → `required|numeric`; `end_year` → `required|numeric` | "O campo categoria é obrigatório.", "Não foi possível recuperar o ano de início.", etc. |
| `MandateCategoryRequest` (`v6/app/Http/Requests/MandateCategoryRequest.php`) | `name` → `required|max:190` | "O campo nome é obrigatório.", "…mais que 190 caracteres." |

Observações:

- `authorize()` retorna `true` em todos — **a autorização é feita só pelo middleware**
  `check.route.permission` e pelos `has_access()` nas views.
- Nenhum request valida `Rule::exists` — um `mandate_categories_id` inexistente gera
  violação de FK capturada pelo `catch (\PDOException)`.
- `setMandates` **não** usa Form Request: valida inline com `Validator::make`
  (mensagens "O campo mandato é obrigatório.", "O campo cargo é obrigatório.",
  "O campo cargo só aceita até 190 caracteres.").

## 6. Permissões exigidas

O helper `has_access()` (`v6/app/Library/Helper/appHelpers.php:22`) delega ao
Sentinel (`hasAccess`/`hasAnyAccess`); `super-admin` sempre passa.

| Slug verificado | Onde | Efeito |
|---|---|---|
| `vereadores.create` | `v6/resources/views/prefecture/councilmen/index.blade.php` | Exibe o botão "Cadastrar Vereador". |
| `vereadores.edit` | `v6/resources/views/prefecture/councilmen/index.blade.php` | Exibe o botão de edição. |
| `agenda.delete` | `v6/resources/views/prefecture/councilmen/index.blade.php` | **Bug**: é o slug usado para exibir o botão de **excluir vereador**. Deveria ser `vereadores.delete`. |
| `mandatos.*` | `v6/resources/views/prefecture/mandates/index.blade.php` | Exibe a coluna "Ações". |
| `mandatos.edit` | `v6/resources/views/prefecture/mandates/index.blade.php` | Exibe "Vereadores do mandato" e "Editar". |
| `mandatos.delete` | `v6/resources/views/prefecture/mandates/index.blade.php` | Exibe o botão de exclusão. |
| `categorias-mandatos.*` | `v6/resources/views/prefecture/mandate-categories/index.blade.php` | Exibe a coluna "Ações". |
| `categorias-mandatos.edit` | idem | Exibe "Editar". |
| `categorias-mandatos.delete` | idem | Exibe "Excluir". |

Além disso, o middleware `CheckRoutePermission`
(`v6/app/Http/Middleware/CheckRoutePermission.php`) bloqueia a rota CMS conforme o
**nome** da rota (ex.: `vereadores.index`, `mandatos.store`,
`vereadores-do-mandato.edit`, `atribuir.mandato`, `drop.mandato`,
`ajust.vereadores-mantado`, `ajax.delete.files.vereadores`).

## 7. Auditoria (`activity()`)

Todos os controllers do CMS registram trilha de auditoria com o pacote de activity log
(Sentinel):

```php
activity()->performedOn($this->councilor)
    ->causedBy(Sentinel::getUser())
    ->withProperty('Detalhe', 'Listado por:'.Sentinel::getUser()->name)
    ->log('Listou Vereadores');
```

| Modelo | Evento registrado |
|---|---|
| `CityCouncilor` | `Listou Vereadores`, `Acessou criação de veredores`, `Acessou edição de veredores`, `Cadastro de arquivo`, `Exclusão de arquivo` |
| `Mandate` | `Listou Mandatos`, `Acessou criação de mandato`, `Acessou edição de mandato`, `Cadastrou de mandato`, `Editou de mandato`, `Exclusão de mandato` |
| `MandateCategory` | `Listou Categorias de mandatos`, `Acessou criação categorias de mandatos`, `Acessou edição de categoria de mandato`, `Cadastrou de categoria de mandato`, `Editou categoria de mandato`, `Exclusão de categoria de mandato` |

O `performedOn` recebe uma instância **não persistida** (`new Mandate()`,
`new MandateCategory()`) nos métodos de listagem/criação/edição — o log fica
desvinculado de um registro real.

## 8. Telemetria de menu

`menuClicks('vereadores')` (`v6/app/Helpers/functions.php:865`) incrementa
`menu_orders.clicks` para o slug `vereadores`. É chamado no construtor de
`Prefecture\CityCouncilorController` **e** de `Prefecture\MandateController` — ou
seja, abrir a tela de mandatos também conta como clique no módulo "Vereadores".

## 9. API / Resources

Não existe API Resource específica de vereadores. Os Resources existentes no Swagger
(`v6/app/Http/Resources/Swagger/V1/Portal/OrgaoResource.php`) não representam
vereadores.
