# Menus do Site — Migracao Moderna (parte 2): Policies, API, Cache e Riscos

> Complemento de `06-MIGRACAO-MODERNA.md` (schema, modelos, resolucao de URL e Form
> Requests). Numero das secoes continua a contagem do arquivo anterior.
>
> Todos os blocos de codigo deste arquivo sao **ilustrativos** (proposta para a
> v7); nao existe codigo equivalente no repositorio legado.

---

## 5. Policies

```php
class MenuPolicy
{
    public function viewAny(User $user): bool   { return $user->hasPermissionTo('menu.index'); }
    public function create(User $user): bool    { return $user->hasPermissionTo('menu.create'); }
    public function update(User $user): bool    { return $user->hasPermissionTo('menu.edit'); }
    public function delete(User $user): bool    { return $user->hasPermissionTo('menu.delete'); }
    public function toggle(User $user): bool    { return $user->hasPermissionTo('menu.edit'); }
}
```

O legado **nao** aplica `menu.edit`/`menu.delete` de fato (as rotas `store`,
`update` e `destroy` sao tratadas como abertas pelo
`v6/app/Http/Middleware/CheckRoutePermission.php`). A v7 **deve** corrigir isso —
e a correcao e segura, porque o CMS ja esconde os botoes quando a permissao
falta (via `has_access`).

O mesmo vale para `POST /menu-item/{id}/toggle`: na v7 deve ficar no CMS
(`/cms/menu-item/{id}/toggle`), sob `auth` + `can:toggle`, e nao no arquivo de
rotas publicas.

---

## 6. Endpoints REST/JSON

### 6.1 CMS (HTML + JSON hibrido, como o legado)

| Metodo | URI | Descricao |
|---|---|---|
| GET | `/cms/menu` | listagem (DataTable) |
| GET | `/cms/menu/{menu}/items` | arvore de itens do menu (JSON, para o *tree component*) |
| POST | `/cms/menu/{menu}/items` | cria item |
| PUT | `/cms/menu-item/{item}` | atualiza item |
| DELETE | `/cms/menu-item/{item}` | exclui item (409 se tiver filhos) |
| POST | `/cms/menu-item/{item}/toggle` | ativa/desativa |
| POST | `/cms/menu/{menu}/reorder` | persiste a ordem (payload: lista de ids) |

### 6.2 API publica (opcional)

| Metodo | URI | Descricao |
|---|---|---|
| GET | `/api/v1/menus/{slug}` | arvore de itens de um menu, ja resolvida (URLs absolutas) |
| GET | `/api/v1/menus/{slug}/items` | paginado |

### 6.3 API Resources

```php
class MenuItemResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'second_name' => $this->second_name,
            'icon' => $this->icon,
            'type' => $this->type->value,
            'url' => app(MenuLinkResolver::class)->resolve($this->resource),
            'target' => $this->targetLink(),
            'is_active' => $this->is_active,
            'is_mega_menu' => $this->is_mega_menu,
            'children' => MenuItemResource::collection($this->whenLoaded('children')),
        ];
    }
}
```

---

## 7. Views Blade modernas

| Legado | v7 |
|---|---|
| `prefecture/menus/index.blade.php` + `datatable.js` | `<x-admin.datatable>` reutilizavel, com `MenuController@index` retornando `MenuResource` paginado (sem view SQL) |
| `prefecture/menus-itens/show.blade.php` (604 linhas, 3 `foreach` aninhados) | Componente `<x-menu.tree :items="$tree" />` que renderiza **recursivamente** (PHP ou Alpine.js) a partir de uma unica collection ja carregada |
| Formulario com radio que injeta `<select>` via JS | Alpine.js `x-model="type"` com `x-show`, sem `innerHTML` |
| `form.blade.php` com `Form::` e `old()` | Componente `<x-form.input>` com `wire:model` (Livewire) ou `x-model` |
| `@php` e `@inject` dentro do Blade | Nada: a arvore chega pronta do controller |
| Sortable jQuery UI | Alpine.js `x-sort` ou SortableJS com `wire:sort` |
| Tooltip com URL reimplementada em Blade | Reutilizar o `MenuLinkResolver` no controller e expor `url` no JSON |

Estrutura de view sugerida:

```
resources/views/admin/menus/
├── index.blade.php
├── partials/tree.blade.php        (recursivo)
└── partials/item-form.blade.php
```

---

## 8. Carregamento da arvore (eliminar N+1)

```php
// app/Services/MenuTreeBuilder.php
final class MenuTreeBuilder
{
    public function forMenu(Menu $menu): Collection
    {
        $items = MenuItem::query()
            ->where('menus_id', $menu->id)
            ->with([
                'link', 'staticPage', 'category', 'content',
                'children' => fn ($q) => $q->with(['link','staticPage','category','content','children']),
            ])
            ->orderBy('order')
            ->get();

        return $this->toTree($items);
    }

    private function toTree(Collection $items): Collection
    {
        $byParent = $items->groupBy(fn (MenuItem $i) => $i->menus_items_id);
        $roots = $byParent->get(null, collect());

        return $roots->map(function (MenuItem $item) use ($byParent) {
            $item->setRelation('children', $this->toTree($byParent->get($item->id, collect())));
            return $item;
        })->values();
    }
}
```

Equivalencia a preservar: o portal renderiza **no maximo 3 niveis** (raiz + 2
filhos). A arvore pode ter mais profundidade no banco (o legado permite), mas
esses niveis mais profundos nunca aparecem. A v7 deve **reproduzir** esse limite
(ou, de preferencia, expor a profundidade como configuracao com o mesmo default).

---

## 9. Cache

O legado nao cacheia nada: cada requisicao do portal roda 4 queries de menu.
Opcoes para a v7:

| Estrategia | Ganho | Risco |
|---|---|---|
| `Cache::tags(['menus'])->remember('menu.'.$id, 60, fn () => $service->tree($id))` | Grande (todo o portal) | Invalidação precisa em cada CRUD de item/menu; risco de menu desatualizado |
| `Model::preventLazyLoading()` + eager loading correto | Correto sem cache | — |
| Cache por request (`scoped`) | Nenhum | — |

Recomendacao: comecar **sem cache** (garantindo eager loading correto) e
introduzir cache por tag so se a medicao justificar. Se houver cache, a
invalidação deve ocorrer em `MenuItem::saved`, `MenuItem::deleted`,
`Menu::saved` e `Menu::deleted`.

---

## 10. Fila, storage e eventos

- **Fila**: nao necessaria. Nenhuma operacao do modulo e demorada.
- **Storage**: nao involved (o modulo nao manipula arquivos).
- **Eventos** recomendados para integracao:
  - `MenuItemMoved` (posicao alterada);
  - `MenuItemToggled` (visibilidade alterada);
  - `MenuItemDeleted` (limpar `links` orfaos criados pelo repository).

---

## 11. Riscos e decisoes de equivalencia funcional

| # | Decisao | Justificativa |
|---|---|---|
| E1 | **O limite de 6 itens raiz por menu se mantem** | E uma regra visivel ao operador (mensagem de erro). Alterar muda o comportamento do cadastro |
| E2 | **`is_active = false` oculta sem excluir** | Mecanismo central de "publicar sem tirar do ar". O legado tambem nao oferece restauracao de item |
| E3 | **`order` continua global por menu** (nao por submenu) | Mudar para ordem por submenu altera a ordem exibida |
| E4 | **O destino continua sendo decidido pelo `type`** + colunas de FK | Consumidores externos (sitemap, layout editor) leem essas colunas |
| E5 | **Os 3 niveis de exibicao se mantem** | O portal mostra raiz + 2 subniveis |
| E6 | **`is_mega_menu` continua sendo uma flag do item raiz** | O layout do portal (modelo1 e modelo5) depende do `javascript:;` e do `mega-dropdown-menu` |
| E7 | **A regra "nao excluir item com filhos" se mantem** (retornando 409) | Evita orfaos e mantem a rastreabilidade |
| E8 | **O menu "Atalhos" continua sem interruptor de ativacao** | Decisao de UX do legado; se mudar, avaliar o impacto nos atalhos do topo |
| E9 | **A ordem dos itens no CMS e a ordem no portal sao a mesma coluna** | Nao ha no legado uma nocao de "ordem de edicao" separada da "ordem de exibicao" |
| E10 | **O nome dos atalhos ("Telefones", "Ouvidoria", "Contato", "Acessibilidade") vira configuracao** | Sao magic strings no legado; configurar preserva o comportamento e remove a fragilidade |
| E11 | **O `slug` continua sendo derivado do `name`** | Consumidores externos podem referenciar o slug do menu; mas deve ser **unico e estavel** (na v7, gerar no `saved` e bloquear a alteracao automatica, ou versionar) |
| E12 | **`clicks` continua sendo incremented no acesso ao modulo** | O `MenuServiceProvider` ordena o menu lateral por `clicks` quando `menu_interativo` esta ativo |
| E13 | **O menu lateral do CMS so mostra modulos com `modulo_id` + `status = true`** | Comportamento de `modulos`/`menu_orders` — ver dossie do modulo de modulos |

---

## 12. Checklist de migracao

1. Criar as tabelas `menus`, `menu_items`, `menu_orders` com os indices propostos.
2. Migrar dados de `menus`, `menu_items` (com `order` saneado: normalizar de 1..N por menu) e `menu_orders`.
3. Migrar `layouts.secretarias-grid` (formato `menus:{id}`) — ja e o formato da v6.
4. Recriar a view `menus_site` **ou** eliminar a view em favor do `MenuResource`.
5. Implementar `MenuLinkResolver` com a tabela de slugs de categoria configuravel.
6. Implementar `MenuTreeBuilder` e os *view composers* (`menuHeader`, `menuRapido`, `atalhos`, `menus` do rodape).
7. Migrar os templates do portal dos 6 modelos para o componente unico de arvore.
8. Migrar as telas do CMS (listagem com paginacao server-side; arvore com reordenacao por drag-and-drop).
9. Criar `MenuPolicy` e remover as rotas publicas de toggle.
10. Escrever os testes de `07-TESTES.md` **antes** de desligar o legado.
