# Feriados Municipais — Migração para Laravel Moderno (v7)

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

| # | Comportamento legado | Regra para a v7 |
|---|---|---|
| E1 | O portal mostra apenas o ano corrente | **Decidir com o negócio**: manter "ano corrente" ou mostrar todos os anos com um seletor. Se mantido, o filtro deve ser explícito (`whereYear('date', now()->year)`) |
| E2 | Um cartão por mês, nome do mês em português | Preservar a ordem cronológica e os rótulos |
| E3 | Cada feriado exibe ícone, dia em selo e descrição | Preservar o desenho (o CSS `feriados-municipais.css` é a referência visual) |
| E4 | O ícone é uma classe CSS livre (ex.: `fab fa-500px`) | Preservar o formato do dado; opcionalmente oferecer um *picker* validado |
| E5 | A data é uma data sem hora | Preservar (`date`) |
| E6 | A exclusão é lógica | Preservar (`SoftDeletes`) |
| E7 | A listagem do CMS ordena por data de cadastro | **Questionar**: o mais útil é ordenar pela data do feriado |
| E8 | As rotas e os nomes `painel.feriados-municipais.*` | Preservar os nomes (links internos e permissões dependem deles) ou migrar em bloco com aliases |
| E9 | A permissão herdada do `check.route.permission` (`feriados-municipais.*`) | Preservar as chaves de permissão para não reatribuir papéis |

## 2. Schema de migração Laravel (equivalente)

A tabela pode ser **mantida como está** — ela já é mínima e estável. Recomenda-se
apenas normalizar os tipos e adicionar índices.

```php
// (ilustrativo) migration de normalização
Schema::table('feriados_municipais', function (Blueprint $table) {
    // data já é 'date'; apenas registra a intenção
});

Schema::create('feriados_municipais', function (Blueprint $table) {
    $table->id();
    $table->date('date')->index();          // índice para o filtro por ano
    $table->string('icon', 60)->nullable(); // alinhado com a regra max:60
    $table->string('description', 200);     // alinhado com a regra max:200
    $table->timestamps();
    $table->softDeletes();
});
```

Observações de migração de dados:

- `description` muda de `text` para `varchar(200)`: exige `ALTER TABLE feriados_municipais ALTER COLUMN description TYPE
  varchar(200)`; valores maiores precisam ser truncados ou bloqueados pela
  migration.
- `icon` de `varchar(191)` para `varchar(60)`: idem.
- O índice em `date` elimina o *seq scan* do portal.

### 2.1 View SQL — substituta recomendada

A view `lista_feriados_municipais` pode ser eliminada em favor de uma
consulta Eloquent com paginação server-side:

```php
// (ilustrativo)
$feriados = FeriadoMunicipal::query()
    ->orderBy('date')
    ->paginate(25);
```

Se a view for mantida por compatibilidade:

```sql
-- (ilustrativo)
CREATE OR REPLACE VIEW lista_feriados_municipais AS
SELECT fm.id,
       to_char(fm.date, 'DD/MM/YYYY') AS data,
       fm.description AS descricao,
       fm.icon
FROM feriados_municipais fm
WHERE fm.deleted_at IS NULL;
```

Recomenda-se **incluir `icon`** na view: hoje o CMS não mostra qual ícone está
gravado.

## 3. Modelos

### 3.1 `FeriadoMunicipal`

```php
// (ilustrativo)
class FeriadoMunicipal extends Model
{
    use SoftDeletes, HasFactory;

    protected $table = 'feriados_municipais';

    protected $fillable = ['date', 'description', 'icon'];

    protected function casts(): array
    {
        return ['date' => 'date'];
    }
}
```

| Aspecto | Legado | v7 |
|---|---|---|
| Cast de `date` | ausente | `date` (Carbon apenas com a data) |
| `$fillable` | `date`, `description`, `icon` | idem |
| Tabela | `feriados_municipais` | idem |
| `SoftDeletes` | sim | idem |

### 3.2 Enums

Não há enum natural no legado. Candidato útil:

```php
// (ilustrativo)
enum HolidayIcon: string
{
    case Calendar = 'fas fa-calendar-day';
    case Party    = 'fas fa-party-jar';
    case Flag     = 'fas fa-flag';
    // demais colunas omitidas
}
```

Com um enum, o formulário passa a oferecer um *picker* e a validação garante
que a classe CSS existe — eliminando o item 3.7 de `05-REGRAS-E-PORTABILIDADE.md`.

## 4. Form Requests

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

    public function rules(): array
    {
        return [
            'date'        => ['required', 'date', 'after_or_equal:today'],
            'description' => ['required', 'string', 'max:200'],
            'icon'        => ['required', 'string', 'min:3', 'max:60', 'regex:/^[A-Za-z0-9 \-]+$/'],
        ];
    }

    public function messages(): array
    {
        return [
            'date.required'        => 'O campo data do feriado é obrigatório.',
            'description.required' => 'O campo Descrição do feriado é obrigatório.',
            'description.max'      => 'Descrição do feriado não deve ter mais que 200 caracteres.',
            'icon.required'        => 'O campo ícone é obrigatório.',
            'icon.min'             => 'Ícone não deve ter menos que 3 caracteres.',
            'icon.max'             => 'Ícone não deve ter mais que 60 caracteres.',
        ];
    }
}
```

```php
// (ilustrativo)
class DestroyAllFeriadosRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('delete', FeriadoMunicipal::class);
    }

    public function rules(): array
    {
        return [
            'toDelete'   => ['required', 'array', 'min:1'],
            'toDelete.*' => ['integer', 'exists:feriados_municipais,id'],
        ];
    }
}
```

O `DestroyAllFeriadosRequest` elimina a propriedade mágica `$request->toDelete`
e a falsa confirmação de sucesso.

## 5. Policies / autorização

```php
// (ilustrativo)
class FeriadoMunicipalPolicy
{
    public function viewAny(User $user): bool  { return $user->hasPermissionTo('feriados-municipais.index'); }
    public function create(User $user): bool   { return $user->hasPermissionTo('feriados-municipais.create'); }
    public function update(User $user, FeriadoMunicipal $f): bool { return $user->hasPermissionTo('feriados-municipais.edit'); }
    public function delete(User $user, FeriadoMunicipal $f): bool { return $user->hasPermissionTo('feriados-municipais.delete'); }
    public function deleteAny(User $user): bool { return $user->hasPermissionTo('feriados-municipais.delete'); }
}
```

`Gate::before()` continua liberando `super-admin`.

## 6. Endpoints REST/JSON sugeridos

### Portal (público)

| Método | URI | Descrição |
|---|---|---|
| GET | `/api/feriados-municipais?ano=2026` | Feriados do ano, agrupados por mês |

### CMS

| Método | URI | Descrição |
|---|---|---|
| GET | `/api/cms/feriados-municipais` | DataTable paginado no servidor (`ano`, `search`, `orderBy`) |
| POST | `/api/cms/feriados-municipais` | Criar |
| PUT | `/api/cms/feriados-municipais/{id}` | Atualizar |
| DELETE | `/api/cms/feriados-municipais/{id}` | Excluir (lógico) |
| POST | `/api/cms/feriados-municipais/destroy-all` | Exclusão em lote |

## 7. Recursos de API

```php
// (ilustrativo)
class FeriadoMunicipalResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'          => $this->id,
            'date'        => $this->date->toDateString(),
            'description' => $this->description,
            'icon'        => $this->icon,
        ];
    }
}
```

Para o portal, um recurso agrupado:

```php
// (ilustrativo)
class FeriadosPorMesResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return $this->resource->map(fn ($grupo, $mes) => [
            'mes'       => (int) $mes,
            'mes_extenso'=> \App\Support\Meses::nome((int) $mes),
            'feriados'  => FeriadoMunicipalResource::collection($grupo),
        ])->values();
    }
}
```

## 8. Views Blade modernas

| Legado | v7 |
|---|---|
| `portal.layouts.{modelo}.feriados-municipais.index` (6 cópias) | Um único componente `<x-portal.feriados.index>` incluído por todos os layouts |
| `portal.components.feriados-municipais.card-accordion` | `<x-portal.feriados.acordeon>` (Bootstrap 5) — elimina a duplicata Bootstrap 4/5 |
| `portal.components.feriados-municipais.accordion-item` | idem |
| `prefecture.contents.feriados-municipais.index` | `<x-admin.crud>` com colunas configuráveis |
| `prefecture.contents.feriados-municipais.create` | Página única com `mode` = create/edit |
| `prefecture.contents.feriados-municipais.form` | `<x-admin.form.finput>` |

Componente do acordeão (exemplo):

```blade
{{-- (ilustrativo) --}}
<div class="accordion" id="feriados">
    @foreach ($feriados as $mes => $lista)
        <div class="accordion-item">
            <h2 class="accordion-header">
                <button class="accordion-button" data-bs-toggle="collapse" data-bs-target="#mes-{{ $mes }}">
                    {{ \App\Support\Meses::nome((int) $mes) }}
                </button>
            </h2>
            <div id="mes-{{ $mes }}" class="accordion-collapse collapse show" data-bs-parent="#feriados">
                <div class="accordion-body">
                    @foreach ($lista as $feriado)
                        <div class="d-flex align-items-center my-3">
                            <i class="{{ $feriado->icon }}"></i>
                            <span class="ms-3">{{ $feriado->date->format('d') }}</span>
                            <span class="ms-5 texto-feriado">{{ $feriado->description }}</span>
                        </div>
                    @endforeach
                </div>
            </div>
        </div>
    @endforeach
</div>
```

## 9. Fila, cache e storage

| Aspecto | Legado | v7 |
|---|---|---|
| Cache | não há | `Cache::remember('feriados.'.now()->year, 300, fn () => $feriados)`; invalidar ao criar/editar/excluir |
| Fila | não há | não necessária |
| Storage | não há | não aplicável (sem arquivos) |
| Sessão | não há | não aplicável |

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

| Risco | Mitigação |
|---|---|
| Escolher entre "ano corrente" e "todos os anos" muda o comportamento observável | Decidir com o negócio **antes** de implementar; se mantido, manter o filtro por ano no servidor |
| Migrar `description` de `text` para `varchar(200)` pode truncar dados | Migration deve falhar ruidosamente se houver valor > 200 |
| Mudar a ordenação da listagem do CMS | Comunicar ao operador; o legado ordena por data de cadastro |
| Remover a rota `show` | Nenhum link interno usa `feriados-municipais.show`; verificar links externos antes |
| Trocar o nome das rotas CMS | Manter aliases (`Route::get('/cms/feriados-municipais')->name('painel.feriados-municipais.index')`) durante 1 release |
| Permissões `feriados-municipais.*` devem sobreviver | Manter as chaves ao criar as Policies |
| Módulo desativado por padrão (`modulos.status = false`) | Confirmar se a v7 deve manter o conceito de "módulo desligável"; se sim, migrar para `features` do Laravel |
