# Fila de Espera SUS — Rotas e Controllers

> Versão analisada: `v5`. Rotas em `v5/routes/basepro.php` (CMS) e `v5/routes/web.php` (Portal).

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

Todas declaradas em `v5/routes/basepro.php:399-406`, dentro do grupo
`Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)`
(`v5/routes/basepro.php:35`).

| Método | URI | Nome da rota | Controller@action | Middleware | Permissão exigida |
|---|---|---|---|---|---|
| GET | `/cms/fila-espera-sus` | `fila-espera-sus.index` | `Prefecture\FilaEsperaSusController@index` | `check.logged` (rota) + `check.route.permission` (controller) | `fila-espera-sus.index` |
| GET | `/cms/fila-espera-sus/cadastrar` | `fila-espera-sus.create` | `Prefecture\FilaEsperaSusController@create` | `check.logged` + `check.route.permission` | `fila-espera-sus.create` |
| GET | `/cms/fila-espera-sus/editar/{id}` | `fila-espera-sus.edit` | `Prefecture\FilaEsperaSusController@edit` | `check.logged` + `check.route.permission` | `fila-espera-sus.edit` |
| PUT | `/cms/fila-espera-sus/atualizar/{id}` | `fila-espera-sus.update` | `Prefecture\FilaEsperaSusController@update` | `check.route.permission` | liberada pelo middleware (ver §4) |
| POST | `/cms/fila-espera-sus` | `fila-espera-sus.store` | `Prefecture\FilaEsperaSusController@store` | `check.route.permission` | liberada pelo middleware (ver §4) |
| DELETE | `/cms/fila-espera-sus/excluir/{id}` | `fila-espera-sus.destroy` | `Prefecture\FilaEsperaSusController@destroy` | `check.route.permission` | liberada pelo middleware (ver §4) |
| POST | `/cms/fila-espera-sus/destroyAll` | `fila-espera-sus.destroyAll` | `Prefecture\FilaEsperaSusController@destroyAll` | `check.route.permission` | `fila-espera-sus.destroyAll` (inexistente na tabela `permissions`) |
| DELETE | `/cms/fila-espera-sus/{id}/arquivo/{index}` | `fila-espera-sus.destroy-arquivo` | `Prefecture\FilaEsperaSusController@destroyArquivo` | `check.route.permission` | `fila-espera-sus.destroy-arquivo` (inexistente na tabela `permissions`) |

### 1.1 Rota de dados do DataTable

| Método | URI | Nome | Controller@action | Declaração |
|---|---|---|---|---|
| GET | `/cms/datatable/fila-espera-sus` | `datatable.fila-espera-sus` | `Prefecture\DatatableController@get_fila_espera_sus` | `v5/routes/basepro.php:452` |

O grupo `datatable` (`v5/routes/basepro.php:409`) fica dentro do prefixo
`cms` e herda `before => csrf` e o middleware global do controller
`Prefecture\DatatableController`. O middleware `check.route.permission`
trata qualquer rota cujo nome contenha o segmento `datatable` como rota
aberta (sem checagem de permissão específica).

## 2. Rotas do Portal (público)

Declaradas em `v5/routes/web.php:128-129`, dentro do grupo
`Route::middleware(['check.site.manutention'])->group(...)`
(`v5/routes/web.php:5`).

| Método | URI | Nome da rota | Controller@action | Middleware |
|---|---|---|---|---|
| GET | `/fila-espera-sus` | `portal.fila-espera-sus.index` | `Portal\FilaEsperaSusController@index` | `check.site.manutention` |
| GET | `/fila-espera-sus/{slug}` | `portal.fila-espera-sus.show` | `Portal\FilaEsperaSusController@indexShow` | `check.site.manutention` |

- A mesma rota do `index` serve a listagem e a exportação: o parâmetro
  `?type=xls|pdf|csv|txt` troca o retorno.
- A rota do detalhe **não** tem restrição de parâmetro: aceita qualquer
  string e o controller também aceita o `id` numérico.
- Não há rota de API (Swagger) para este módulo na v5.

Trilha de navegação: `v5/routes/breadcrumbs.php:225-234` registra
`fila-espera-sus` (filho de `home`) e `fila-espera-sus-show` (filho do
primeiro), consumidos pelas views do portal.

## 3. Permissões

O bloco de permissões da v5 é composto por:

- `resources` (v5): colunas `id, slug, name, description, status_create,
  created_at, updated_at, deleted_at`. O resource do módulo é
  `id = 17`, `slug = 'fila-espera-sus'`.
- `permissions` (v5): 8 verbos (`index`, `create`, `edit`, `delete`,
  `inactive`, `inactivate`, `restore`, `show`).
- A permissão final é a concatenação `resource_slug.permissao_slug`
  montada em `v5/resources/views/admin/role/form.blade.php:55`
  (ex.: `fila-espera-sus.index`), e gravada no perfil via
  `Sentinel::getRoleRepository()` em
  `v5/app/Http/Controllers/Admin/RoleController.php`.
- `has_access()` (`v5/app/Library/Helper/appHelpers.php:22-45`) resolve o
  slug: `super-admin` passa sempre; demais perfis usam
  `Sentinel::check()->hasAnyAccess($slugs)`.

Uso de `has_access` no módulo:

| Contexto | Verbo | Arquivo |
|---|---|---|
| Botão "Cadastrar Registro" | `fila-espera-sus.create` | `v5/resources/views/prefecture/fila-espera-sus/index.blade.php` |
| Botão "Excluir Registro(s)" (lote) e checkbox master | `fila-espera-sus.delete` | `v5/resources/views/prefecture/fila-espera-sus/index.blade.php` |
| Botão editar/excluir por linha | `fila-espera-sus.edit` / `fila-espera-sus.delete` | `v5/app/Http/Controllers/Prefecture/DatatableController.php:508-524` (`struct_views`) |

**Observação:** o resource `fila-espera-sus` foi criado pela migration do
módulo, mas os papéis (roles) do banco não têm as permissões
correspondentes atribuídas — no banco v5 a tabela `permissions` não possui
nenhum slug com "fila" e nenhum papel foi configurado para o resource 17.
Na prática, apenas `super-admin` acessa o módulo.

## 4. Como o middleware de permissão decide

`v5/app/Http/Middleware/CheckRoutePermission.php`:

1. Se não há sessão, redireciona para `login`.
2. Se o usuário é `super-admin`, libera.
3. Caso contrário, usa o **nome da rota** corrente:
   - Segmentos `update`, `store`, `destroy` estão em `$resourceRoutes` e são
     tratados como **rotas abertas** — ou seja, `fila-espera-sus.store`,
     `fila-espera-sus.update` e `fila-espera-sus.destroy` passam apenas pelo
     requisito de login. Na prática, quem tem `fila-espera-sus.create` (e
     também `fila-espera-sus.index`) consegue gravar, editar e excluir.
   - `fila-espera-sus.destroyAll` e `fila-espera-sus.destroy-arquivo` **não**
     estão nas listas abertas e exigem permissões que não existem no
     catálogo (`permissions` só tem os 8 verbos CRUD/inativos) — ficam
     restritos a `super-admin`.

## 5. Form Request — `FilaEsperaSusRequest`

Arquivo: `v5/app/Http/Requests/FilaEsperaSusRequest.php`.
`authorize()` retorna `true` (a autorização real é feita pelo middleware).

| Campo | Regra | Efeito observável |
|---|---|---|
| `sus_card` | `required|numeric|digits_between:1,15` | Cartão SUS obrigatório, só dígitos, de 1 a 15 caracteres. |
| `update_date` | `required|date` | Data de atualização obrigatória. |
| `register_date` | `required|date` | Data de registro obrigatória. |
| `patients_count` | `required|integer|min:0` | Número de pacientes, inteiro, não negativo. |
| `specialty` | `required|string|max:255` | Especialidade obrigatória, até 255 caracteres. |
| `procedure` | `required|string|max:255` | Procedimento obrigatório, até 255 caracteres. |
| `avg_wait_time` | `required|integer|min:0` | Tempo médio de espera em dias, inteiro, não negativo. |
| `priority_criteria` | `required|string|max:255` | Critério de priorização obrigatório. |
| `contact` | `nullable|string|max:255` | Contato opcional (o formulário limita a 15 caracteres). |
| `queue_status` | `required|string|max:255` | Status da fila obrigatório. |
| `arquivo` | `nullable|array` | Lista de anexos. |
| `arquivo.*` | `nullable|mimes:pdf` | Cada anexo precisa ser PDF (checagem por conteúdo). |

Nomes amigáveis (`attributes()`): "Cartão SUS", "Data de Atualização",
"Data de Registro", "Número de Pacientes", "Especialidade", "Procedimento",
"Tempo Médio de Espera", "Critério de Priorização", "Contato",
"Status da Fila", "Arquivo".

**Não há validação de unicidade** para `sus_card` (o mesmo cartão pode ter
vários registros, um por procedimento).

## 6. Filtros do portal — `FilterFilaEsperaSus`

Arquivo: `v5/app/Http/Filter/FilterFilaEsperaSus.php`. É instanciado pelo
container (o construtor recebe o model).

| Parâmetro da URL | Efeito na query |
|---|---|
| (fixo) | `where('is_show', true)` — só registros publicados. |
| `specialty` | `where('specialty', $request->specialty)` — igualdade. |
| `priority_criteria` | `where('priority_criteria', $request->priority_criteria)` — igualdade. |
| `busca` | `where(sus_card ilike %busca% OR specialty ilike %busca% OR procedure ilike %busca%)`. |

O parâmetro `busca` é concatenado com `%` e repassado como argumento do
`ilike` (query builder parametrizado — **não há concatenação SQL crua**).
Comparação case-insensitive garantida pelo `ilike` do PostgreSQL.

Observação: o filtro é aplicado a partir de `$request->busca` sem
`trim` nem sanitização de tamanho; o valor é ecoado de volta no campo de
busca da view (escapado pelo `{{ }}` do Blade).

## 7. Controllers — resumo de ações

### `Prefecture\FilaEsperaSusController`

| Ação | Comportamento |
|---|---|
| `index` | Registra atividade ("Listou Fila de Espera SUS"), incrementa `menuClicks('fila-espera-sus')` e devolve a view de listagem. |
| `create` | Devolve o formulário vazio com `new FilaEsperaSus()`. |
| `store` | Valida, grava anexos em `public_path('storage/fila-espera-sus')`, cria o registro com `is_show = true`, calcula o slug e redireciona com `flash()->success()`. Em exceção: `flash()->error()` + `back()->withInput()`. |
| `edit` | Carrega por `id`; se não achar, `flash('Registro não encontrado.')->error()` e volta à listagem. |
| `update` | Mesma lógica de `store`, **acrescentando** os anexos novos aos já existentes. |
| `destroy` | `delete()` (soft) e devolve JSON `{"success": "..."}` ou `{"erro": "..."}`. |
| `destroyAll` | `FilaEsperaSus::whereIn('id', $request->ids)->delete()` e devolve `true`/`false` em JSON. |
| `destroyArquivo` | Remove um item do array JSON de `file_path` pelo índice e devolve JSON. |

### `Portal\FilaEsperaSusController`

| Ação | Comportamento |
|---|---|
| `index` | Filtra, ordena (`update_date` desc, `specialty` asc), exporta se houver `?type=`, senão pagina 15 e monta as listas de `specialty`/`priority_criteria` para os selects. |
| `indexShow` | Localiza o registro por `slug` **ou** por `id` numérico, aplica `is_show = true` e renderiza o detalhe (404 se não achar). |
| `exportTxt` (privada) | Monta o TXTseparado por `\|` com cabeçalho fixo. |

## 8. API Resources / presenters

**Não existem** API Resources para o módulo na v5. A saída pública é HTML
Blade; a saída do CMS é JSON do yajra DataTables (sem resource dedicado) e
as exportações são XLSX/CSV/PDF/TXT.

## 9. Dependência de `str_slug`

O slug é gerado com o helper `str_slug()`, removido do Laravel a partir da
5.8 e inexistente no PHP 8 nativo. Trecho real do `store`:

```php
$item = FilaEsperaSus::create($data);
$item->slug = str_slug($item->specialty . '-' . $item->procedure . '-' . $item->id);
$item->save();
```

No `update`, o mesmo cálculo é feito **antes** do `update($data)` e com os
valores já persistidos:

```php
$data['slug'] = str_slug($item->specialty . '-' . $item->procedure . '-' . $item->id);
$item->update($data);
```

Consequência: alterar especialidade ou procedimento **não** altera o slug
nem a URL pública do registro.
