# Servidor — Gaps, Riscos e Dívidas

> **Módulo:** `servidor` (tabela `modulos`, id 35)
> **Versão analisada:** `v6` (idêntico à `v5`)

---

## 1. Bugs e erros conhecidos

### B1 — SQL injection na busca pública de diárias (**severidade crítica**)

`v6/app/Http/Controllers/Portal/BuscaDiariasController.php:32-41` concatena a entrada do
usuário diretamente em um `whereRaw`:

```php
if (isset($request->req_name)) {
    $ids = Servidor::whereRaw("translate(lower(name), 'àáâãäéèëêíìïîóòõöôúùüûçÇ', 'aaaaaeeeeiiiiooooouuuucc') ilike '%" . $request->req_name . "%'")->pluck('id');
    $diarias->whereIn('id_servidor', $ids);
}

if (isset($request->cpf)) {
    $ids = Servidor::whereRaw("translate(lower(cpf), ' àáâãäéèëêíìïîóòõöôúùüûçÇ', 'aaaaaeeeeiiiiooooouuuucc') ilike '%" . $request->cpf . "%'")->pluck('id');
    $diarias->whereIn('id_servidor', $ids);
}
```

A rota é pública (`v6/routes/web.php:211`, `Route::any('/requerimento-de-diarias', 'Portal\BuscaDiariasController@index')`,
apenas com `check.site.manutention`). A validação (`App\Http\Requests\DiariasRequest`)
cobre **apenas** `cpf` com `nullable|min:14` — `req_name` não tem validação alguma e
`cpf` não tem restrição de caracteres. Um visitante anônimo pode injetar SQL sobre a
tabela `servidor` (e, por subquery, sobre qualquer outra).

**Correção obrigatória na v7:** parametrizar (`whereRaw($sql, [$termo])`), ou reescrever a
comparação com `whereRaw` apenas para a função `translate` e o termo como binding.

### B2 — Rota `servidor.show` aponta para um método inexistente (**severidade média**)

`Route::resource("servidor", "Prefecture\ServidorController")`
(`v6/routes/basepro.php:204`) gera sete rotas, incluindo
`GET /cms/servidor/{servidor}` → `servidor.show`. O controller **não implementa**
`show()`. Uma requisição a `GET /cms/servidor/{id}` resulta em
`BadMethodCallException` (HTTP 500).

**Correção:** `->except(['show'])` no resource ou implementar a ação.

### B3 — Rota `servidor/destroyAll` registrada duas vezes (**severidade baixa**)

`v6/routes/basepro.php:247` e `v6/routes/basepro.php:253` contêm a mesma linha:

```php
Route::post("servidor/destroyAll", "Prefecture\ServidorController@destroyAll")->name('servidor.destroyAll');
```

A segunda sobrescreve a primeira no registro de nomes. O comportamento é inofensivo,
mas é duplicação sem efeito (e o mesmo padrão se repete em outros módulos).

### B4 — A exclusão em lote sempre reporta sucesso (**severidade média**)

`App\Repositories\Repository::destroyAll()`
(`v6/app/Repositories/Repository.php:96-108`) devolve **sempre** um `JsonResponse`
(`response()->json(true)` ou `response()->json(false)`). Em PHP, um objeto é sempre
truthy, logo em `ServidorController@destroyAll`:

```php
if ($this->repository->destroyAll($request->toDelete))
    return response()->json(['success' => 'Servidores deletados com sucesso.'], 200);
```

a condição é **sempre verdadeira**. Se nada foi apagado (ids inválidos, erro de FK), o
operador ainda vê "Servidores deletados com sucesso.".

### B5 — `dd()` no repository base expõe exceções ao operador (**severidade média**)

`v6/app/Repositories/Repository.php:66-72`:

```php
public function upInsert($request, int $id = null)
{
    try {
        $data = DB::transaction(function () use ($request, $id) {
            return $this->structureUpInsert($this->dataTrait($request));
        });
        return $this->getModel()->updateOrCreate(['id' => $id], $data);
    } catch (\Throwable $th) {
        dd($th);
    }
    return false;
}
```

`dd()` encerra a requisição com um dump do stack trace. O `catch (Exception $exception)`
do `ServidorController@store`/`@update` **nunca** é alcançado, e o `flash($exception->getMessage())->error()`
previsto no controller jamais aparece. O operador vê uma página de debug — que em
produção expõe caminhos de arquivo, linhas de código e sometimes credenciais de
conexão.

### B6 — View `servidores` × tabela `servidor` (**severidade média**)

- Tabela do model: `servidor` (singular), `protected $table` explícito.
- Listagem do CMS: `DB::table('servidores')` (a **view**).
- Migration: `create_servidors_table.php` que cria `servidor`.

A view expõe apenas `value`, `name`, `cpf`, `id`: cargo, matrícula, identidade,
secretaria e dados bancários **não** aparecem na listagem, embora existam na tabela.
Consultas escritas à mão correm o risco de trocar uma pela outra.

### B7 — Coluna "ID" da listagem não é o `servidor.id` (**severidade baixa**)

O `row_number() OVER (ORDER BY created_at ASC)` da view vira a coluna `value`, que o
`runDatatables` insere como primeira coluna. O cabeçalho diz "ID", mas o valor é uma
numeração sequencial por data de criação — e a ordenação exibida é a do DataTables.

### B8 — Regra órfã no Form Request (**severidade baixa**)

`v6/app/Http/Requests/ServidorRequest.php` termina o array de regras com
`new Validate_cpf` solto. O PHP cria a chave numérica `4` no array; o validador passa
a considerar um atributo chamado `"4"`, que não existe no request, e a regra não é
implícita — logo, nunca executa. Resíduo de código sem efeito funcional.

### B9 — `secretaria` não é validada (**severidade média**)

Não há regra para `secretaria`. Um id inexistente violaria a FK `contents(id)` e cairia
no `dd()` do repository base (B5), exibindo um dump em vez de uma mensagem de erro.
O mesmo ocorre com qualquer campo fora da allowlist do model.

### B10 — Permissões effectively abertas em `store`, `update` e `destroy` (**severidade média**)

Em `CheckRoutePermission::hasAccess()`, o array `$resourceRoutes`
(`['edit' => 'update', 'create' => 'store', 'delete' => 'destroy']`) é testado como
lista de **valores** (`in_array($value, $resourceRoutes)`). Como o nome da rota é
dividido por `.`, qualquer rota cujo segundo segmento seja `store`, `update` ou
`destroy` é liberada para **qualquer usuário autenticado**.

Para este módulo: `servidor.store`, `servidor.update` e `servidor.destroy` ficam
abertas; `servidor.index`, `servidor.create` e `servidor.edit` exigem permissão.

### B11 — Permissão `servidor.destroyAll` não é gerável (**severidade baixa**)

A tela de Perfis monta as chaves como `{resources.slug}.{permissions.slug}`, e os
slugs de `permissions` são genéricos (`index`, `create`, `edit`, `delete`, `inactive`,
`inactivate`, `restore`, `show`, `publish`). Não existe `servidor.destroyAll` — logo, a
exclusão em lote fica restrita ao `super-admin` na prática, embora o botão apareça
para quem tem `servidor.delete`.

## 2. Funcionalidades incompletas ou mortas

| Item | Situação |
|---|---|
| `ServidorController@show` | rota gerada, método inexistente (B2) |
| `Servidor::secretarias()` | relação `hasOne` declarada e **nunca utilizada**; o formulário usa a coleção `$secretarias` do controller |
| `IServidorRepository` | interface **vazia**: não declara nenhum método; o controller depende de métodos herdados de `Repository` |
| `ServidorRequest` — regra `new Validate_cpf` órfã | código inerte (B8) |
| Exportação própria do módulo | inexistente; só há os botões Excel/PDF padrão do DataTables, que exportam `value`, `name` e `cpf` |
| Auditoria de alterações | nenhuma: o módulo não registra log de atividade, ao contrário de outros fluxos do CMS |
| Restauração de servidores | não há `restore`; o soft delete é definitivo na interface |
| Tela de detalhe | inexistente no CMS: o operador precisa abrir a edição para ver os dados |
| Mapeamento de secretaria → `contents` | não há qualquer entidade "secretaria"; o vínculo depende de a categoria de conteúdos `secretarias` existir e estar correta |

## 3. Itens da v5 e da v6

Nada foi alterado entre as duas versões (diff byte a byte de todos os arquivos do
módulo — ver `05-REGRAS-E-PORTABILIDADE.md`, seção 5).

| Item | v5 | v6 |
|---|---|---|
| Controller, model, request, repository | presentes | **idênticos** |
| Views (`index`, `create`, `form`) | presentes | **idênticas** |
| Migrations da tabela e da view | presentes | **idênticas** |
| `Route::resource("servidor", "Prefecture\ServidorController")` | presente (`v5/routes/basepro.php:185`) | presente (`v6/routes/basepro.php:204`) |
| Rota `servidor/destroyAll` **duplicada** | presente (duplicada) | presente (duplicada) |
| Rota `datatable/servidor` | presente (`v5/routes/basepro.php:450`) | presente (`v6/routes/basepro.php:439`) |
| Registro em `modulos` | **inexistente** (a tabela não existe na v5) | `modulos.id = 35` |
| Registro em `resources` | **inexistente** | `resources.id = 47` — habilita as permissões `servidor.*` |
| Painel `/cms/modulos` | inexistente | presente |

Nenhum item foi **removido** da v5 para a v6 neste módulo.

## 4. Riscos de dados (migração de schema)

| Risco | Detalhe |
|---|---|
| Tabela `servidor` × view `servidores` | nomes quase idênticos; qualquer script que use `servidores` como tabela quebra |
| Coluna `secretaria` → `contents(id)` | migrar para uma tabela `secretarias` exige reescrever o FK e as views de requerimentos de diárias (`join servidor s on s.id = rd.id_servidor`) |
| FK de `requerimento_diarias.id_servidor` | impede a exclusão física de servidores com requerimentos; a política de "arquivar" precisa ser definida |
| CPF sem unicidade | a migração para `UNIQUE` exige resolver duplicidades existentes |
| CPF com máscara ou sem máscara | misturar os dois formatos quebra buscas por CPF e a validação por dígito verificador |
| Dados bancários em claro | cifrar exige ler o valor atual: é preciso um comando de migração de dados, não apenas DDL |
| Soft delete sem restauração | registros apagados ocupam a base e não podem ser recuperados pela interface |
| `contents` com soft delete | excluir (soft) uma secretaria deja o servidor apontando para um registro que some do select; ao salvar o servidor, o vínculo é perdido (`secretaria` volta a `NULL`) |
| Ausência de auditoria | não há como reconstituir quem alterou um dado bancário |

## 5. Riscos de segurança e de privacidade

| Risco | Detalhe |
|---|---|
| **SQL injection pública** | B1 — rota anônima permite injeção de SQL na tabela `servidor` |
| Dados bancários em claro | `banco`, `conta` e `agencia` são gravados e exibidos sem cifragem nem permissão específica |
| CPF e identidade em claro | expostos no formulário e no select de servidores do módulo de diárias (`components/inputs/select.blade.php` mascara o CPF, mas o dado bruto está na resposta) |
| Permissões abertas em escrita | B10 — qualquer usuário autenticado pode criar, alterar e excluir servidores se conhecer a URL |
| Autorização inexistente no Form Request | `ServidorRequest::authorize()` retorna `true` sempre; a proteção depende exclusivamente do middleware |
| Ausência de log de auditoria | nenhuma trilha para o tratamento de dados pessoais de servidores (LGPD) |
| Exposição em log de erro | B5 — o `dd()` imprime o stack trace na tela |
| Volume de dados pessoais exposto | a listagem devolve CPF de todos os servidores para qualquer operador autorizado |

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

1. Os **dados bancários** continuam sendo necessários? O "Manual de Servidor" diz que só
   são obrigatórios quando o servidor usa Requerimento de Diárias — vale a pena manter
   o campo, ou vinculá-lo ao requerimento?
2. "Secretaria" deve ser uma entidade própria, ou continua sendo um conteúdo da categoria
   `secretarias`?
3. O CPF deve ser único? Em caso afirmativo, como tratar os duplicados já cadastrados?
4. A matrícula é o identificador funcional real? Deve ser única e indexada?
5. Servidores afastados, cessados ou exonerados devem ser **arquivados** (com data) em vez
   de excluídos? Hoje não há registro de vigência.
6. Por quanto tempo os dados devem ser retidos (LGPD)?
7. A listagem deve mostrar cargo e secretaria (hoje mostra apenas nome e CPF)?
8. É aceitável que a exclusão em lote apague servidores com requerimentos de diárias
   vinculados?

## 7. Mapa v5 → v6

| Item v5 | Item v6 | Tipo de mudança |
|---|---|---|
| `v5/app/Http/Controllers/Prefecture/ServidorController.php` | `v6/app/Http/Controllers/Prefecture/ServidorController.php` | **idêntico** |
| `v5/app/Http/Model/Prefecture/Servidor.php` | `v6/app/Http/Model/Prefecture/Servidor.php` | **idêntico** |
| `v5/app/Http/Requests/ServidorRequest.php` | `v6/app/Http/Requests/ServidorRequest.php` | **idêntico** |
| `v5/app/Repositories/Eloquent/Prefecture/Servidor/ServidorRepository.php` | `v6/app/Repositories/Eloquent/Prefecture/Servidor/ServidorRepository.php` | **idêntico** |
| `v5/app/Repositories/Contracts/IServidorRepository.php` | `v6/app/Repositories/Contracts/IServidorRepository.php` | **idêntico** (vazio nos dois) |
| `v5/resources/views/prefecture/contents/servidor/index.blade.php` | `v6/resources/views/prefecture/contents/servidor/index.blade.php` | **idêntico** |
| `v5/resources/views/prefecture/contents/servidor/create.blade.php` | `v6/resources/views/prefecture/contents/servidor/create.blade.php` | **idêntico** |
| `v5/resources/views/prefecture/contents/servidor/form.blade.php` | `v6/resources/views/prefecture/contents/servidor/form.blade.php` | **idêntico** |
| `v5/public/assets/js/custom/contents/servidor/datatable.js` | `v6/public/assets/js/custom/contents/servidor/datatable.js` | **idêntico** |
| `v5/database/migrations/2023_01_12_093921_create_servidors_table.php` | `v6/database/migrations/2023_01_12_093921_create_servidors_table.php` | **idêntico** |
| `v5/database/migrations/2023_03_27_154732_create_view_to_servidores.php` | `v6/database/migrations/2023_03_27_154732_create_view_to_servidores.php` | **idêntico** |
| `v5/routes/basepro.php:185` (`Route::resource`) | `v6/routes/basepro.php:204` | mesma rota, linha diferente |
| `v5/routes/basepro.php:219` e `:225` (`servidor/destroyAll`) | `v6/routes/basepro.php:247` e `:253` | **mesma duplicação** |
| `v5/routes/basepro.php:450` (datatable) | `v6/routes/basepro.php:439` | mesma rota, linha diferente |
| — | `v6/database/migrations/2024_11_01_150206_create_modulos_table.php` | **novo**: `modulos.id = 35` |
| — | `v6/app/Http/Controllers/Admin/ModulosController.php` | **novo**: habilitação de módulos |
| — | `resources.id = 47` (`slug = 'servidor'`) | **novo**: origina as permissões `servidor.*` |

**Resumo do mapa:** o módulo `servidor` não sofreu nenhuma alteração funcional entre
`v5` e `v6`. As únicas diferenças são estruturais e globais (registro de módulos,
painel de habilitação e o recurso de permissões), que afetam todos os módulos do CMS.
