# Vereadores e Mandatos — Gaps, Dúvidas e Riscos

> Módulo `vereadores` (id 43). **Novo na v6** — a `v5` não possui nenhum equivalente.

---

## 1. Bugs e defeitos encontrados

| # | Severidade | Local | Descrição |
|---|---|---|---|
| B1 | **Alta** | `v6/resources/views/prefecture/councilmen/index.blade.php` | O botão de **excluir vereador** é controlado por `has_access(['agenda.delete'])` — slug de outro módulo. Quem tiver `agenda.delete` vê o botão; quem deveria ter (`vereadores.delete`) não vê. |
| B2 | **Alta** | `v6/app/Http/Controllers/Prefecture/CityCouncilorController.php` (`update`) | A edição **não** valida com `CityCouncilorRequest` (usa `Request` cru): nome vazio ou partido vazio passam; só o `store` valida. |
| B3 | **Alta** | `v6/app/Http/Controllers/Prefecture/MandateController.php` (`update`) | A verificação de slug duplicado não exclui o registro em edição → cada gravação gera um slug novo (`slug_xxxxx`), acumulando slugs órfãos e quebrando links. |
| B4 | **Alta** | `v6/app/Http/Controllers/Prefecture/CityCouncilorController.php` (`store`/`update`) | O `sync()` de mandatos insere no pivot sem `role` (NOT NULL) — se o campo `mandates_id` voltar ao formulário, o cadastro quebra com `PDOException` e a transação é desfeita. O caminho está morto porque o formulário não tem o campo. |
| B5 | **Alta** | `v6/app/Http/Controllers/Prefecture/CityCouncilorController.php` (`destroy`) | `count($vereador->mandates)` carrega todas as linhas do pivot em memória; deveria ser `exists()`. Além disso, o count considera **todos** os mandatos (inclusive inativos), enquanto a mensagem diz "mandatos ativos". |
| B6 | **Média** | `v6/app/Http/Model/Prefecture/CouncilmenMandates.php` | Não usa `SoftDeletes`, embora a tabela tenha `deleted_at` → a remoção da associação é física e irreversível. |
| B7 | **Média** | `v6/app/Http/Model/Prefecture/Mandate.php:26-29` e `CouncilmenMandates.php:18-20` | Relações declaradas como `hasOne` onde caberia `belongsTo`. Funcionam por acaso; quebram qualquer código que espere coleção ou que aplique `withTrashed`. |
| B8 | **Média** | `v6/database/migrations/2024_09_11_104812_create_councilmen_mandates_table.php` | Pivot sem FKs, sem índice único `(councilmen_id, mandates_id)` e sem índice para `where('mandates_id')`. Permite órfãos e duplicatas. |
| B9 | **Média** | `v6/app/Http/Controllers/Portal/CityCouncilorController.php:34` | `show()` não filtra por `status`: vereador desativado continua público por link direto. |
| B10 | **Média** | `v6/app/Http/Controllers/Prefecture/CityCouncilorController.php` | `archives.path` é gravado como `storage/vereadores/{id}/{nome}`, mas o arquivo é gravado em `content/vereadores/{id}/{nome}`. O carrossel corrige com *fallback* de string; as demais views (portal, CMS) usam o caminho direto `storage/content/vereadores/...`. |
| B11 | **Média** | `v6/resources/views/prefecture/councilmen/show.blade.php` | O `confirmation` do botão "Excluir" (mandato atribuído) tem o `form.submit()` **comentado** → a exclusão da associação pode não ser executada. |
| B12 | **Média** | `v6/app/Http/Controllers/Prefecture/CityCouncilorController.php` (`setMandates`) | O `order` da nova associação fica `NULL`; no PostgreSQL o valor NULL vai para o fim da ordenação. |
| B13 | **Baixa** | `v6/app/Http/Controllers/Prefecture/MandateController.php` (`ajustOrder`) | Só renumera os itens enviados; linhas não enviadas mantêm o `order` antigo, podendo duplicar posições. Não valida se os pids pertencem ao mandato. |
| B14 | **Baixa** | `v6/app/Http/Controllers/Prefecture/MandateController.php` (`__construct`) | `menuClicks('vereadores')` na tela de mandatos — telemetria atribuída ao módulo errado. |
| B15 | **Baixa** | `v6/app/Http/Controllers/Prefecture/MandateCategoryController.php` (`update`) | Não regenera nem verifica unicidade do `slug`. |
| B16 | **Baixa** | `v6/app/Http/Controllers/Portal/MandatesController.php` | `create/store/edit/update/destroy` são métodos vazios — código morto. |
| B17 | **Baixa** | `v6/resources/views/portal/layouts/modelo1/informacoes-instituicao/mandates/show.blade.php` | N+1: uma query por mandato dentro do `@foreach`. |
| B18 | **Baixa** | 12 arquivos Blade do portal | `trata_nome()` duplicada; a abreviação de nome não tem teste nem é reutilizável. |
| B19 | **Baixa** | `v6/routes/basepro.php:160` | Nome da rota com typo: `ajust.vereadores-mantado`. |
| B20 | **Baixa** | `v6/app/Http/Controllers/Prefecture/CityCouncilorController.php` | A tela de listagem do CMS **não** é paginada no servidor (DataTable client-side com todos os registros no HTML). |
| B21 | **Baixa** | `v6/app/Http/Controllers/Prefecture/CityCouncilorController.php` | `update()` não regenera o `slug` ao renomear o vereador → a URL pública fica dessincronizada do nome exibido. |

## 2. Funcionalidades incompletas ou mortas

| Item | Situação |
|---|---|
| Seleção de mandatos no cadastro do vereador | **Removida do formulário**, mas o controller ainda processa `mandates_id` (`create`/`edit` passam `$mandates` à view, que não usa). Código morto + bug latente (B4). |
| Endpoints REST de recursos | Inexistentes. Não há API JSON de vereadores ou mandatos. |
| Exportação (Excel/PDF/CSV) | Inexistente. |
| Paginação e busca no portal | Inexistentes (`->get()` sem filtros). |
| Cache | Inexistente; toda visita ao portal consulta o banco. |
| Fila / processamento assíncrono | Inexistente; upload e geração de imagem são síncronos. |
| Thumbnails | Inexistente; o carrossel recorta a imagem original no CSS (`object-fit: cover`). |
| "Ver todos" do carrossel | Aponta para `route('vereadores')` — funciona. |
| Filtro por partido no portal | Inexistente. |
| Histórico de Vereadores (mandatos antigos) | Existe indiretamente: o mesmo cadastro de vereador recebe vários mandatos; não há, porém, marcação de "vereador em Exercício" vs. "ex-vereador" além do `status`. |

## 3. Riscos de migração de schema (v6 → v7)

| # | Risco | Probabilidade | Impacto | Mitigação |
|---|---|---|---|---|
| R1 | `councilmen_mandates` com `councilmen_id`/`mandates_id` órfãos (sem FK) | Média | Alto — impede criar as FKs na v7 | Script de saneamento: `DELETE FROM councilmen_mandates WHERE councilmen_id NOT IN (SELECT id FROM councilmen) OR mandates_id NOT IN (SELECT id FROM mandates)` |
| R2 | Duplicatas de `(councilmen_id, mandates_id)` | Média | Alto — impede criar o unique composto | Agrupar e manter apenas a linha de menor `id` (ou a mais antiga) |
| R3 | `role` NULL/vazio em linhas antigas | Baixa | Alto — impede `NOT NULL` no novo schema | Verificar e preencher antes da migration |
| R4 | `order` NULL ou duplicado | **Alta** | Médio — ordenação inconsistente no portal | `UPDATE councilmen_mandates SET order = rn FROM (SELECT id, row_number() OVER (PARTITION BY mandates_id ORDER BY id) rn FROM councilmen_mandates) s WHERE councilmen_mandates.id = s.id` |
| R5 | `slug` duplicado em `councilmen`/`mandates`/`mandate_categories` | Média | Alto — impede índice unique | Desambiguar com sufixo antes de criar o índice |
| R6 | `contents.councilmen_id` ou `viap.councilman_id` apontando para deletados | Baixa | Alto — impede criar as FKs | `UPDATE contents SET councilmen_id = NULL WHERE councilmen_id IN (SELECT id FROM councilmen WHERE deleted_at IS NOT NULL)` |
| R7 | Links externos para `/vereadores/{id}` (id numérico) | Média | Médio | Manter o id ou expor também a rota por slug com redirecionamento 301 |
| R8 | `archives.path` divergente do disco real | **Alta** | Médio | Migrar os `path` para `storage/content/vereadores/...` e eliminar o *fallback* do carrossel |
| R9 | Soft delete x exclusão física no pivot | Baixa | Médio | Definir a política (recomendação: `SoftDeletes` no pivot) e migrar as remoções já feitas |
| R10 | Dependência do `hasOne` em `Mandate::categories()` | — | Baixo | Renomear para `belongsTo` e atualizar todos os consumidores (`mandates/index.blade.php` usa `$content->categories->name`) |

## 4. Itens da v5 que foram removidos na v6 (ou vice-versa)

Nada foi removido: **a v6 introduz o módulo**. Detalhamento no mapa abaixo.

## 5. Mapa v5 → v6

### 5.1 O que existia na v5

**Nada.** Verificado por busca textual em `v5/routes/` por
`vereadores|councilmen|mandat|câmara|legislatura` e por listagem de
`v5/app/Http/Model/Prefecture/`: **não há** rota, controller, model, migration, view
ou seed de vereadores/mandatos na v5.

| Artefato | v5 | v6 |
|---|---|---|
| Rotas públicas | — | `v6/routes/web.php:140-143` (`/vereadores`, `/vereadores/{id}`, `/mandatos/`, `/mandatos/{category}`) |
| Rotas CMS | — | `v6/routes/basepro.php:214-219`, `160`, `164` |
| Migrations | — | 4 tabelas criadas em `2024_09_11` |
| Models | — | `CityCouncilor`, `Mandate`, `MandateCategory`, `CouncilmenMandates` |
| Views do portal | — | 24 arquivos Blade (6 temas × 4 telas) + carrossel do `modelo5` |
| Views do CMS | — | 15 arquivos Blade em `prefecture/{councilmen,mandates,mandate-categories}` |
| Seeds | — | `MandateCategoryTableSeeder`, `AddVereadoresCarouselToLayout` |
| Módulo no CMS | — | `modulos` id 43, `status = false` (desligado por padrão) |

### 5.2 Lacuna funcional que isso representa para o município

Antes da v6, o portal **não expunha a composição da Câmara Municipal**. Concretamente:

1. **Não havia cadastro de atores políticos.** O cidadão não conseguia saber quem são
   os vereadores, a que partido pertencem, ou quais são suas biografias.
2. **Não havia informação institucional da Casa Legislativa.** Não havia canal
   público para a composição da mesa diretora, das comissões permanentes ou das
   legislaturas (mandatos) — dados exigidos pela transparência do Legislativo e
   por lei federal de acesso à informação.
3. **Não havia identificação de autores em documentos do portal.** Requerimentos,
   ofícios e solicitações não podiam ser atribuídos a um parlamentar; o campo
   `contents.councilmen_id` (criado em 2026) e a view SQL `requerimentos` (que
   expõe a coluna `requerente`) são o primeiro sintoma dessa carência.
4. **Não havia vínculo de gabinete.** O módulo VIAP (`viap.councilman_id`) depende
   diretamente de `councilmen`; sem o cadastro, o VIAP não poderia classificar
   atendimentos como "Gabinete".
5. **Não havia carrossel institucional na home.** O `modelo5` ganhou, na v6, uma
   seção de vere-SPAN (componente `vereadores-carousel` do layout), usando o tema
   institucional como vitrine.

### 5.3 O que precisa ser decidido/replicado na v7

| Decisão | Detalhe |
|---|---|
| D1 | **Escopo**: manter o módulo como "Vereadores + Mandatos" (4 tabelas) ou separar em dois módulos (`vereadores` e `mandatos`)? Na v6 os dois compartilham o `modulo_id` 43 e a mesma telemetria. |
| D2 | **Exclusão de vereador com mandato**: manter o bloqueio (recomendado, é a regra R4) ou permitir exclusão com desvinculação automática dos mandatos? |
| D3 | **Soft delete no pivot**: decidir se a remoção de uma associação é reversível (`SoftDeletes`) e, em caso positivo, migrar as remoções já feitas. |
| D4 | **Campos livres vs. enumerados**: `party` e `role` são texto livre na v6. Modelar como tabela de partidos/cargos ou como enums (impacto na operação). |
| D5 | **Mandato "em exercício"**: separar a modelagem de "vereador atual" de "ex-vereador"? Hoje só existe `status` global. |
| D6 | **URL do perfil**: manter `/vereadores/{id}` (id numérico) ou migrar para `/vereadores/{slug}` com redirecionamento dos links antigos? |
| D7 | **Imagens**: migrar `archives.path` para o caminho real e padronizar placeholder (`/assets/images/perfil.jpg` no portal x `/img/sem-imagem.png` no carrossel — hoje são dois placeholders diferentes). |
| D8 | **Permissões**: corrigir `agenda.delete` → `vereadores.delete` e criar as linhas de permissão correspondentes no seed de roles. |
| D9 | **Carrossel**: manter como elemento de layout configurável (como na v6) ou incorporar fixamente à home do tema `modelo5`? |
| D10 | **Multi-tema**: na v6 há 6 cópias das views. Decidir se a v7 mantém a multiplicidade de temas (com um único componente Blade compartilhado) ou fixa um tema único. |
| D11 | **Auditoria**: replicar o `activity()` (com `performedOn` real, e não uma instância não persistida) para as ações de criação/edição/exclusão. |
| D12 | **API**: decidir se o portal da v7 consumirá os dados de vereadores por API interna ou por Eloquent direto (na v6 é Eloquent direto, com consultas repetidas nos controllers). |
| D13 | **Paginação e busca**: a listagem pública não pagina; com dezenas de vereadores em municípios maiores, avaliar paginação/lazy loading. |
| D14 | **Integração com os demais módulos novos** (`sessoes`, `pauta-do-dia`, `transmissoes`, `tv-camara`): vincular o vereador a sessões e a pauta do dia, de modo que a composição da Câmara possa ser exibida em cada sessão. |

## 6. Perguntas em aberto para o negócio

1. Um vereador que saiu da Câmara deve continuar público, com o histórico de
   mandatos, ou ser despublicado? A v6 só oferece `status` global.
2. A ordenação dentro do mandato deve ser por ordem alfabética, por ordem de
   eleição ou pela ordem definida pelo operador (hoje é manual)?
3. O `role` deve ser uma lista fechada (Presidente, Vice-Presidente, Secretary,
   Correlator) ou livre? Hoje é livre.
4. É preciso um histórico de **troca de partido** ao longo do mandato? O modelo
   atual guarda o partido no cadastro do vereador, não no pivot.
5. Devem existir **suplentes** com modelagem própria? Hoje não há distinção entre titular e
   suplente.
6. A composição da Câmara deve ser exibida por **legislatura** (categoria
   "Legislaturas") ou por **período** (2021–2024)? A primeira opção é a existente.
7. O carrossel da home deve listar **todos** os vereadores ativos ou apenas a
   mesa diretora?
8. É necessário oferecer download da lista de vereadores em formato aberto (CSV/
   OData)? A v6 não oferece.

## 7. Checklist de correções para a v7

- [ ] Corrigir `has_access(['agenda.delete'])` → `vereadores.delete`.
- [ ] Usar `CityCouncilorRequest` (ou `UpdateCityCouncilorRequest`) no `update`.
- [ ] Corrigir a verificação de slug duplicado excluindo o registro em edição
      (`Rule::unique()->ignore()`).
- [ ] Trocar `hasOne` por `belongsTo` em `Mandate::categories()`,
      `CouncilmenMandates::councilmen()` e `CityCouncilor::archives()`.
- [ ] Criar as FKs e o unique composto em `councilmen_mandates` (após saneamento).
- [ ] Substituir `count($vereador->mandates)` por `exists()`/`withCount()`.
- [ ] Decidir e implementar `SoftDeletes` no pivot.
- [ ] Regenerar o slug do vereador na edição (ou documentar a decisão).
- [ ] Preencher `order` ao atribuir mandato.
- [ ] Corrigir o `form.submit()` comentado na tela de detalhe do vereador.
- [ ] Unificar a telemetria (`menuClicks`) por módulo real.
- [ ] Extrair `trata_nome()` para um helper único.
- [ ] Extrair o bloco de upload de foto (duplicado em `store` e `update`).
- [ ] Sanear `archives.path` para o caminho real e remover o *fallback* do carrossel.
- [ ] Adicionar testes automatizados (ver `07-TESTES.md`).
