# ENQUETES — Rotas e Controllers

> **Versão analisada:** `v6`
> CMS: `v6/routes/basepro.php` (prefixo `/cms`) · Portal: `v6/routes/web.php`
> Middleware global de rota: grupo `web` (aplicado em
> `v6/app/Providers/RouteServiceProvider.php:56-58` e `:86-88`), o que inclui
> `VerifyCsrfToken`.

---

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

Arquivo: `v6/routes/basepro.php`. O grupo externo é
`Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)` (linha 35).
O atributo `before` é herança do Laravel 4 e **não tem efeito** no Laravel 5;
o CSRF é garantido pelo middleware `web`.

### 1.1 Resource `enquete`

Declaração: `v6/routes/basepro.php:222`
`Route::resource('enquete', 'Prefecture\SurveyController')->middleware('check.logged');`

| Método | URI | Nome | Controller@action | Middleware |
|---|---|---|---|---|
| GET | `/cms/enquete` | `enquete.index` | `Prefecture\SurveyController@index` | `web`, `check.logged` |
| GET | `/cms/enquete/create` | `enquete.create` | `Prefecture\SurveyController@create` | `web`, `check.logged` |
| POST | `/cms/enquete` | `enquete.store` | `Prefecture\SurveyController@store` | `web`, `check.logged` |
| GET | `/cms/enquete/{enquete}` | `enquete.show` | `Prefecture\SurveyController@show` | `web`, `check.logged` |
| GET | `/cms/enquete/{enquete}/edit` | `enquete.edit` | `Prefecture\SurveyController@edit` | `web`, `check.logged` |
| PUT/PATCH | `/cms/enquete/{enquete}` | `enquete.update` | `Prefecture\SurveyController@update` | `web`, `check.logged` |
| DELETE | `/cms/enquete/{enquete}` | `enquete.destroy` | `Prefecture\SurveyController@destroy` | `web`, `check.logged` |

> O parâmetro de rota gerado pelo `Route::resource` é `{enquete}` (derivado do
> nome do recurso). Os métodos do controller o recebem posicionalmente como
> `$id`, então a grafia não afeta o funcionamento.

> **Atenção:** `SurveyController` **não implementa** `show()`. A rota
> `enquete.show` (gerada pelo `Route::resource`) leva a um método inexistente e
>|resulta em `BadMethodCallException` (HTTP 500). O controller implementa
> apenas `index`, `create`, `store`, `edit`, `update`, `destroy` e
> `deleteByLote`.

### 1.2 Rotas auxiliares do CMS

| Método | URI | Nome | Controller@action | Middleware | Observação |
|---|---|---|---|---|---|
| POST | `/cms/ajax/enquete/delete-por-lote` | `ajax.survey.deleteByLote` | `Prefecture\SurveyController@deleteByLote` | `web` (CSRF) | Exclusão em lote. **Sem `check.logged`** — ver `05-REGRAS-E-PORTABILIDADE.md` |
| POST | `/cms/ajax/ajax-delete-answer` | `ajax.delete.answer` | `Prefecture\SurveyController@ajaxDeleteAnswer` | `web` (CSRF) | **Método inexistente** → HTTP 500. Rota morta |
| GET | `/cms/enquete/{slug}/{id}/delete` | *(sem nome)* | `Prefecture\SurveyController@deleteAnswer` | `web` | **Método inexistente** → HTTP 500. Rota morta. Além disso usa `GET` para uma ação destrutiva |
| GET | `/cms/datatable/enquete` | `enquete` | `Prefecture\DatatableController@get_enquetes` | `web` | Alimenta o DataTable do CMS |

Referências de linha: `v6/routes/basepro.php:133` (ajax-delete-answer),
`:155` (delete-por-lote), `:222` (resource), `:230` (enquete/{slug}/{id}/delete),
`:417` (datatable).

O prefixo `ajax` das duas primeiras rotas vem do grupo
`Route::group(['prefix' => 'ajax'], ...)` aberto em
`v6/routes/basepro.php:114`.

### 1.3 Roteamento por permissão

`App\Http\Controllers\Prefecture\Controller` (base do CMS) registra
`check.route.permission` no seu construtor
(`v6/app/Http/Controllers/Prefecture/Controller.php:21-24`), que traduz o nome
da rota corrente em permissão Sentinel.

**Porém `Prefecture\SurveyController::__construct()` não chama
`parent::__construct()`** (`v6/app/Http/Controllers/Prefecture/SurveyController.php:24-28`).
Como o middleware de controller é registrado **no construtor** e lido da
instância criada pelo container
(`v6/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php:38-48`),
o `check.route.permission` **não é aplicado** às rotas de enquete.

Consequência: a proteção efetiva do CMS para as rotas de enquete é apenas:

| Route | Proteção efetiva |
|---|---|
| `enquete.index/create/store/show/edit/update/destroy` | `check.logged` (rota) + CSRF (`web`) |
| `ajax.survey.deleteByLote` | somente CSRF (`web`) |
| `ajax.delete.answer`, `enquete/{slug}/{id}/delete` | somente CSRF — e ambas caem em método inexistente |

Permissões existentes para o recurso `enquete` (de
`v6/database/seeds/RolesTableSeeder.php:26`):
`enquete.index`, `enquete.create`, `enquete.edit`, `enquete.delete`,
`enquete.inactive`, `enquete.inactivate`, `enquete.restore`, `enquete.show`.

A checagem é feita dentro da view da listagem, via `has_access()`:
`v6/resources/views/prefecture/surveys/index.blade.php:11` (botão "Cadastrar
Enquete") e `:16`/`:28` (botão de exclusão e checkbox mestre).

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

Arquivo: `v6/routes/web.php:31-35`, dentro do grupo
`Route::middleware(['check.site.manutention'])->group(...)` (linha 7).

| Método | URI | Nome | Controller@action | Middleware |
|---|---|---|---|---|
| GET | `/enquetes` | `enquetes` | `Portal\SurveyController@index` | `web`, `check.site.manutention` |
| GET | `/enquetes/{slug}` | `enquetes.show` | `Portal\SurveyController@show` | `web`, `check.site.manutention` |
| GET | `/enquetes/encerrada/{slug}` | `enquetes.closed.show` | `Portal\SurveyController@showClosed` | `web`, `check.site.manutention` |
| POST | `/send-survey-ajax` | `send.survey.ajax` | `Portal\SurveyController@sendSurvey` | `web` (CSRF), `check.site.manutention` |

Observações:

- **Não há autenticação** no portal: o voto é anônimo (só IP + user-agent são
  gravados).
- A rota `send.survey.ajax` é `POST` e está no grupo `web`, portanto exige
  **token CSRF** válido. O formulário do portal é aberto com
  `Form::open(...)`, que injeta o token; o `FormData` enviado pelo JS o carrega.
- A URI `send-survey-ajax` é **sem barra inicial**, o que a torna uma rota de
  raiz do site (`/send-survey-ajax`) e não `/send-survey-ajax/` — convenção
  inconsistente com o resto de `web.php`.
- **Ordem de rotas:** `Route::get('/enquetes/{slug}')` está declarada **antes**
  de `Route::get('/enquetes/encerrada/{slug}')`. Para o slug literal
  `encerrada`, a primeira rota casa e o controller `show()` é executado; o
  redirect para `enquetes/encerrada/{slug}` só ocorre por causa do
  `final_date` já vencido. Uma enquete cujo slug seja literalmente `encerrada`
  é, portanto, inalcançável pela rota de resultado.
- **API:** não existe rota em `v6/routes/api.php` para enquetes.

## 3. Breadcrumbs

Arquivo: `v6/routes/breadcrumbs.php`

| Nome | Definição | Uso |
|---|---|---|
| `survey` | `:289-292` — pai `home`, label "Enquetes", link `route('enquetes')` | `portal/layouts/surveys/index.blade.php:4` |
| `surveyAnswers` | `:294-297` — pai `survey`, label `str_limit($survey->question, 30) . "..."`, link `route('enquetes.show', [$survey->slug])` | `portal/layouts/surveys/show.blade.php:4` e `closed.blade.php:4` |

O breadcrumb da tela de resultado (`closed.blade.php`) aponta para a rota de
**votação** (`enquetes.show`), e não para `enquetes.closed.show` — o que gera
um link de retorno inconsistente.

## 4. Controllers

### 4.1 `Prefecture\SurveyController`

Arquivo: `v6/app/Http/Controllers/Prefecture/SurveyController.php`

| Método | Linhas | Comportamento |
|---|---|---|
| `__construct(CalendarEvent $survey, ISurveyRepository $repository)` | 24-28 | Injeta o repository. **Não** chama o construtor pai (perda do `check.route.permission`). O `$surveyLog` é um `CalendarEvent`, não um `Survey` — herança do módulo *Agenda de Eventos*. |
| `index()` | 30-45 | Lista `surveys` por `final_date desc`; carrega `SurveyResult::all()` (não usado pela view); registra auditoria; `menuClicks('enquete')`; retorna `prefecture.surveys.index`. |
| `create()` | 53-64 | View `prefecture.surveys.create` + `prefecture.surveys.form`; `$disabled = false`. |
| `store(SurveyRequest)` | 72-94 | `repository->upInsert($request)`; flash de sucesso e `activity()` **dentro do `if`**. |
| `edit($id)` | 101-115 | Carrega a enquete com `surveyAnswers`; `$disabled = count(SurveyResult::where('survey_id',...)) > 0`. |
| `update(SurveyRequest, $id)` | 124-145 | `repository->upInsert($request, $id)`; flash e `activity()` dentro do `if`. |
| `destroy($id)` | 152-172 | Auditoria + `repository->destroy($id)` (remove também as opções). Responde JSON `{"success": "Enquete deletada com sucesso."}` (200) ou `{"erro": ...}` (500). |
| `deleteByLote(Request)` | 174-192 | `repository->destroyAll($request->toDelete)`; **não** remove as opções das enquetes excluídas. |

> **Bug estrutural:** `SurveyRepository::upInsert()` **não retorna nada**
> (`v6/app/Repositories/Eloquent/Prefecture/Survey/SurveyRepository.php:43-52`).
> Portanto `if ($survey = $this->repository->upInsert($request))` é sempre
> falso: o operator **nunca** vê "Cadastrado com sucesso" / "Editado com
> sucesso" e **nenhum registro de auditoria** é gravado em create/update.
> O redirecionamento para `enquete.index` acontece de qualquer forma.

### 4.2 `Portal\SurveyController`

Arquivo: `v6/app/Http/Controllers/Portal/SurveyController.php`

| Método | Linhas | Comportamento |
|---|---|---|
| `index()` | 15-24 | `Survey::where('status','1')->where('publication_date','<=',$current_date)->orderBy('final_date','desc')->get()`; sem paginação; view `portal.layouts.surveys.index`. |
| `categoryIndex($slug)` | 26-39 | **Método de Agenda de Eventos copiado** para esta classe; não tem rota própria — código morto. |
| `show($slug)` | 43-61 | Busca por slug; redirect para `enquetes/encerrada/{slug}` se vencida; calcula `$votado` comparando o IP com o do **primeiro** resultado da enquete. |
| `showClosed($slug)` | 63-89 | Redirect para `enquetes/{slug}` se ainda vigente; apura percentuais por opção (1+N queries). |
| `sendSurvey(Request)` | 91-116 | Valida presença de `survey_answer_id`; grava IP/browser; `SurveyResult::create($request->all())`. |
| `calcularPorcentagem($parcial, $total)` | 119-124 | `(parcial * 100) / total`, com guarda `0/0 → 0`. |

Observação: `Portal\SurveyController` estende `App\Http\Controllers\Admin\Controller`
(linha 12) — a base do CMS — apenas para usar `getTemplateActived()`
(`v6/app/Http/Controllers/Admin/Controller.php:39-49`).

## 5. Form Requests e validação

| Request | Caminho | Regras | Mensagens |
|---|---|---|---|
| `SurveyRequest` | `v6/app/Http/Requests/SurveyRequest.php` | `question` required; `final_date` required; `publication_date` required; `answers` required | "Por favor, informe uma pergunta." / "Por favor, informe a data de publicação." / "Por favor, informe a data de expiração." / "Por favor, cadastre respostas." |

Pontos frágeis da validação:

- `authorize()` retorna `true` — não há checagem de permissão dentro do
  FormRequest.
- Não há validação de **formato** das datas; a conversão
  `toUSDataHourFormat()` (`v6/app/Http/Traits/AppTrait.php:285-296`) explode a
  string em `/` e em ` ` — uma data malformada gera notices/erros em vez de
  mensagem de validação.
- Não há validação de que `final_date > publication_date`.
- Não há validação de que `answers` é um array não vazio (apenas `required`).
- O portal **não usa FormRequest**: `sendSurvey` valida apenas a presença de
  `survey_answer_id`.

## 6. API Resources / presenters

Não há `API Resource`, `JsonResource` nem presenter específico do módulo.
As únicas saídas JSON são construídas à mão:

| Endpoint | Formato da resposta |
|---|---|
| `POST /send-survey-ajax` | Array posicional: `[400, "Por favor, marque uma das opções"]`, `[201, "Voto salvo com sucesso! Obrigado por sua participação!"]` ou `[400, "Ocorreu um erro, por favor tente novamente."]` |
| `DELETE /cms/enquete/{enquete}` | `{"success": "Enquete deletada com sucesso."}` (200) ou `{"erro": "..."}` (500) |
| `POST /cms/ajax/enquete/delete-por-lote` | `{"success": "Enquetes deletadas com sucesso."}` (200) ou `{"erro": "..."}` (500) |
| `GET /cms/datatable/enquete` | JSON do Yajra DataTables (`data`, `recordsTotal`, `recordsFiltered`, ...) |

O consumo no JS depende do formato **array posicional** (`data[0] == 400`),
o que é frágil e não tipado.

## 7. Permissões exigidas (papéis/guard)

| Recurso | Permissão | Onde é exigida |
|---|---|---|
| `enquete` | `enquete.index` | listagem (consequência do `check.route.permission`, hoje inoperante) |
| `enquete` | `enquete.create` | botão "Cadastrar Enquete" (`prefecture/surveys/index.blade.php:9`) |
| `enquete` | `enquete.edit` | botão de edição no DataTable (via `struct_views`) |
| `enquete` | `enquete.delete` | botão de exclusão e exclusão em lote |
| `enquete` | `enquete.restore` / `enquete.inactivate` | colunas `active`/`inactivate` geradas por `struct_views` (não usadas por este módulo) |

`has_access()` (`v6/app/Library/Helper/appHelpers.php:22-42`) retorna `true`
para o papel `super-admin` e, para usuários logados, delega a
`Sentinel::hasAccess()`.

O portal **não exige nenhum papel**: qualquer visitante anônimo pode votar.
