# Fila de Espera SUS — Migração para Laravel Moderno

> Plano de reconstrução em PHP 8.x + Laravel 11/12 (a "v7"). O módulo
> **não existe** na v6: esta é uma reimplementação, não uma porta.

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

| Comportamento legado | Decisão na v7 |
|---|---|
| URL pública de listagem `/fila-espera-sus` | Manter (link externo e índice de buscadores já existentes). |
| URL de detalhe `/fila-espera-sus/{slug}` | Manter, com slug recalculado a partir dos **novos** valores de especialidade/procedimento + id. |
| Cartão SUS sempre mascarado na saída pública | Manter e **melhorar**: mascarar também o valor armazenado ou, no mínimo, impedir a busca pelo número completo. |
| Exportação por `?type=` na mesma URL | Manter (o JS do portal já depende disso) e acrescentar rota dedicada `/fila-espera-sus/exportar/{tipo}`. |
| Filtros `specialty`, `priority_criteria`, `busca` | Manter os nomes dos parâmetros (links e URLs salvas). |
| Paginação de 15 itens | Manter ou tornar configurável (`?per_page=`), com 15 como padrão. |
| Soft delete | Manter. |
| Publicação via `is_show` | Manter, mas **expor o controle** no formulário (o legado não permitia despublicar). |
| Anexos em PDF com descrição | Manter, mas storing em disco privado com rota de download autenticada. |
| Auditoria via `activity_log` | Manter o nome de log `fila-espera-sus` para preservar o histórico. |

## 2. Schema de migração

Os caminhos `v7/...` abaixo são **propostas** de estrutura para a
reconstrução (não existem no repositório atual).

```php
// v7/database/migrations/2026_10_21_000000_create_fila_espera_sus_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('fila_espera_sus', function (Blueprint $table) {
            $table->id();
            $table->string('sus_card', 32)->index();
            $table->date('update_date')->index();
            $table->date('register_date');
            $table->unsignedInteger('patients_count')->default(0);
            $table->string('specialty')->index();
            $table->string('procedure');
            $table->unsignedInteger('avg_wait_time')->default(0);
            $table->string('priority_criteria')->index();
            $table->string('contact')->nullable();
            $table->string('queue_status');
            $table->json('file_path')->nullable();
            $table->boolean('is_show')->default(true)->index();
            $table->string('slug')->nullable()->unique();
            $table->timestamps();
            $table->softDeletes();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('fila_espera_sus');
    }
};
```

Notas de migração de dados:

- `file_path` vira `json` (no legado era `text` com JSON); a migração de
  dados deve fazer `CAST`/parse do texto para o formato estruturado.
- Índices novos em `specialty`, `priority_criteria`, `update_date` e
  `is_show` resolvem as consultas de filtro e ordenação, que hoje são
  varreduras sequenciais.
- Se a prefeitura mantiver o volume da v5 (hoje 0 registros), a migração
  de dados é trivial; se houver dados em outro ambiente, migrar com
  mascaramento do `sus_card` no destino, mediante decisão do negócio.

## 3. Model

```php
// v7/app/Models/FilaEsperaSus.php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\Traits\LogsActivity;

class FilaEsperaSus extends Model
{
    use HasFactory, SoftDeletes, LogsActivity;

    protected $table = 'fila_espera_sus';

    protected $fillable = [
        'sus_card', 'update_date', 'register_date', 'patients_count',
        'specialty', 'procedure', 'avg_wait_time', 'priority_criteria',
        'contact', 'queue_status', 'is_show',
    ];

    protected $casts = [
        'update_date' => 'date',
        'register_date' => 'date',
        'patients_count' => 'integer',
        'avg_wait_time' => 'integer',
        'is_show' => 'boolean',
        'file_path' => 'array',
    ];

    protected static $logName = 'fila-espera-sus';

    // slug recalculado a partir dos valores novos
    protected static function booted(): void
    {
        static::saving(function (FilaEsperaSus $model) {
            $model->slug = Str::slug(
                $model->specialty.'-'.$model->procedure.'-'.$model->getKey()
            );
        });
    }
}
```

- `slug` sai do `$fillable` (é gerado pelo model).
- Acessores: `getSusCardMaskedAttribute()` reaproveitado **e** usado por
  todas as views (elimina as cinco implementações duplicadas da v5).
- `getDescriptionForEvent()` reimplementado no formato do activity log 4.x.
- `$logFillable` substituído por `$logAttributes` explícito, **sem**
  `sus_card` e `contact` (dados sensíveis não vão para o log).

## 4. Enums sugeridos

O legado tratava `queue_status` e `priority_criteria` como texto livre, o
que gera variação de escrita ("Em espera", "em espera", "Em Espera").
Proposta (com migração de normalização):

```php
// v7/app/Enums/QueueStatus.php
enum QueueStatus: string
{
    case Waiting = 'em_espera';
    case Scheduled = 'agendado';
    case Attended = 'atendido';
    case Cancelled = 'cancelado';
    case Suspended = 'suspenso';
}
```

O mesmo para o critério de priorização (`Urgência`, `Ordem de chegada`,
`Condição clínica`, `Data de cadastro`). Enquanto o negócio não aprovar
enums, manter `string` com **normalização** e uma tabela de apoio
(`fila_espera_sus_status` e `fila_espera_sus_prioridades`).

## 5. Form Requests

```php
// v7/app/Http/Requests/StoreFilaEsperaSusRequest.php
public function rules(): array
{
    return [
        'sus_card' => ['required', 'digits_between:1,15'],
        'update_date' => ['required', 'date'],
        'register_date' => ['required', 'date'],
        'patients_count' => ['required', 'integer', 'min:0'],
        'specialty' => ['required', 'string', 'max:255'],
        'procedure' => ['required', 'string', 'max:255'],
        'avg_wait_time' => ['required', 'integer', 'min:0'],
        'priority_criteria' => ['required', 'string', 'max:255'],
        'contact' => ['nullable', 'string', 'max:255'],
        'queue_status' => ['required', 'string', 'max:255'],
        'arquivo' => ['nullable', 'array', 'max:10'],
        'arquivo.*' => ['nullable', 'mimes:pdf', 'max:10240'],
    ];
}
```

Mudanças em relação à v5: `numeric` em `sus_card` é substituído por
`digits_between` (rejeita sinal e casas decimais), limite de tamanho de
arquivo e de quantidade, e `authorize()` passa a delegar à Policy.

`UpdateFilaEsperaSusRequest` herda as mesmas regras; a exclusão de anexos
passa a ser um endpoint próprio (`DELETE /cms/fila-espera-sus/{id}/arquivo/{uuid}`),
com UUID no lugar do índice do array (o índice posicional quebra quando um
arquivo é removido no meio da lista).

## 6. Policies e autorização

```php
// v7/app/Policies/FilaEsperaSusPolicy.php
class FilaEsperaSusPolicy
{
    public function viewAny(User $user): bool      { return $user->can('fila-espera-sus.index'); }
    public function create(User $user): bool       { return $user->can('fila-espera-sus.create'); }
    public function update(User $user, FilaEsperaSus $m): bool { return $user->can('fila-espera-sus.edit'); }
    public function delete(User $user, FilaEsperaSus $m): bool { return $user->can('fila-espera-sus.delete'); }
    public function download(User $user, FilaEsperaSus $m): bool { return $user->can('fila-espera-sus.index'); }
}
```

As permissões passam a vir do sistema de papéis da v7 (tabela `modulos` +
`resources.modulo_id`, conforme
`v6/database/migrations/2024_11_01_150206_create_modulos_table.php` e
`v6/database/migrations/2024_11_05_104026_add_modulo_id_to_resources_table.php`),
e não mais do par `resources`/`permissions` da v5.

O registro do módulo precisa ser recriado: `modulos` (novo) +
`resources` com `slug = 'fila-espera-sus'` e `modulo_id` apontando para o
módulo de saúde.

## 7. Endpoints sugeridos

### Portal (público)

| Método | URI | Descrição |
|---|---|---|
| GET | `/fila-espera-sus` | Listagem paginada + filtros (HTML). |
| GET | `/fila-espera-sus/{slug}` | Detalhe (HTML). |
| GET | `/fila-espera-sus/exportar/{tipo}` | Exportação (`xls`, `pdf`, `csv`, `txt`) com os filtros da query string. |
| GET | `/api/v1/fila-espera-sus` | Listagem em JSON (paginado, com `specialty`, `priority_criteria`, `busca`). |
| GET | `/api/v1/fila-espera-sus/{slug}` | Detalhe em JSON. |

### CMS (`/cms`)

| Método | URI | Descrição |
|---|---|---|
| GET | `/cms/fila-espera-sus` | Listagem (DataTable server-side ou Inertia). |
| GET | `/cms/fila-espera-sus/criar` | Formulário. |
| POST | `/cms/fila-espera-sus` | Cadastro. |
| GET | `/cms/fila-espera-sus/{id}/editar` | Formulário de edição. |
| PUT | `/cms/fila-espera-sus/{id}` | Atualização. |
| DELETE | `/cms/fila-espera-sus/{id}` | Exclusão (soft). |
| POST | `/cms/fila-espera-sus/excluir-lote` | Exclusão em lote — corpo `{"ids": [1,2]}` (corrige o bug A3). |
| POST | `/cms/fila-espera-sus/{id}/arquivos` | Upload de anexos. |
| DELETE | `/cms/fila-espera-sus/{id}/arquivos/{uuid}` | Remoção de anexo. |
| GET | `/cms/fila-espera-sus/{id}/arquivos/{uuid}/download` | Download autenticado. |

## 8. API Resource

```php
// v7/app/Http/Resources/FilaEsperaSusResource.php
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'sus_card_masked' => $this->sus_card_masked,
        'update_date' => $this->update_date?->toDateString(),
        'register_date' => $this->register_date?->toDateString(),
        'patients_count' => $this->patients_count,
        'specialty' => $this->specialty,
        'procedure' => $this->procedure,
        'avg_wait_time' => $this->avg_wait_time,
        'priority_criteria' => $this->priority_criteria,
        'contact' => $this->contact,
        'queue_status' => $this->queue_status,
        'slug' => $this->slug,
    ];
}
```

O `sus_card` **completo** não deve sair da API pública; se o negócio
exigir o número para atendimento presencial, expor via endpoint autenticado
e auditado.

## 9. Views modernas

- **CMS:** um único formulário em Blade (ou Inertia/Vue) com componente
  `<x-fila-espera-sus.form>`, separado em `create`/`edit` apenas pelo
  `route()`; usar `<input type="date">` e `<input type="number">` nativos,
  sem `laracasts/presenter`.
- **Portal:** eliminar as 18 views duplicadas. Criar
  `resources/views/portal/fila-espera-sus/{index,show,filtro}.blade.php`
  como views únicas, incluídas por todos os layouts (o layout ativo continua
  sendo resolvido por `configurations.layout`, mas com um **fallback** para
  o layout `default`).
- **Tabela do portal:** componente Blade único
  (`x-fila-espera-sus.table`) compartilhado por todos os temas.
- **Exportação:** uma única classe `FilaEsperaSusExport` (`FromView`,
  `ShouldAutoSize`) e uma view Blade de exportação com cabeçalho fixo
  (título, município, data) **sempre** presente, corrigindo o bug A4.

## 10. Fila, cache e storage

| Item | Decisão |
|---|---|
| Anexos | Disco local (ou S3) **privado**, caminho `storage/app/private/fila-espera-sus/{id}/{uuid}.pdf`; download via rota autenticada com `response()->download()`. |
| Exportações | Geradas sob demanda; para volumes grandes, enfileirar (`QUEUE_CONNECTION`) e notificar por e-mail. |
| Cache | `Cache::remember` para as listas de `specialty` e `priority_criteria` (selects de filtro), invalidando ao alterar um registro. |
| Consulta de search | Índice trigram (`pg_trgm`) em `specialty`/`procedure` se a busca textual crescer. |

## 11. Riscos e decisões de equivalência

| Risco | Impacto | Mitigação na v7 |
|---|---|---|
| Exposição de dado de saúde identificável | Alto (LGPD, art. 11) | Mascarar em repouso ou no acesso; nunca expor `sus_card` completo em rota pública; registrar acessos. |
| Enumeração de cartões pelo filtro `busca` | Alto | Remover `sus_card` da busca pública ou exigir autenticação para busca por cartão. |
| Extensão de arquivo executável | Alto | Validar extensão **e** MIME com lista fechada (`pdf`) e gravar com extensão fixa `.pdf`. |
| Perda da trilha de auditoria | Médio | Recriar `activity_log` (ou usar o pacote 4.x) com `log_name = 'fila-espera-sus'`. |
| Perda do resource de permissão | Médio | Recriar `modulos` + `resources` com `modulo_id` e vincular aos papéis. |
| Volume inesperado na exportação | Médio | Limitar o número de linhas exportadas e/ou processar em segundo plano. |
| Cards SUS com 1 a 5 dígitos (validação aceita) | Baixo | Definir com o negócio a máscara mínima de ocultação. |
| Slug mudando após edição | Baixo | Slug estável: manter o valor original ou gerar redirecionamento do slug antigo. |
