# Suporte — Migração para Laravel Moderno

> O plano depende de uma **decisão de negócio** previa: o módulo "Suporte"
> é a página de contato do fornecedor (ativa) ou um sistema de chamados
> (órfão)? Este documento cobre os dois caminhos, com o caminho A
> recomendado.

---

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

| Item | Regra |
|---|---|
| Acesso ao suporte | A página deve continuar acessível a **qualquer operador autenticado** (não há permissão hoje). |
| Fonte do dado | O fornecedor exibido é o de `fornecedor_ativo = true`. |
| Campos exibidos | site, e-mail, telefone, WhatsApp, endereço e mapa (embed). |
| Layout do CMS | Continuar dentro do AdminLTE (menu de engrenagem). |
| Se o sistema de tickets for reativado | O **protocolo** deve continuar sendo gerado e exibido; o ticket deve continuar sendo vinculado a uma categoria e a um usuário. |

---

## 2. Caminho A (recomendado): página de contato do fornecedor

### 2.1 Schema

**Nenhuma tabela nova.** O módulo passa a ser uma **projeção** do módulo
`fornecedores`. Recomenda-se:

- manter `suppliers` como está (ou movê-la para o novo
  `app/Models/Supplier`);
- remover `supports` e `supports_categories` (dados exportados antes);
- remover o slug `suporte` do registro de módulos, ou mantê-lo como
  **atalho** para a tela de fornecedores.

### 2.2 Modelos

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

    protected $fillable = [
        'name', 'slug', 'email', 'site', 'phone', 'whatsapp',
        'image', 'icon', 'address', 'locale', 'fornecedor_ativo',
    ];

    protected $casts = ['fornecedor_ativo' => 'boolean'];

    public function scopeAtivo(Builder $q): Builder
    {
        return $q->where('fornecedor_ativo', true);
    }
}
```

### 2.3 Controller

```php
// app/Http/Controllers/Admin/SupportController.php
final class SupportController extends Controller
{
    public function __invoke(SupplierRepository $suppliers): View
    {
        $fornecedor = $suppliers->ativo() ?? null;

        return view('admin.support.index', compact('fornecedor'));
    }
}
```

Rota:

```php
Route::get('/suporte', SupportController::class)
    ->name('informacoes-instituicao')
    ->middleware('check.logged');
```

> Manter o nome `informacoes-instituicao` evita quebrar o menu; o ideal é
> renomear para `suporte.index` e atualizar o único consumidor.

### 2.4 View

```blade
<x-support-card :fornecedor="$fornecedor" />
```

Com tratamento explícito do estado vazio:

```blade
@if (! $fornecedor)
    <x-alert type="warning">
        Nenhum fornecedor ativo cadastrado.
        Cadastre em <a href="{{ route('supplier.index') }}">Fornecedores</a>.
    </x-alert>
@else
    ...
@endif
```

### 2.5 Rotas REST/JSON

| Método | URI | Descrição |
|---|---|---|
| GET | `/api/suppliers/active` | fornecedor ativo (mesmo recurso do módulo `fornecedores`) |

---

## 3. Caminho B: reativar o sistema de chamados

### 3.1 Schema

```php
Schema::create('support_categories', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('slug')->unique();
    $table->boolean('status')->default(true);
    $table->timestamps();
});

Schema::create('support_tickets', function (Blueprint $table) {
    $table->id();
    $table->string('protocol')->unique();          // "DU25A2026"
    $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
    $table->foreignId('support_category_id')->constrained('support_categories');
    $table->string('subject');
    $table->text('description');
    $table->foreignId('assigned_to')->nullable()->constrained('users')->nullOnDelete();
    $table->string('status')->default('open');      // open | answered | closed
    $table->timestamps();
    $table->softDeletes();
});
```

### 3.2 Modelo e serviço

```php
final class SupportTicket extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'protocol', 'user_id', 'support_category_id',
        'subject', 'description', 'assigned_to', 'status',
    ];

    protected $casts = ['status' => SupportTicketStatus::class];
}

enum SupportTicketStatus: string
{
    case Open     = 'open';
    case Answered = 'answered';
    case Closed   = 'closed';
}
```

O protocolo deve ser gerado com garantia de unicidade:

```php
final class ProtocolGenerator
{
    public function generate(SupportCategory $category): string
    {
        $prefix = Str::upper(Str::substr($category->slug, 0, 2));

        do {
            $protocol = $prefix . now()->format('y') . Str::upper(Str::random(4)) . now()->year;
        } while (SupportTicket::where('protocol', $protocol)->exists());

        return $protocol;
    }
}
```

> Formato **equivalente** ao legado (`{prefixo}{id}{ano}`, ex.: `DU25A2026`),
> mas com sufixo aleatório para evitar a colisão do `max('id') + 1`.

### 3.3 Requests

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

    public function rules(): array
    {
        return [
            'support_category_id' => ['required', 'integer', 'exists:support_categories,id'],
            'subject'             => ['required', 'string', 'max:255'],
            'description'         => ['required', 'string', 'min:5'],
        ];
    }
}
```

### 3.4 Notificações (substituem o `Mail::send` imperativo)

```php
final class SupportTicketCreated extends Notification
{
    public function via(object $notifiable): array
    {
        return ['mail'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('Suporte | Ticket '.$this->ticket->protocol)
            ->line('Nova solicitação de suporte.')
            ->line('Assunto: '.$this->ticket->subject)
            ->line($this->ticket->description);
    }
}
```

Disparo: após o commit do ticket, notificar o e-mail de suporte
(configuração `email_suporte`) e o próprio usuário. Na v7, usar
`Notification::route('mail', $config)->notify(...)` e, se necessário,
uma **job em fila** para não bloquear a requisição.

### 3.5 Policy

```php
final class SupportTicketPolicy
{
    public function viewAny(User $u): bool   { return $u->hasCapability('suporte.index'); }
    public function create(User $u): bool    { return $u->hasCapability('suporte.create'); }
    public function view(User $u, SupportTicket $t): bool
    {
        return $u->hasCapability('suporte.show') || $t->user_id === $u->id;
    }
    public function answer(User $u, SupportTicket $t): bool
    {
        return $u->hasCapability('suporte.edit');
    }
}
```

Recursos a criar no catálogo: `suporte.index`, `suporte.create`,
`suporte.show`, `suporte.edit`, `suporte.delete`.

### 3.6 Endpoints REST/JSON

| Método | URI | Descrição | Autorização |
|---|---|---|---|
| GET | `/api/support/tickets` | lista paginada (com filtro por status/usuário) | `suporte.index` |
| POST | `/api/support/tickets` | abre um ticket | `suporte.create` |
| GET | `/api/support/tickets/{ticket}` | detalhe | `suporte.show` |
| POST | `/api/support/tickets/{ticket}/answer` | responde | `suporte.edit` |
| PATCH | `/api/support/tickets/{ticket}` | muda o status | `suporte.edit` |
| GET | `/api/support/categories` | categorias ativas | `suporte.index` |

### 3.7 API Resources

```php
final class SupportTicketResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'        => $this->id,
            'protocol'  => $this->protocol,
            'subject'   => $this->subject,
            'description' => $this->description,
            'status'    => $this->status->value,
            'category'  => $this->whenLoaded('category', fn () => $this->category->name),
            'user'      => $this->whenLoaded('user', fn () => $this->user->name),
            'created_at'=> $this->created_at?->toIso8601String(),
        ];
    }
}
```

### 3.8 Views

| Tela legada | v7 |
|---|---|
| `prefecture/support/index.blade.php` | `<x-support-ticket-table>` com DataTable server-side, filtro por status |
| `prefecture/support/create.blade.php` + `form.blade.php` | `<x-support-ticket-form>` (Reactivity API ou Livewire) |
| `prefecture/support/show.blade.php` | `<x-support-ticket-detail>` com o histórico de respostas |
| templates de e-mail | `resources/views/mail/support/created.blade.php` + `resources/views/mail/support/answered.blade.php` |

---

## 4. Fila, cache e storage

| Recurso | Caminho A | Caminho B |
|---|---|---|
| Fila | Nenhuma | Envio de e-mail em job `SendSupportNotification` (fila `mail`) |
| Cache | Nenhum | Cache do `email_suporte` (configuração) por 5 min |
| Storage | Nenhum | Nenhum |
| Sessão | Presente (login) | Presente (login) |

---

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

| Risco | Impacto | Mitigação |
|---|---|---|
| Escolher o caminho errado | retrabalho ou perda de dados de chamados | decisão de negócio antes de codificar |
| Remover `supports` com dados | perda de histórico | exportar CSV antes |
| Renomear a rota `informacoes-instituicao` | quebra o menu do AdminLTE | manter o alias ou atualizar o consumidor |
| Deixar `/init` acessível | credenciais administrativas criadas por qualquer visitante | **remover a rota** `GET /init` em qualquer cenário |
| Manter `has_access()` sem argumento | brecha de autorização | `has_access` com argumento obrigatório na v7 |
| Protocolo com formato diferente | quebra a expectativa do usuário | manter o formato `{prefixo}{id}{ano}` ou documentar a mudança |
| Gravar o ticket antes da notificação | inconsistência banco/e-mail | notificar via job após o commit; registrar a falha de envio |
| `Support`/`SupportCategory` com `SoftDeletes` inconsistente | registros "apagados" visíveis | uniformizar o uso do trait |
