## Context Fase 0 entregou Laravel 13, Filament 5 em `/admin`, design tokens, autenticação interna e pipeline CI. O repositório possui apenas home placeholder em [routes/web.php](../../routes/web.php). Esta change implementa a **metade CMS** da Fase 1 (SPEC §18): persistência e gestão Filament de conteúdo, sem rotas públicas. Padrões existentes a seguir: - Resources Filament com form/table separados ([app/Filament/Resources/Users/](../../app/Filament/Resources/Users/)) - Policies admin-only ([app/Policies/UserPolicy.php](../../app/Policies/UserPolicy.php)) - `declare(strict_types=1);` em todo PHP próprio ## Goals / Non-Goals **Goals:** - Assessora admin gerencia settings, serviços, portfólio e depoimentos no Filament. - Dados persistidos conforme SPEC §8.2, prontos para consumo pela change `build-public-site`. - Feature tests locais em PostgreSQL (jsonb, constraints reais). - Seed determinístico parcial para desenvolvimento e testes futuros. **Non-Goals:** - Rotas públicas, home, SEO, sitemap, snapshots visuais, briefing. - Actions de publicação com auditoria (`PublishPortfolioCase` etc.) — adiadas até Fase 5. - Variantes responsivas de imagem, page builder, categorias dinâmicas. ## Decisions ### 1. Publicação via `published_at` único **Decisão:** `published_at` nullable timestamp é a única fonte de verdade. Scope `published()` no model (`whereNotNull('published_at')`). Sem coluna `is_published`. **Alternativa rejeitada:** boolean `is_published` + `published_at` — viola SPEC §1.1 (não persistir status derivável). ### 2. Mídia em disco `public` local, S3 em produção **Decisão:** uploads de capa/galeria/foto usam disco Laravel configurável via `FILESYSTEM_DISK`. Local: `public` + `php artisan storage:link`. Produção: `s3`. Path gerado com UUID/hash, nunca nome original. **Alternativa rejeitada:** blobs no PostgreSQL — proibido por SPEC §6.4. ### 3. Validação de mídia centralizada **Decisão:** classe em `app/Support/` (ex.: `PublicImageUploadRules`) com allowlist MIME (`image/jpeg`, `image/png`, `image/webp`), extensões correspondentes, limite 10 MB, alt text obrigatório quando imagem presente. Reutilizada nos Resources Filament. ### 4. Sem Actions de domínio nesta fase **Decisão:** Filament Resources salvam diretamente via Eloquent. Actions (`PublishPortfolioCase`, `UnpublishPortfolioCase`) só serão criadas quando auditoria (Fase 5) exigir efeito colateral transacional. **Rationale:** SPEC §9.5 — não criar Action para CRUD sem regra adicional. ### 5. Auditoria de publicação adiada **Decisão:** publicar/despublicar **não** grava `audit_logs` nesta change. **Desvio documentado:** ADM-02 lista publicação como auditável, mas tabela `audit_logs` é Fase 5. Retomar em change de hardening. ### 6. Autorização admin-only para CMS **Decisão:** Policies espelham `UserPolicy` — `$user->isAdmin()` para view/create/update/delete em conteúdo do site. Assistant recebe 403 em todos os Resources de conteúdo. **Referência:** WEB-02 — "admin pode gerenciar; assistant não pode". ### 7. Site settings como singleton Filament Page **Decisão:** model `SiteSetting` com método `instance()` (primeiro registro ou create default). Filament `ManageSiteSettings` page (não Resource de lista) — evita CRUD genérico de chave/valor (SPEC §WEB-06). ### 8. Slug automático a partir do título **Decisão:** slug gerado no model (observer ou mutator) com `Str::slug()`, unique constraint no banco. Admin pode editar slug no form. ### 9. Navegação Filament **Decisão:** grupo `Conteúdo do site` com sort order conforme SPEC §5.2: 1. Configurações (singleton page) 2. Serviços 3. Portfólio 4. Depoimentos ## Risks / Trade-offs | Risco | Mitigação | |---|---| | Uploads locais sem S3 em dev | `Storage::fake()` nos testes; documentar `storage:link` no README | | Galeria ordenada complexa no Filament | Relation manager com `sort_order` e reorderable | | jsonb `social_links` difere SQLite | phpunit.xml já migrado para PostgreSQL | | Conteúdo criado sem rotas públicas | seed + testes Filament validam CMS; rotas vêm em `build-public-site` | | Auditoria ausente em publicação | desvio explícito; retomar Fase 5 | ## Migration Plan 1. Rodar migrations em ordem: `site_settings` → `services` → `portfolio_cases` + `portfolio_images` → `testimonials`. 2. Executar seed de conteúdo após migrations. 3. Sem rollback de dados em produção (greenfield); `migrate:rollback` suportado em dev. ## Open Questions - Nenhuma bloqueante. Fontes tipográficas finais da home ficam para `build-public-site`.