# Perguntas Frequentes — Migração para Laravel Moderno (PHP 8.x + Laravel 11/12)

> Plano de reconstrução do módulo. **DECISÃO** marca o que não pode mudar.

---

## 1. Schema de migração Laravel

A v7 deve ter tabela própria, separando pergunta de resposta e permitindo
ordenação, status e agrupamento (categoria/tema):

```php
// exemplo ilustrativo
Schema::create('faq_questions', function (Blueprint $table) {
    $table->id();
    $table->string('question', 191);
    $table->text('answer');
    $table->boolean('status')->default(true)->index();
    $table->unsignedInteger('order')->default(0);
    $table->foreignId('faq_category_id')->nullable()->constrained('faq_categories');
    $table->timestamps();
    $table->softDeletes();
});
```

**DECISÃO 1 — pergunta e resposta continuam no mesmo registro**, como no legado.
A migração mapeia `contents.title` -> `question` e `contents.text` -> `answer` para os
registros da categoria 12.

**DECISÃO 2 — a URL pública `/acesso-rapido/perguntas-frequentes` permanece**
(link já publicado e indexado por buscadores). A v7 pode *adicionar*
`/perguntas-frequentes` como alias permanente para a mesma página.

Script de migração de dados (exemplo ilustrativo):

```php
DB::table('contents')
    ->where('category_contents_id', $faqCategoryId)
    ->whereNull('deleted_at')
    ->orderBy('id')
    ->chunk(500, function ($rows) use ($faqCategoryId) {
        foreach ($rows as $row) {
            DB::table('faq_questions')->insert([
                'question' => $row->title,
                'answer' => $row->text,
                'status' => (bool) $row->status,
                'order' => $row->order ?? 0,
                'created_at' => $row->created_at,
                'updated_at' => $row->updated_at,
                'deleted_at' => $row->deleted_at,
            ]);
        }
    });
```

## 2. Models

```php
// exemplo ilustrativo
class FaqQuestion extends Model
{
    use SoftDeletes;

    protected $fillable = ['question', 'answer', 'status', 'order', 'faq_category_id'];

    protected $casts = [
        'status' => 'boolean',
        'order' => 'integer',
    ];

    public function category(): BelongsTo
    {
        return $this->belongsTo(FaqCategory::class, 'faq_category_id');
    }

    public function scopePublished(Builder $query): Builder
    {
        return $query->where('status', true);
    }
}
```

| Model | Tabela | Observações |
|---|---|---|
| `FaqQuestion` | `faq_questions` | Pergunta/resposta |
| `FaqCategory` | `faq_categories` | Opcional: agrupa por tema (o legado não tem) |

## 3. Form Requests

```php
// exemplo ilustrativo
class StoreFaqQuestionRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', FaqQuestion::class);
    }

    public function rules(): array
    {
        return [
            'question' => ['required', 'string', 'max:120'],
            'answer' => ['required', 'string'],
            'status' => ['boolean'],
            'order' => ['integer', 'min:0'],
        ];
    }
}
```

**DECISÃO 3 — a resposta passa a ser validada no servidor.** No legado ela só é
obrigatória no markup (`form.blade.php`).

## 4. Policies / autorização

| Ação | Permissão legada | Policy v7 |
|---|---|---|
| Ver no portal | — | pública |
| Criar/editar/excluir | `perguntas-frequentes.create`, `.edit`, `.delete` | `FaqQuestionPolicy` |

O nome da permissão continua sendo o slug `perguntas-frequentes` para preservar os
papéis já gravados (`resources.slug`, `menu_orders.slug`).

## 5. Endpoints REST/JSON sugeridos

| Método | URI | Descrição |
|---|---|---|
| GET | `/api/v1/perguntas-frequentes` | Lista paginada (`?q=`, `?categoria=`, `?per_page=`) |
| GET | `/api/v1/perguntas-frequentes/{id}` | Item único |
| POST/PUT/DELETE | `/api/v1/perguntas-frequentes[/{id}]` | CRUD do CMS via API |
| GET | `/perguntas-frequentes` | Página pública (alias novo) |
| GET | `/acesso-rapido/perguntas-frequentes` | URL histórica (mantida, com redirect interno) |

## 6. Recursos de API

```php
// exemplo ilustrativo
class FaqQuestionResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'question' => $this->question,
            'answer' => $this->answer,
        ];
    }
}
```

## 7. Views Blade modernas

| Antes (v6) | Depois (v7) |
|---|---|
| 7 arquivos de listagem (modelo1..6, newsbit, default) | 1 componente `<x-faq.lista :perguntas="$perguntas" />` |
| 2 implementações de filtro client-side | 1 componente de filtro (Alpine.js ou Stimulus) com normalização de acentos |
| `getTemplateActived()` escolhendo o arquivo | Controller único com layout do portal |

Exemplo de componente (exemplo ilustrativo):

```blade
{{-- resources/views/components/faq/lista.blade.php --}}
<div class="faq-search">
    <input type="search" id="faq-filter" placeholder="Digite para filtrar:" />
</div>
<ul class="list-unstyled" id="ulItens">
    @foreach ($perguntas as $pergunta)
        <li>
            <h5>{{ $pergunta->question }}</h5>
            <p><strong>R.</strong> {!! $pergunta->answer !!}</p>
            <hr>
        </li>
    @endforeach
</ul>
```

**DECISÃO 4 — o prefixo "R." antes da resposta e o cartão "Perguntas Frequentes" com
ícone permanecem**, porque são o contrato visual percebido pelo cidadão.

**DECISÃO 5 — a resposta continua sendo HTML renderizado** (`{!! !!}`), mas sanitizada na
gravação (HTMLPurifier) para eliminar o risco de XSS apontado no legado.

## 8. Fila, cache e storage

| Aspecto | Recomendação |
|---|---|
| Cache | `Cache::remember('faq:publico', 600, fn () => FaqQuestion::published()->orderBy('order')->get())`, invalidado no save/delete |
| Fila | Nenhuma tarefa assíncrona é necessária |
| Storage | Nenhum arquivo é previsto para a FAQ |
| Busca | Integrar `question`/`answer` ao índice de busca (a busca atual usa `ilike` + `translate`) |
|PSR-4 | `app/Models/Faq/FaqQuestion.php` |

## 9. Riscos e decisões de equivalência funcional

| # | Risco | Mitigação |
|---|---|---|
| R1 | Quebrar links antigos `/acesso-rapido/perguntas-frequentes` | Manter a rota (DECISÃO 2) com redirect para o novo controller |
| R2 | Alterar o conjunto de perguntas exibidas ao filtrar por status | Se o-status for corrigido, avisar o negócio (mudança observável) |
| R3 | Perder o seed de demonstração (8 perguntas da LAI) | Replicar no seeder da v7 (`database/seeders/FaqQuestionSeeder.php`) |
| R4 | Dependência do id 12 em `getViewToBeReturned` | Migrar para resolução por slug |
| R5 | Ruptura de permissões ao renomear o slug | Manter `perguntas-frequentes` |
| R6 | `str_limit` incompatível com Laravel 11 | `Str::limit` |
| R7 | Duplicação de views por layout | Componente único (DECISÃO 4/5) |
