# 12 — Gravação da Resposta e Tela de Resultado

> Continuação de `10-FLUXO-DE-PREENCHIMENTO-PORTAL.md` (etapas 1 a 4: abertura do formulário, HTML gerado, upload antecipado e validação no cliente). Este arquivo cobre as etapas 5 e 6 (gravação da resposta e tela de resultado), o diagrama do fluxo completo e o checklist de equivalência funcional.

## 1. Etapa 5 — gravação da resposta

O portal delega ao action do CMS: `v6/app/Http/Controllers/Portal/DynamicForms/FilledQuestionnaire/FilledQuestionnairesController.php` → `return SaveFilledQuestionnaireController::handle($request);`.

`v6/app/Http/Controllers/Prefecture/DynamicForms/FilledQuestionnaire/Actions/SaveFilledQuestionnaireController.php`:

```php
foreach ($questionnaire->asks as $ask) {
    $type = data_get($ask, 'ask_type_id');
    if($type && in_array($type, [AskTypesEnum::MULTI_SELECT, AskTypesEnum::MULTI_FILE])) {
        $aux = $request->get($ask->identifier);
        if(!is_array($aux) || count($aux) == 0){
            throw new HttpResponseException(
                response()->json(['errors' => ['message' => 'Erro ao preencher questionário!']], Response::HTTP_UNPROCESSABLE_ENTITY)
            );
        }
        $array = array_filter($aux, function ($option) { return $option !== null; });
        $request->merge([$ask->identifier => $array]);
    }
    $data[] = [
        'ask_id' => $ask->id,
        'value' => $request->get($ask->identifier)
    ];
}

$filled = FilledQuestionnaire::query()->updateOrCreate(["id" => $request->get('id')], [
    'ip' => $request->ip(),
    'questionnaire_id' => $request->get('questionnaire_id'),
    'answers' => $data
]);
```

E o vínculo dos arquivos:

```php
public static function saveFilledQuestionnaireOnAksFile($value, $filledQuestionnaireId)
{
    if(is_array($value)) {
        foreach ($value as $askFileId) {
            if(!$askFileId) { return; }
            AskFile::query()->updateOrCreate(["id" => $askFileId], [
                "filled_questionnaire_id" => $filledQuestionnaireId
            ]);
        }
    } else {
        if(!$value) { return; }
        AskFile::query()->updateOrCreate(["id" => $value], [
            "filled_questionnaire_id" => $filledQuestionnaireId
        ]);
    }
}
```

Formato gravado em `filled_questionnaires.answers` (JSON):

```json
[
  { "ask_id": 12, "value": "texto digitado" },
  { "ask_id": 13, "value": ["a", "b"] },
  { "ask_id": 14, "value": 37 }
]
```

Observações:

- `updateOrCreate` com `["id" => $request->get('id')]` — como o portal sempre envia `id` vazio, na prática é um `create`. Mas se o `id` for fornecido, uma resposta existente é **sobrescrita** (e o `ip` é substituído).
- O `questionnaire_id` vem do request (campo oculto `questionnaire_id`), **não** do slug acessado — um cliente pode gravar a resposta em outro questionário.
- O `ip` é o IP reportado por `$request->ip()`; o middleware `v6/app/Http/Middleware/TrustProxies.php` não define nenhum proxy confiável (`protected $proxies;` sem valor), portanto, atrás de balanceador/CDN o IP gravado pode ser o do proxy.
- Resposta JSON: `{"success":{"message":"Questionário preenchido com sucesso!"},"filled_id":N}`.

## 2. Etapa 6 — tela de resultado

```
GET /formularios/resultado/{slug}          → HTML (view do modelo)
GET /formularios/resultado/{id}/list       → JSON das respostas
```

`FilledQuestionnairesController@show`:

```php
$validator = validator(['slug' => $slug], ['slug' => ['required', 'exists:questionnaires,slug']]);
abort_if($validator->fails(), '404', 'Questionário não encontrado.');
$questionnaire = Questionnaire::query()
    ->with('categories', 'asks')
    ->where('status', true)
    ->where('has_result', true)
    ->where('slug', $slug)
    ->first();
abort_unless($questionnaire, 404, 'Questionário não encontrado.');
$isAuth = !!data_get(Sentinel::check(), 'id', false);
return view("portal.layouts.".$model.".dynamics-forms.filled-questionnaire.show", compact('questionnaire', 'title', 'model', 'modelNumber', 'isAuth'));
```

O JS `resultado.js` busca o JSON e desenha os gráficos:

```js
const xhr = new XMLHttpRequest();
xhr.open('GET', `/formularios/resultado/${id.value}/list`, true);
```

O endpoint `list` é `FilledQuestionnairesController@list`, que delega a `ShowFilledQuestionnaireController::handle($id)` → `ShowResponsesResource` com `categories`, `asks` (com arquivos) e `responses`.

Visibilidade por tipo (ver `04-VIEWS-E-UX.md` §2.4): gráficos de escolha/radio/satisfação são públicos; texto/arquivo/intervalo/checkbox exigem usuário logado.

## 3. Diagrama do fluxo completo

```mermaid
flowchart TD
    A[GET /formularios/slug] --> C{status = true?}
    C -- não --> D[404]
    C -- sim --> F[render.blade.php -> categories -> asks -> componente por tipo]
    F --> G[Cidadão preenche]
    G --> H{escolheu arquivo?}
    H -- sim --> I[POST /formularios/ask-file]
    I --> J[ask_files criada, filled_questionnaire_id = null]
    H -- não --> K[Clica Salvar]
    J --> K
    K --> L[validate() no cliente]
    L -- inválido --> M[Erro na tela]
    L -- ok --> N[POST /formularios/fill-questionnaire/save]
    N --> O[SaveFilledQuestionnaireController::handle]
    O --> P[filled_questionnaires.answers = JSON]
    O --> Q[ask_files.filled_questionnaire_id = filled.id]
    P --> R[Sucesso na tela]
    Q --> R
    R --> S{has_result?}
    S -- sim --> T[GET /formularios/resultado/slug]
    T --> U[GET /formularios/resultado/id/list -> graficos]
```

## 4. Checklist de equivalência funcional (v7)

- [ ] `GET /formularios/{slug}` continua público e 404 para questionário inativo.
- [ ] O `name` dos inputs continua sendo o identificador da pergunta (ou o UUID, se o identificador migrar — exige reescrever o JS e o `ask-file`).
- [ ] O upload continua ocorrendo antes do envio (ou passa a ocorrer junto, o que exige temp table).
- [ ] `answers` continua sendo gravado com `ask_id` + `value` (ou vira tabela `answers` — ver `06-MIGRACAO-MODERNA.md`).
- [ ] A resposta ao submit mantém `{success:{message}, filled_id}` e o endpoint `result.list` mantém `{data:{categories, asks, responses}}` (o JS do portal depende desses formatos).
- [ ] `has_result` continua sendo requisito da tela de resultado.
