# ENQUETES — Arquitetura

> **Versão analisada:** `v6` (idêntica à `v5`)
> Módulo com duas faces: **CMS** (`/cms/enquete`) e **Portal** (`/enquetes`).

---

## 1. Visão geral das camadas

O módulo usa o padrão *Controller → Repository → Model* apenas no CMS. No
Portal o acesso ao banco é **direto** pelo model, sem repository e sem
FormRequest.

```
CMS (admin logado)                          PORTAL (público)
─────────────────────────────────────        ─────────────────────────────────────
GET/POST /cms/enquete                       GET  /enquetes
        │                                            │
        ▼                                            ▼
Prefecture\SurveyController                 Portal\SurveyController
 (check.logged na rota)                      (sem autenticação)
        │                                            │
        ├─► SurveyRequest (FormRequest)              ├─► Survey::where(status,publication_date)
        │    question/final_date/                    │    Survey::with('surveyAnswers')
        │    publication_date/answers                │    SurveyResult::where(survey_id)->first()
        │                                            │
        ▼                                            ▼
ISurveyRepository                           SurveyResult::create($request->all())
   └─► SurveyRepository                              │
        ├─► ISurveyAnswerRepository                  │
        │    └─► SurveyAnswerRepository              │
        └─► App\Prefecture\Survey                    │
             App\Prefecture\SurveyAnswer             │
                    │                                │
                    ▼                                ▼
              PostgreSQL: surveys / survey_answers / survey_results
                    │
                    └──► view SQL "enquetes"  ──► DatatableController@get_enquetes (CMS)
```

## 2. Classes envolvidas

### 2.1 CMS

| Classe | Caminho | Papel |
|---|---|---|
| `Prefecture\SurveyController` | `v6/app/Http/Controllers/Prefecture/SurveyController.php` | CRUD de enquetes, auditoria (`activity()`), contador de menu (`menuClicks('enquete')`), exclusão em lote. |
| `App\Http\Requests\SurveyRequest` | `v6/app/Http/Requests/SurveyRequest.php` | Valida `question`, `final_date`, `publication_date`, `answers` (todos `required`). `authorize()` retorna `true` (sem autorização própria). |
| `App\Repositories\Contracts\ISurveyRepository` | `v6/app/Repositories/Contracts/ISurveyRepository.php` | Contrato do repositório de enquetes. |
| `App\Repositories\Eloquent\Prefecture\Survey\SurveyRepository` | `v6/app/Repositories/Eloquent/Prefecture/Survey/SurveyRepository.php` | Gera `slug`, converte datas, cria/substitui as opções de resposta. |
| `App\Repositories\Contracts\ISurveyAnswerRepository` | `v6/app/Repositories/Contracts/ISurveyAnswerRepository.php` | Contrato do repositório de opções de resposta. |
| `App\Repositories\Eloquent\Prefecture\SurveyAnswer\SurveyAnswerRepository` | `v6/app/Repositories/Eloquent/Prefecture/SurveyAnswer/SurveyAnswerRepository.php` | Filtra os campos `answer` e `survey_id` e grava as opções. |
| `App\Repositories\Repository` | `v6/app/Repositories/Repository.php` | Base genérica: `upInsert` (`updateOrCreate`), `destroy` (soft delete), `destroyAll`. |
| `App\Http\Model\Prefecture\Survey` | `v6/app/Http/Model/Prefecture/Survey.php` | Model da enquete (`surveys`), com `SoftDeletes`. |
| `App\Http\Model\Prefecture\SurveyAnswer` | `v6/app/Http/Model/Prefecture/SurveyAnswer.php` | Model das opções (`survey_answers`), com `SoftDeletes`. |
| `App\Http\Model\Prefecture\SurveyResult` | `v6/app/Http/Model/Prefecture/SurveyResult.php` | Model dos votos (`survey_results`), **sem** `SoftDeletes`. |
| `App\Http\Controllers\Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/DatatableController.php` | `get_enquetes()` (linha 146) serve o DataTable do CMS a partir da view `enquetes`. |
| `App\Http\Controllers\Prefecture\Controller` | `v6/app/Http/Controllers/Prefecture/Controller.php` | Base do CMS; no construtor registra `check.route.permission`. |

### 2.2 Portal

| Classe | Caminho | Papel |
|---|---|---|
| `Portal\SurveyController` | `v6/app/Http/Controllers/Portal/SurveyController.php` | Listagem, tela de votação, tela de resultado e gravação do voto. Herda de `App\Http\Controllers\Admin\Controller` (base do CMS) apenas para obter `getTemplateActived()`. |
| `App\Http\Controllers\Admin\Controller` | `v6/app/Http/Controllers/Admin/Controller.php` | `getTemplateActived()` lê `Configuration` chave `layout` e valida a existência da view `home/index` do template. |
| `App\Prefecture\Survey` / `SurveyResult` | `v6/app/Http/Model/Prefecture/Survey.php`, `v6/app/Http/Model/Prefecture/SurveyResult.php` | Usados diretamente (inclusive via `@inject` dentro das sidebars Blade). |

### 2.3 Auxiliares (fora do namespace do módulo)

| Recurso | Caminho | Uso |
|---|---|---|
| `activity()` | `spatie/laravel-activitylog` (via `v6/composer.json`) | Registro de auditoria do CMS. |
| `flash()` | `laracasts/flash` (via `v6/composer.json`) | Mensagens de sucesso/erro. |
| `menuClicks($slug)` | `v6/app/Helpers/functions.php:865` | Incrementa `menu_orders.clicks` do slug `enquete`. |
| `has_access($perm)` | `v6/app/Library/Helper/appHelpers.php:22` | Checagem de permissão por recurso/papel (Sentinel). |
| `slugfy()` / `toUSDataHourFormat()` | `v6/app/Http/Traits/AppTrait.php:758` e `:285` | Geração de slug e conversão `dd/mm/aaaa hh:mm` → `Y-m-d H:i`. |
| `DataTables::of()` | `yajra/laravel-datatables` | Resposta JSON do DataTable. |
| `ModulosServiceProvider` | `v6/app/Providers/ModulosServiceProvider.php` | Injeta `$modulos` (status por slug) nas views do portal; a sidebar só mostra a enquete se `modulos['enquetes']` estiver ligado. |

## 3. Fluxo detalhado

### 3.1 Criação / edição (CMS)

1. `GET /cms/enquete/create` → `SurveyController@create` → `prefecture.surveys.create`
   → inclui `prefecture.surveys.form`.
2. `POST /cms/enquete` → `SurveyController@store(SurveyRequest $request)`.
3. `SurveyRequest::rules()` exige `question`, `final_date`, `publication_date`,
   `answers` (`v6/app/Http/Requests/SurveyRequest.php:27-35`).
4. `SurveyRepository::upInsert($request)`:
   - `structureUpInsert()` gera `slug = slugfy(str_limit(question, 50))`; se já
     existe outro slug com o mesmo prefixo, acrescenta `"-{$count}"`;
   - converte `publication_date` e `final_date` de `dd/mm/aaaa hh:mm` para
     `Y-m-d H:i` (`v6/app/Repositories/Eloquent/Prefecture/Survey/SurveyRepository.php:33-40`);
   - `parent::upInsert()` executa `updateOrCreate(['id' => null], $data)` dentro de
     `DB::transaction`;
   - para cada item de `answers`, chama `SurveyAnswerRepository::upInsert()` com
     `survey_id` preenchido (`v6/app/Repositories/Eloquent/Prefecture/Survey/SurveyRepository.php:43-52`).
5. No `update` (`$id` presente), as opções existentes são **soft-deleted** e
   recriadas com novos `id` (`v6/app/Repositories/Eloquent/Prefecture/Survey/SurveyRepository.php:47`).

### 3.2 Votação (Portal)

1. `GET /enquetes/{slug}` → `Portal\SurveyController@show`.
   - carrega `Survey::with('surveyAnswers')->where('slug', $slug)->first()`;
   - se `final_date <= now`, redireciona para `enquetes/encerrada/{slug}`;
   - calcula `$votado` comparando o IP do visitante com o IP do **primeiro**
     `survey_results` da enquete.
2. O Blade `portal.layouts.surveys.show` monta o formulário com
   `Form::radio('survey_answer_id', $answer->id)` e o botão "Votar".
3. `onclick="contact()"` → `$.ajax` `POST` para `route('send.survey.ajax')` com
   `FormData` do formulário (`v6/resources/views/portal/layouts/surveys/show.blade.php:86-100`).
4. `Portal\SurveyController@sendSurvey`:
   - se `survey_answer_id` vazio → `response()->json([400, 'Por favor, marque uma das opções'])`;
   - injeta `ip` (`$_SERVER['REMOTE_ADDR']`) e `browser` (`$_SERVER['HTTP_USER_AGENT']`) no payload;
   - `SurveyResult::create($dataForm)` → `survey_results`;
   - sucesso → `response()->json([201, 'Voto salvo com sucesso! Obrigado por sua participação!'])`.
5. O JS substitui `#div-form-survey` por um cartão "Voto salvo com sucesso!".

### 3.3 Apuração (Portal)

`Portal\SurveyController@showClosed` (`v6/app/Http/Controllers/Portal/SurveyController.php:63-89`):

1. Carrega a enquete com suas opções.
2. `SurveyResult::where('survey_id', $survey->id)->get()` → total de votos.
3. Para **cada** opção: `SurveyResult::where('survey_answer_id', $answer->id)->get()`
   → contagem, e `porcetagem = parcial * 100 / total` via
   `calcularPorcentagem()` (mesmo nome de método da global `calcPorcentagem()`
   em `v6/app/Helpers/functions.php:874`, porém com grafia incorreta).
4. Os valores são injetados no model como atributos dinâmicos
   (`$answer['qtd_votos']`, `$answer['porcetagem']`) e consumidos pela view
   `portal.layouts.surveys.closed`.

## 4. Dependências externas

| Pacote | Versão declarada em `v6/composer.json` | Uso no módulo |
|---|---|---|
| `laracasts/flash` | `3.0.*` | `flash('Cadastrado com sucesso')->success()` |
| `spatie/laravel-activitylog` | `^2.8` | `activity()->performedOn($survey)->causedBy(Sentinel::getUser())->log('...')` |
| `yajra/laravel-datatables` | `^1.5` | `DataTables::of(...)->make(true)` |
| `cartalyst/sentinel` | `2.0.*` | `Sentinel::getUser()` (auditoria), `has_access()` |
| `laracasts/presenter` | `^0.2.2` | Não usado por este módulo (apenas pelo módulo *Revisões*) |

Não há uso de fila (`queue`), cache próprio, e-mail, storage ou storage de
arquivos neste módulo.

## 5. Acoplamentos

| Acoplamento | Detalhe |
|---|---|
| `modulos` (slug `enquetes`) | Se o módulo estiver desligado, a sidebar do portal não exibe a enquete; a rota `/enquetes` continua acessível. |
| `menu_orders` (slug `enquete`) | `menuClicks('enquete')` incrementa o contador de acessos ao item de menu do CMS. |
| `resources` (slug `enquete`, `modulo_id = 38`) | Base das permissões `enquete.index`, `enquete.create`, `enquete.edit`, `enquete.delete`, `enquete.inactive`, `enquete.inactivate`, `enquete.restore` e `enquete.show` gravadas em `v6/database/seeds/RolesTableSeeder.php:26`. |
| `configurations` (chave `layout`) | Define qual template do portal (`modelo1`..`modelo6`) será usado nas telas de enquete. |
| `App\Http\Controllers\Admin\Controller` | Base do controller do portal — herança invertida (portal herdando de base do CMS). |
| `Portal\ContactController` | A view do formulário de voto usa `Form::open(['route' => 'contato.enviar'])`, ou seja, o `action` HTML aponta para a rota de contato (`v6/routes/basepro.php:523`), e não para a rota de votação. Só funciona porque o JS define a URL explicitamente. |

## 6. Diagrama de sequência do voto

```
Cidadão            JS (survey show)      Rota                 Controller          PostgreSQL
   |                     |                 |                       |                   |
   | clica "Votar"      |                 |                       |                   |
   |-------------------->|                 |                       |                   |
   |                     | POST send-survey-ajax (FormData)       |                   |
   |                     |---------------->|                       |                   |
   |                     |                 | SurveyResult::create |                   |
   |                     |                 |---------------------->|                   |
   |                     |                 |                       | INSERT survey_results
   |                     |                 |                       |------------------>|
   |                     | [201, "Voto salvo com sucesso!"]      |                   |
   |                     |<----------------|-----------------------|                   |
   |<--------------------| substitui #div-form-survey por cartão de sucesso              |
```
