# Feriados Municipais — Regras de Negócio e Portabilidade

## 1. Regras de negócio não óbvias

| # | Regra | Onde | Consequência observável |
|---|---|---|---|
| R1 | O portal publica **apenas o ano corrente** | `v6/app/Http/Controllers/Portal/FeriadosMunicipaisController.php:15` | Feriados cadastrados para 2027 não aparecem enquanto o servidor estiver em 2026; o passado também desaparece |
| R2 | A listagem do CMS ordena por `created_at DESC` (via a view SQL), **não** pela data do feriado | view `lista_feriados_municipais` | O operador não vê os feriados em ordem cronológica |
| R3 | A ordenação inicial do portal é por data **crescente** dentro do ano | `Portal\FeriadosMunicipaisController@index` | Janeiro aparece antes de dezembro |
| R4 | O agrupamento por mês é feito em PHP, com chave `"01"`..`"12"` | `Carbon::parse($val->date)->format('m')` | A ordem dos cartões é a ordem de inserção do array agrupado (chronológica, porque a query é ordenada por data) |
| R5 | A data é armazenada como `date` (sem hora) | migration da tabela | Não há como registrar dois feriados com horários diferentes no mesmo dia |
| R6 | A data é convertida de `dd/mm/aaaa` para `Y-m-d` no repositório | `FeriadosMunicipaisRepository::structureUpInsert` | O formulário e o banco usam formatos diferentes |
| R7 | `icon` guarda a **classe CSS** do ícone | model + view `card-accordion` | Um valor inválido simplesmente não renderiza ícone nenhum |
| R8 | A exclusão é lógica (`SoftDeletes`) e a view SQL filtra `deleted_at IS NULL` | model + view | Um feriado excluído some do CMS e do portal, mas a linha permanece no banco |
| R9 | `menuClicks('feriados-municipais')` roda no **construtor**, portanto em **toda** requisição ao controller | `Prefecture\FeriadosMunicipaisController::__construct` | O contador de acessos do menu infla com POST/PUT/DELETE, não só com visitas à listagem |
| R10 | A rota `show` existe mas o método é **vazio** | `Prefecture\FeriadosMunicipaisController@show` | `GET /cms/feriados-municipais/{id}` devolve 200 com corpo vazio |
| R11 | `edit($id)` renderiza a mesma view de `create` | controller | O formulário serve para os dois casos; `$data` ausente gera o formulário de cadastro na URL de edição |
| R12 | O módulo só aparece no menu do CMS se `modulos.status = true` | `v6/app/Providers/MenuServiceProvider.php:46` | O módulo nasce **desativado** (`status = 0` na migration e no banco) |
| R13 | O `icon` é obrigatório e com 3 a 60 caracteres | `FeriadosMunicipaisRequest::rules` | "fa" (2 caracteres) é recusado |
| R14 | A descrição é obrigatória e limitado a 200 caracteres | idem | Textos mais longos são recusados |
| R15 | Não há unicidade de data | schema | O mesmo feriado pode ser cadastrado duas vezes |

## 2. Acoplamentos

### 2.1 Dependências

| Depende de | Tipo | Onde |
|---|---|---|
| `feriados_municipais` | Dados | model |
| `lista_feriados_municipais` | Dados (view) | DataTable do CMS |
| `modulos` (id 51) | Configuração | menu do CMS |
| `menu_orders` (id 37) | Configuração | contador `clicks` |
| `resources` (id 70) | Permissões | `resources.modulo_id = 51` |
| `configurations.layout` | Configuração | tema do portal |
| `App\Repositories\Repository` | Código | `upInsert`, `destroyAll` |
| `App\Http\Traits\AppTrait` | Código | `filter_request`, `traitRequest` |
| `check.route.permission` | Autorização | middleware |

### 2.2 Dependência de outros módulos no módulo

Nenhuma. **Feriados Municipais é um módulo folha**: nenhum outro controller,
model ou view o utiliza. É o único módulo do conjunto que não compartilha
tabela com o resto do sistema.

## 3. Armadilhas e riscos reais

### 3.1 ALTO — `dd()` dentro do `catch` do `upInsert`

`v6/app/Repositories/Repository.php`:

```php
public function upInsert($request, int $id = null)
{
    try {
        $data = DB::transaction(function () use ($request, $id) {
            return $this->structureUpInsert($this->dataTrait($request));
        });
        return $this->getModel()->updateOrCreate(['id' => $id], $data);
    } catch (\Throwable $th) {
        dd($th);
    }
    return false;
}
```

- `dd()` despeja a exceção completa (mensagem, arquivo, linha e stack trace) e
  encerra o request com HTTP 200. Em produção, isso **vaza estrutura interna**
  ao operador.
- O `catch (\Throwable)` nunca propaga a exceção — o `try/catch` do controller
  (`catch (\Exception $exception)`) **nunca** é acionado por falhas do
  `upInsert`, porque a exceção já foi engolida.
- O `return false` após o `catch` é inalcançável na prática (o `dd()` aborta).
- **Correção na v7:** `throw` (ou `report($th)` + `return false`).

### 3.2 ALTO — `destroyAll` sempre responde sucesso

`v6/app/Repositories/Repository.php`:

```php
public function destroyAll($array, string $column = 'id'): JsonResponse
{
    try {
        $delete = DB::transaction(function () use ($array, $column) {
            return $this->getModel()->whereIn($column, $array)->delete();
        });
        if ($delete)
            return response()->json(true);
    } catch (\Exception $e) {
    }
    return response()->json(false);
}
```

- O método retorna um **`JsonResponse`**, que é um objeto — portanto
  **sempre truthy** em PHP.
- Em `Prefecture\FeriadosMunicipaisController@destroyAll`, o teste
  `if ($this->repository->destroyAll($request->toDelete))` é **sempre verdadeiro**:
  a API responde `{"success": "Os feriados foram deletados com sucesso!"}` com
  HTTP 200 **mesmo quando nada foi apagado** ou quando houve exceção.
- O `catch (\Exception $e)` também é vazio — falhas de banco viram sucesso.
- **Correção na v7:** retornar `bool` (ou o número de linhas afetadas) e
  sinalizar o erro corretamente.

### 3.3 ALTO — Propriedade mágica `$request->toDelete`

`v6/app/Http/Controllers/Prefecture/FeriadosMunicipaisController.php:151`:

```php
if ($this->repository->destroyAll($request->toDelete)) {
```

- `$request->toDelete` **não é uma propriedade declarada**; o Laravel resolve
  via `__get` → `$request->all()['toDelete']`.
- Se a chave não vier na request (requisição malformada), o valor é `null` e
  `whereIn('id', null)` compila para `in (null)`, que **não casa com nenhuma
  linha** — nada é apagado. Mesmo assim, por causa do item 3.2, a API responde
  "deletado com sucesso".
- Não há validação de que os ids são inteiros: `whereIn` com strings não
  numéricas gera erro de SQL.
- O nome `toDelete` (singular) para um array é confuso.
- **Correção na v7:** `$request->validate(['toDelete' => ['required','array'], 'toDelete.*' => ['integer','exists:feriados_municipais,id']])`.

### 3.4 ALTO — Script de exclusão em lote com caminho errado (404)

`v6/resources/views/prefecture/contents/feriados-municipais/index.blade.php`:

```blade
<script src="{{ url('assets/js/custom/Utilities/deleteAll.js') }}"></script>
```

O arquivo real é `v6/public/assets/js/custom/utilities/deleteAll.js`
(minúsculo). Em sistema de arquivos sensível a caixa (Linux, Docker), a
requisição retorna **404** e a função `submitDeleteAll` **não existe**: o botão
"Excluir Registro(s)" não faz nada.

O mesmo bug **não** ocorre no módulo do Comitê de Investimentos, que usa
`assets/js/custom/utilities/deleteAll.js` corretamente.

### 3.5 MÉDIO — Link "Voltar" aponta para o módulo errado

`v6/resources/views/prefecture/contents/feriados-municipais/create.blade.php`:

```blade
<a class="align-middle" href="{{ route('servidor.index') }}">Voltar</a>
```

O operador que edita um feriado e clica em "Voltar" cai na tela de
**Servidores**. Texto copiado do módulo Servidor.

### 3.6 MÉDIO — Mensagem de sucesso copiada do Diário Oficial

`v6/app/Http/Controllers/Prefecture/FeriadosMunicipaisController.php:129`:

```php
return response()->json(['success' => "O diário oficial deletado com sucesso!"], 200);
```

O operador recebe "O diário oficial deletado com sucesso!" ao excluir um
feriado.

### 3.7 MÉDIO — `flash()` de erro executado sempre

Em `store()` e `update()`, o `flash('Ocorreu um erro ao tentar cadastrar o
registro.')->error()` está **fora** do `try/catch`, logo é executado em
qualquer caminho que não tenha retornado antes — inclusive quando
`upInsert` retorna `false` (que, pelo `dd()`, nunca acontece de fato).

Além disso, `update()` exibe "Registro **cadastrado** com sucesso." ao
atualizar.

### 3.8 MÉDIO — `menuClicks` no construtor

`menuClicks('feriados-municipais')` é chamado no construtor do controller, que
executa em **todas** as requisições (index, create, store, edit, update,
destroy, destroyAll). O contador `menu_orders.clicks` (que o
`MenuServiceProvider` usa para ordenar o menu quando `menu_interativo = 1`)
passa a refletir o número de operações administrativas, não de visitas.

### 3.9 MÉDIO — Controller instanciando o repository manualmente

`$this->repository = new FeriadosMunicipaisRepository();` — quebra o padrão de
injeção por container usado no resto do sistema (ex.: `ContentController`
recebe `IContentRepository` no construtor) e impede mock em testes.

### 3.10 MÉDIO — Carregamento integral sem paginação

- `index()` do CMS: `$this->table->orderBy('date','ASC')->get()` — todos os
  registros, sem paginação. O valor não é usado pela view (a listagem real vem
  do DataTable), então é uma **consulta inútil** em cada visita ao index.
- Portal: `FeriadosMunicipais::whereYear(date,ano)->get()->groupBy(mes)` — todos os feriados do ano em memória.
- DataTable: `DB::table("lista_feriados_municipais")->get()` — a view inteira
  é carregada e depois paginada **no cliente** pelo DataTables.

### 3.11 BAIXO — Rota `show` inútil e nome inconsistente

- `show()` tem corpo vazio: a rota `GET /cms/feriados-municipais/{id}` responde
  200 sem conteúdo.
- O nome da rota `show` é `feriados-municipais.show` (não `painel.feriados-municipais.show`), porque
  o array `names` da chamada a `Route::resource` não cobre `show`.

### 3.12 BAIXO — `edit()` com id inexistente

`$this->table->find($id)` retorna `null` quando o id não existe; a view
recebe `$data = null` e, como o template usa `isset($data)`, renderiza o
**formulário de cadastro** na URL de edição. Salvar, nesse estado, cria um
registro novo em vez de editar.

### 3.13 BAIXO — `icon` nullable no banco, required no formulário

O banco aceita `icon = NULL`; o `FormRequest` exige `icon`. Divergência só
explorável por inserção direta (ou por um `update` mass-assignment que zere o
campo).

### 3.14 BAIXO — `status` gravado em coluna inexistente

`structureUpInsert` adiciona `'status' => null` ao payload. Como `status` não
existe na tabela `feriados_municipais` nem no `$fillable` do model, o valor é
descartado silenciosamente. Código morto.

### 3.15 BAIXO — `Request` importado e não usado no controller do portal

`use Illuminate\Http\Request;` em
`v6/app/Http/Controllers/Portal/FeriadosMunicipaisController.php` — o método
`index()` não recebe nenhum `Request`.

### 3.16 BAIXO — CSS do `modelo6` inexistente

`v6/resources/views/portal/layouts/modelo6/feriados-municipais/index.blade.php`
referencia `layouts/modelo6/css/feriados-municipais.css`, arquivo que não
existe (o diretório `v6/public/layouts/modelo6/css/` contém apenas
`style.css`).

### 3.17 BAIXO — `to_char(fm.date::timestamp with time zone)` na view

A view converte `date` para `timestamp with time zone` antes de formatar. Em
fusos horários negativos, a data pode exibir o **dia anterior** (o `date` é
interpretado à meia-noite local e reconvertido para UTC). A coluna `data` da
view é `text`, então a ordenação cronológica na listagem do CMS é textual
(luckily `DD/MM/YYYY` ordena corretamente, já que a ordenação real vem do
`created_at`).

## 4. Código duplicado ou copiado

| Trecho | Origem da cópia |
|---|---|
| Mensagem "O diário oficial deletado com sucesso!" | `Prefecture\DiarioController` |
| Link "Voltar" → `route('servidor.index')` | Formulário de `Prefecture\ServidorController` |
| Bloco `store()`/`update()` | Padrão copiado de outros CRUDs do CMS |
| `Repository::upInsert` / `destroyAll` | Base compartilhada por todos os repositories |
| `card-accordion` e `accordion-item` | Duas versões do mesmo componente (Bootstrap 4 e 5) |

## 5. Onde a v6 divergiu da v5

**Praticamente nada.** Comparação arquivo a arquivo:

| Arquivo | Diferença v5 → v6 |
|---|---|
| `app/Http/Controllers/Portal/FeriadosMunicipaisController.php` | **Idêntico** |
| `app/Http/Controllers/Prefecture/FeriadosMunicipaisController.php` | **Idêntico** |
| `app/Http/Model/Prefecture/FeriadosMunicipais.php` | **Idêntico** |
| `app/Http/Requests/FeriadosMunicipaisRequest.php` | **Idêntico** |
| `app/Repositories/Contracts/IFeriadosMunicipaisRepository.php` | **Idêntico** |
| `app/Repositories/Eloquent/Prefecture/FeriadosMunicipais/FeriadosMunicipaisRepository.php` | **Idêntico** |
| `database/migrations/2023_05_29_144236_create_view_lista_de_feriados_municipais.php` | **Idêntico** |
| `database/migrations/2023_02_02_094344_create_feriados_municipais_table.php` | **Única diferença**: a v5 semeava `resources`, `menu_orders`, `links` e `menu_items`; a v6 removeu esse bloco |
| `resources/views/prefecture/contents/feriados-municipais/*` | **Idêntico** |
| `resources/views/portal/components/feriados-municipais/*` | **Idêntico** |
| `resources/views/portal/layouts/modelo1/feriados-municipais/index.blade.php` | Diferença de **classes CSS** da coluna lateral (`pl-0` → `pr-0 pl-0`) |
| `resources/views/portal/layouts/modelo3/feriados-municipais/index.blade.php` | Diferença de **classes CSS** da coluna lateral (`ml-auto` → `ml-auto pr-0 pl-0`) e remoção de `pl-20` |
| `resources/views/portal/layouts/modelo2,4,5,6/feriados-municipais/index.blade.php` | **Idêntico** |
| `routes/web.php` | Mesma rota, apenas mudou o número da linha (183 → 205) |
| `routes/basepro.php` | Mesmas rotas (307-315 → 335-343) e mesmo DataTable (467 → 454) |

Conclusão: o módulo foi **copiado sem alteração** da v5 para a v6. A única
mudança funcional é a **remoção do seed legacy** da migration (a v6 passou a
usar a tabela `modulos`).

## 6. Dependências de terceiros

| Pacote | Uso |
|---|---|
| `laravel/framework` | Core |
| `nesbot/carbon` | `Carbon::parse($val->date)->format('m')` |
| `yajra/laravel-datatables-oracle` | DataTable do CMS |
| `cartalyst/sentinel` | Autenticação e permissões |
| `sweetalert2` | Confirmações de exclusão (carregado pelo layout) |
| `bootstrap` 4 e 5 | Acordeões do portal |
| `datetimepicker` / `jQuery Mask` | Campos de data no CMS |
