# Manual do Sistema — Migração para Laravel Moderno

> Plano de reconstrução do módulo em **PHP 8.x + Laravel 11/12**.

---

## 1. Decisões de equivalência (o que NÃO pode mudar)

| Item | Regra |
|---|---|
| Um manual por módulo | Deve continuar existindo a noção de "manual de um módulo", com título, objetivo, links e áreas. |
| Áreas com imagem e descrição | Cada área mantém o print (imagem) e a descrição HTML, exibidos na mesma tela. |
| Edição em tela única | Manual e áreas são editados juntos, sem navegação para outra página. |
| Links `url_portal` / `url_cms` | Devem continuar disponíveis como atalhos. |
| Autorização | O módulo deve continuar protegido por permissão; a v7 deve corrigir o acesso (hoje só `super-admin`). |
| Descrição como HTML | O texto rico é o formato esperado; deve ser sanitizado, não escapado. |

---

## 2. Schema de migração Laravel

```php
Schema::create('manuals', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('slug')->unique();          // novo: chave estável
    $table->text('objective');                 // 'objetivo'
    $table->string('url_portal')->nullable();
    $table->string('url_cms')->nullable();
    $table->foreignId('module_id')->nullable()
          ->constrained('modules')->nullOnDelete();   // vínculo com o registro de módulos
    $table->foreignId('created_by')->nullable()->constrained('users');
    $table->softDeletes();
    $table->timestamps();
});

Schema::create('manual_areas', function (Blueprint $table) {
    $table->id();
    $table->foreignId('manual_id')->constrained('manuals')->cascadeOnDelete();
    $table->foreignId('archive_id')->nullable()->constrained('archives')->nullOnDelete();
    $table->string('title');
    $table->text('description')->nullable();     // HTML sanitizado
    $table->softDeletes();
    $table->timestamps();
});
```

### 2.1 Migração dos dados

```php
// títulos -> slug estável
DB::table('manuals')->orderBy('id')->get()->each(function ($m) {
    DB::table('manuals')->where('id', $m->id)->update([
        'slug' => Str::slug($m->titulo),
        'objective' => $m->objetivo,
        'title' => $m->titulo,
    ]);
});
```

> A coluna `titulo` vira `title` e `objetivo` vira `objective` — ou, de
> forma menos invasiva, manter os nomes atuais. A recomendação é manter
> `titulo`/`objetivo` para reduzir o ruído da migração, já que não há
> consumidor em inglês.

---

## 3. Modelos

```php
// app/Models/Manual.php
final class Manual extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = ['titulo', 'slug', 'objetivo', 'url_portal', 'url_cms', 'module_id'];

    public function areas(): HasMany
    {
        return $this->hasMany(ManualArea::class)->orderBy('id');
    }

    public function module(): BelongsTo { return $this->belongsTo(Module::class); }
}

// app/Models/ManualArea.php
final class ManualArea extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = ['titulo', 'descricao', 'manual_id', 'archive_id'];

    protected $casts = ['descricao' => 'string'];

    public function manual(): BelongsTo { return $this->belongsTo(Manual::class); }
    public function archive(): BelongsTo { return $this->belongsTo(Archive::class); }
}
```

> O `descricao` continua sendo HTML; a sanitização deve ocorrer na
> **escrita** (FormRequest) e, se necessário, na leitura.

---

## 4. Form Requests

```php
final class StoreManualRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Manual::class);
    }

    public function rules(): array
    {
        return [
            'titulo'     => ['required', 'string', 'max:191'],
            'objetivo'   => ['required', 'string', 'max:5000'],
            'url_portal' => ['nullable', 'url', 'max:191'],
            'url_cms'    => ['nullable', 'url', 'max:191'],
        ];
    }

    protected function prepareForValidation(): void
    {
        $this->merge(['slug' => Str::slug($this->input('titulo'))]);
    }
}
```

```php
final class StoreManualAreaRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('update', $this->route('manual'));
    }

    public function rules(): array
    {
        return [
            'titulo'    => ['required', 'string', 'max:191'],
            'descricao' => ['nullable', 'string'],          // HTML sanitizado
            'arquivo'   => ['nullable', 'image', 'max:4096'],
            'manual_id' => ['required', 'integer', 'exists:manuals,id'],
        ];
    }
}
```

Sanitização do HTML (usar `mews/purifier` ou `HTMLPurifier`):

```php
protected function prepareForValidation(): void
{
    if ($this->filled('descricao')) {
        $this->merge(['descricao' => Purifier::clean($this->input('descricao'))]);
    }
}
```

---

## 5. Policies

```php
final class ManualPolicy
{
    public function viewAny(User $u): bool   { return $u->hasCapability('manual-usuario.index'); }
    public function create(User $u): bool    { return $u->hasCapability('manual-usuario.create'); }
    public function view(User $u, Manual $m): bool  { return $u->hasCapability('manual-usuario.show'); }
    public function update(User $u, Manual $m): bool { return $u->hasCapability('manual-usuario.edit'); }
    public function delete(User $u, Manual $m): bool { return $u->hasCapability('manual-usuario.delete'); }
}
```

E o recurso correspondente, que **deve** ser criado (o que faltou na v6):

| recurso (slug) | ação | descrição |
|---|---|---|
| `manual-usuario` | `index` | Listar manuais do sistema |
| `manual-usuario` | `create` | Cadastrar manual do sistema |
| `manual-usuario` | `edit` | Editar manual do sistema |
| `manual-usuario` | `delete` | Excluir manual do sistema |
| `manual-usuario` | `show` | Ver manual do sistema |

> A v7 deve rodar um **seeder idempotente** que garanta a existência de
> todos os recursos dos módulos, e um teste que falhe se algum faltar.

---

## 6. Endpoints REST/JSON sugeridos

| Método | URI | Descrição | Autorização |
|---|---|---|---|
| GET | `/api/manuals` | lista (paginada) | `manual-usuario.index` |
| POST | `/api/manuals` | cria | `manual-usuario.create` |
| GET | `/api/manuals/{manual}` | detalhe com áreas | `manual-usuario.show` |
| PUT | `/api/manuals/{manual}` | atualiza | `manual-usuario.edit` |
| DELETE | `/api/manuals/{manual}` | exclui (soft) | `manual-usuario.delete` |
| GET | `/api/manuals/{manual}/areas` | áreas do manual | `manual-usuario.show` |
| POST | `/api/manuals/{manual}/areas` | cria área (multipart) | `manual-usuario.edit` |
| PUT | `/api/manual-areas/{area}` | atualiza área | `manual-usuario.edit` |
| DELETE | `/api/manual-areas/{area}` | exclui área (+ arquivo) | `manual-usuario.delete` |

> A exclusão do manual deve ser `DELETE`, não `GET`. A rota legada
> `GET /cms/manual/delete/{id}` deve ser removida.

---

## 7. API Resources

```php
final class ManualResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'         => $this->id,
            'titulo'     => $this->titulo,
            'slug'       => $this->slug,
            'objetivo'   => $this->objetivo,
            'url_portal' => $this->url_portal,
            'url_cms'    => $this->url_cms,
            'areas'      => ManualAreaResource::collection($this->whenLoaded('areas')),
        ];
    }
}

final class ManualAreaResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'         => $this->id,
            'titulo'     => $this->titulo,
            'descricao'  => $this->descricao,
            'arquivo'    => $this->archive?->url,
        ];
    }
}
```

---

## 8. Views Blade modernas

| Tela legada | v7 |
|---|---|
| `manual-usuario/index.blade.php` | `<x-manual-index :manuals="..." />` com Livewire (busca, ordenação alfabética, grid de 3 colunas) |
| `manual-usuario/create.blade.php` + `form.blade.php` | `<x-manual-form :manual="..." />` — um único componente para create e edit |
| `manual-usuario/show.blade.php` | `<x-manual-show :manual="..." />` com `<x-lightbox>` para as imagens |
| `manual-usuario/area/index.blade.php` + `area/modal.blade.php` | `<x-manual-areas :manual="..." />` — componente Livewire com upload e editor, **sem JS imperativo** |

Componente de área (Livewire 3):

```php
final class ManualAreas extends Component
{
    public Manual $manual;
    public ?string $titulo = null;
    public ?string $descricao = null;
    public ?TemporaryUploadedFile $arquivo = null;

    public function save(): void
    {
        $this->validate([
            'titulo'    => ['required', 'string', 'max:191'],
            'descricao' => ['nullable', 'string'],
            'arquivo'   => ['nullable', 'image', 'max:4096'],
        ]);

        $path = $this->arquivo?->store("manual-usuario/area/{$this->manual->id}/destaque", 'public');

        ManualArea::updateOrCreate(
            ['id' => $this->areaId],
            [
                'manual_id'  => $this->manual->id,
                'titulo'     => $this->titulo,
                'descricao'  => Purifier::clean((string) $this->descricao),
                'archive_id' => $path ? $this->archiveRepository->store($path, $this->manual->id) : $this->areaId?->archive_id,
            ]
        );

        $this->reset(['titulo', 'descricao', 'arquivo']);
    }
}
```

O `TemporaryUploadedFile` do Livewire resolve o upload de forma segura
(sem `dd()`, sem `FormData` manual, sem memória de 2 MB no PHP).

---

## 9. Fila, cache e storage

| Recurso | Proposta v7 |
|---|---|
| Storage | Disk `public`, caminho `manual-usuario/area/{manual_id}/destaque/{uuid}.{ext}`; usar `Storage::putFileAs` |
| Fila | Opcional: um job `DeleteManualImage` para limpar arquivos órfãos |
| Cache | Nenhum |
| Sessão | Não utilizada |
| Sanitização | `mews/purifier` (configuração restritiva: sem `script`, sem `on*`) |

---

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

| Risco | Impacto | Mitigação |
|---|---|---|
| Remover `GET /cms/manual/delete/{id}` | links antigos quebram | manter um redirect para a nova rota `DELETE` por um período |
| Trocar `titulo`/`objetivo` por `title`/`objective` | seeders e consultas por nome quebram | manter os nomes atuais ou migrar em bloco |
| Remover os dados da migration `2023_02_01_083626` | `migrate:fresh` perde os manuais | mover para seeder **antes** de rodar em produção |
| Sanitizar o HTML da descrição | conteúdo existente com tags não permitidas é alterado | re-sanitizar no backfill e avisar o cliente |
| Criar o recurso `manual-usuario` | perfis passam a poder receber a permissão (mudança de comportamento) | comunicação e treinamento: o módulo deixa de ser exclusivo do super-admin |
| Adicionar FK `module_id` | manuais sem módulo correspondente ficam `null` | backfill por mapeamento título ↔ slug do módulo |
| Cachear a listagem | um manual novo não aparece | invalidar o cache no `store`/`update`/`delete` |
