Compare commits

...

5 Commits

Author SHA1 Message Date
900b76ad19 merge: integrar main (MAN-139) na branch de modalidades
Some checks failed
CI / static (pull_request) Successful in 2m1s
CI / unit (pull_request) Successful in 4m1s
CI / feature (pull_request) Successful in 3m1s
CI / container (pull_request) Successful in 55s
CI / browser (pull_request) Failing after 2m13s
Resolve conflito em PackageContactLink: mantém forOrientation do main e a mensagem de orientação da Ata.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 23:43:16 -03:00
9ee9b51b0c feat: CTA de orientação das modalidades via WhatsApp
Some checks failed
CI / static (pull_request) Successful in 2m55s
CI / unit (pull_request) Successful in 3m27s
CI / feature (pull_request) Successful in 2m57s
CI / container (pull_request) Successful in 59s
CI / browser (pull_request) Failing after 2m11s
A banda "ainda não sabe" abre wa.me com mensagem de orientação; sem número, cai no briefing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 23:39:29 -03:00
220143390e feat: alinhar copy das modalidades à Apresentação
Cards e páginas /pacotes passam a usar o texto conceitual oficial, sem preço, VIP ou limites comerciais.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 23:39:29 -03:00
fd3a4eb5f1 feat: CTA orientação WhatsApp para indecisos (MAN-127) (#7)
All checks were successful
CI / static (push) Successful in 2m33s
CI / unit (push) Successful in 3m39s
CI / feature (push) Successful in 2m49s
CI / container (push) Successful in 20s
CI / browser (push) Successful in 4m9s
## Summary

- CTA orientação (banda “Ainda não sabe?”) na home e em `/servicos` abre WhatsApp com mensagem distinta; sem número oficial, cai em `/briefing`.
- Glossário canônico em `SPEC.md` §8.0 (Vertente, Modalidade, Briefing, Contato parceiro, CTA contextual, CTA orientação). Sem `CONTEXT.md` paralelo.
- Linear: https://linear.app/maneco-workspace/issue/MAN-127/ajustar-ctas-consultivos-no-whatsapp-e-briefing-comercial

## Test plan

- [x] Feature: `HomePageContentTest` + `ServicesPageCtaTest` (17 passed)
- [ ] CI Gitea (static, unit, feature, browser, container)
- [ ] Conferir banda de modalidades com e sem `whatsapp_number` no Filament

Reviewed-on: #7
Co-authored-by: manoel freitas <manoel.josefneto@gmail.com>
Co-committed-by: manoel freitas <manoel.josefneto@gmail.com>
2026-08-14 02:35:06 +00:00
3207b7e568 docs: registrar Modalidade vs Service no modelo de domínio
O visitante vê modalidades de casamento; services permanece vertente. Preço e limites ficam fora do registro público.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 23:30:58 -03:00
12 changed files with 272 additions and 135 deletions

View File

@@ -79,4 +79,4 @@ Default vocabulary: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-
### Domain docs ### Domain docs
Single-context repo. There is no `CONTEXT.md` — the domain is documented in `SPEC.md` (§8 is the domain model and database schema) and `PRODUCT.md`, with current capabilities described per-capability under `openspec/specs/`. `docs/adr/README.md` is an index only: ADR-001 through ADR-010 are decided in `SPEC.md` §21, and there are no standalone ADR files. `docs/agents/domain.md` describes the generic `CONTEXT.md`/`CONTEXT-MAP.md` layout that the engineering skills look for and instructs them to proceed silently when it's absent, which is the case here. Single-context repo. There is no `CONTEXT.md` on purpose — **`SPEC.md` §8.0 is the ubiquitous-language SSOT** (domain model/schema continue in §8.1+). `PRODUCT.md` covers positioning; capabilities live under `openspec/specs/`. `docs/adr/README.md` is an index only: ADR-001 through ADR-010 are decided in `SPEC.md` §21. `docs/agents/domain.md` tells skills to use `SPEC.md` §8 instead of inventing a parallel `CONTEXT.md`.

View File

@@ -67,7 +67,7 @@ Site-wide content is a singleton row reached via `SiteSetting::instance()`. Publ
- **Livewire/Filament temp uploads are pinned to the `local` disk** when `FILESYSTEM_DISK=r2`, because the S3 driver would make the browser PUT straight to R2 and hit CORS. Final media still lands on `r2` via `App\Support\PublicImageUploadRules`. Set `LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK` explicitly to override. - **Livewire/Filament temp uploads are pinned to the `local` disk** when `FILESYSTEM_DISK=r2`, because the S3 driver would make the browser PUT straight to R2 and hit CORS. Final media still lands on `r2` via `App\Support\PublicImageUploadRules`. Set `LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK` explicitly to override.
- **Contact form is rate limited**: named limiter `contact-briefing`, 5/min per IP, registered in `AppServiceProvider` and applied in `routes/web.php`. - **Contact form is rate limited**: named limiter `contact-briefing`, 5/min per IP, registered in `AppServiceProvider` and applied in `routes/web.php`.
- **Filament 5 nested resource layout**: resources are split into `app/Filament/Resources/<Resource>/{Pages,Schemas,Tables,RelationManagers}` rather than a flat resource class. Follow the existing shape in `Resources/PortfolioCases/`. - **Filament 5 nested resource layout**: resources are split into `app/Filament/Resources/<Resource>/{Pages,Schemas,Tables,RelationManagers}` rather than a flat resource class. Follow the existing shape in `Resources/PortfolioCases/`.
- **Everything user-facing is pt-BR**: routes are `/servicos`, `/portfolio`, `/portfolio/{slug}`, `/sobre`, `/privacidade`, `/contato`. `APP_LOCALE=pt_BR`, `APP_TIMEZONE=America/Sao_Paulo` (`config/app.php:68`). - **Everything user-facing is pt-BR**: routes are `/servicos`, `/pacotes/{slug}`, `/portfolio`, `/portfolio/{slug}`, `/sobre`, `/briefing`, `/contato`, `/privacidade`. `APP_LOCALE=pt_BR`, `APP_TIMEZONE=America/Sao_Paulo` (`config/app.php:68`). Glossário canônico: `SPEC.md` §8.0.
- **Design tokens** live in `resources/css/tokens.css` (Heritage Editorial; see `DESIGN.md`). `tests/Feature/PublicSite/HeritageEditorialTokensTest.php` reads that file and asserts the exact hex values, `EB Garamond`, zero border radii, `--amare-container-max: 1120px`, and the *absence* of shadow tokens — so any token edit is a deliberate test change too. Motion lives in `resources/js/motion.js` and is asserted by `tests/Feature/PublicSite/MotionMarkupTest.php` + `tests/Browser/MotionTest.php`. - **Design tokens** live in `resources/css/tokens.css` (Heritage Editorial; see `DESIGN.md`). `tests/Feature/PublicSite/HeritageEditorialTokensTest.php` reads that file and asserts the exact hex values, `EB Garamond`, zero border radii, `--amare-container-max: 1120px`, and the *absence* of shadow tokens — so any token edit is a deliberate test change too. Motion lives in `resources/js/motion.js` and is asserted by `tests/Feature/PublicSite/MotionMarkupTest.php` + `tests/Browser/MotionTest.php`.
## Navigating the normative docs ## Navigating the normative docs

View File

@@ -37,7 +37,7 @@ No lançamento, o site público premium e o CMS interno formam a superfície ent
## Operating Context ## Operating Context
- Atuação em São Paulo, capital. - Atuação em São Paulo, capital.
- Serviços: casamentos, eventos corporativos e celebrações. - Serviços: casamentos, eventos corporativos e celebrações. No site, casamento aparece como três **modalidades** (Essenza, Conduzione, Grand Jour); preço e limites comerciais ficam na proposta, não na página.
- Operação realizada por assessora principal e equipe pequena. - Operação realizada por assessora principal e equipe pequena.
- Jornada de lançamento: descoberta no site, briefing por e-mail e contato comercial fora da plataforma. - Jornada de lançamento: descoberta no site, briefing por e-mail e contato comercial fora da plataforma.
- Jornada futura prevista: qualificação do lead, pipeline comercial, conversão em evento, checklist, fornecedores, orçamento, pagamentos manuais e documentos. - Jornada futura prevista: qualificação do lead, pipeline comercial, conversão em evento, checklist, fornecedores, orçamento, pagamentos manuais e documentos.

69
SPEC.md
View File

@@ -283,11 +283,15 @@ Uma funcionalidade fora do MVP só poderá entrar quando:
| Método | Rota | Nome sugerido | Finalidade | | Método | Rota | Nome sugerido | Finalidade |
|---|---|---|---| |---|---|---|---|
| GET | `/` | `home` | Home editorial | | GET | `/` | `home` | Home editorial |
| GET | `/servicos` | `services.index` | Lista de serviços publicados | | GET | `/servicos` | `services.index` | Lista de serviços / modalidades publicadas |
| GET | `/pacotes/{slug}` | `packages.show` | Detalhe de uma modalidade de casamento |
| GET | `/portfolio` | `portfolio.index` | Lista de casos publicados | | GET | `/portfolio` | `portfolio.index` | Lista de casos publicados |
| GET | `/portfolio/{slug}` | `portfolio.show` | Detalhe de caso | | GET | `/portfolio/{slug}` | `portfolio.show` | Detalhe de caso |
| GET | `/sobre` | `about` | História, método e credenciais | | GET | `/sobre` | `about` | História, método e credenciais |
| GET | `/contato` | `contact` | Briefing de contato | | GET | `/briefing` | `briefing` | Briefing comercial (pedido de proposta) |
| POST | `/briefing` | `briefing.store` | Envio do briefing comercial |
| GET | `/contato` | `contact` | Contato de parceiro / fornecedor (não é briefing) |
| POST | `/contato` | `contact.store` | Envio da consulta de parceiro |
| GET | `/privacidade` | `privacy` | Política de privacidade | | GET | `/privacidade` | `privacy` | Política de privacidade |
| GET | `/sitemap.xml` | `sitemap` | Sitemap público | | GET | `/sitemap.xml` | `sitemap` | Sitemap público |
| GET | `/robots.txt` | `robots` | Política de crawling | | GET | `/robots.txt` | `robots` | Política de crawling |
@@ -324,7 +328,8 @@ Administração
- Dashboard: `Filament Page` customizada com widgets orientados a exceção. - Dashboard: `Filament Page` customizada com widgets orientados a exceção.
- Detalhe do evento: página customizada do Resource com resumo operacional. - Detalhe do evento: página customizada do Resource com resumo operacional.
- Briefing público: Blade + Controller (`POST /contato`), ver §11.2. - Briefing comercial: Blade + Controller (`POST /briefing`), ver §11.2.
- Contato de parceiro: Blade + Controller (`POST /contato`) — canal separado do briefing.
- Home: Blade com componentes de design reutilizáveis. - Home: Blade com componentes de design reutilizáveis.
### 5.4 Decisões de UX YAGNI ### 5.4 Decisões de UX YAGNI
@@ -1243,6 +1248,35 @@ Auditoria não precisa ser um event sourcing. Registrar apenas operações crít
## 8. Modelo de domínio e banco de dados ## 8. Modelo de domínio e banco de dados
Esta seção é a **fonte única de verdade** do vocabulário de domínio do produto. Issues Linear, OpenSpec, código e copy devem usar estes termos. Não criar glossário paralelo em `CONTEXT.md`.
### 8.0 Linguagem ubíqua (glossário)
**Vertente**:
Frente de marca da Amare — **Casamentos** ou **Corporate**.
_Avoid_: linha de negócio, vertical de produto (em copy pública), “área”
**Modalidade**:
Pacote/formato de acompanhamento de casamento gerenciado no CMS (`WeddingPackage`: Essenza, Conduzione, Grand Jour).
_Avoid_: pacote (em copy pública preferir “modalidade”), serviço genérico, plano
**Briefing**:
Pedido comercial de um potencial cliente descrevendo o evento e solicitando atendimento/proposta. Rota canônica: `/briefing`.
_Avoid_: contato (quando o sentido é pedido comercial), formulário de parceiro
**Contato parceiro**:
Consulta de fornecedor ou proposta de parceria. Rota canônica: `/contato`. Não mistura com Briefing.
_Avoid_: briefing, lead comercial
**CTA contextual**:
Link de conversão de uma Modalidade escolhida — WhatsApp com mensagem que nomeia a modalidade, ou fallback para `/briefing?servico_interesse=` quando não há número oficial válido.
**CTA orientação**:
Link de conversão para visitante que ainda não escolheu Modalidade — WhatsApp com mensagem de orientação humana (sem quiz), ou fallback para `/briefing` quando não há número oficial válido.
**Boutique**:
Conceito de posicionamento (cuidado, especialização, múltiplos serviços sob a mesma marca) — não é entidade de domínio nem rota.
### 8.1 Módulos ### 8.1 Módulos
| Módulo | Responsabilidade | | Módulo | Responsabilidade |
@@ -1254,6 +1288,12 @@ Auditoria não precisa ser um event sourcing. Registrar apenas operações crít
| Finance | Orçamento e pagamentos manuais | | Finance | Orçamento e pagamentos manuais |
| Identity | Usuários, acesso e auditoria | | Identity | Usuários, acesso e auditoria |
Vocabulário público vs persistência (Marketing):
- **Modalidade** é o nome público de cada oferta de acompanhamento de casamento. O visitante vê o nome próprio (Essenza, Conduzione, Grand Jour) e a tag de momento (Completa, Parcial, Final). Persistida em `wedding_packages`.
- **Vertente** é o contexto da marca (Casamentos vs Corporate). A tabela `services` descreve vertentes do catálogo, não as três modalidades.
- **Pacote**, no sentido de SKU com preço e limites operacionais, não é superfície pública. Preço, equipe no dia, teto de fornecedores, quilometragem e ferramentas de terceiros (incluindo gestão de convidados) ficam na proposta, não no registro público da modalidade.
### 8.2 Tabelas obrigatórias ### 8.2 Tabelas obrigatórias
#### `users` #### `users`
@@ -1314,6 +1354,27 @@ Singleton:
- timestamps; - timestamps;
- soft delete opcional somente se recuperação for necessária. - soft delete opcional somente se recuperação for necessária.
#### `wedding_packages`
Modalidades públicas de casamento (não confundir com `services`):
- `id`;
- `name`;
- `slug` unique;
- `level` (rótulo de momento, ex. `01 / COMPLETA`);
- `tag` nullable (ex. Assessoria completa);
- `subtitle` nullable;
- `summary`;
- `scope_items` jsonb (bullets de conceito nos cards);
- `cta_label`;
- `compare_heading` nullable;
- `compare_summary` nullable;
- campos editoriais da página `/pacotes/{slug}` (eyebrow, título, hero, benefícios, incluso em alto nível, audiência, CTA final, meta) — todos opcionais salvo o que a publicação exigir;
- `sort_order` integer indexed;
- `published_at` timestamp nullable indexed;
- timestamps;
- **sem** colunas de preço, centavos, equipe, limites de fornecedores/convidados/km ou ferramentas de terceiros.
#### `portfolio_cases` #### `portfolio_cases`
- `id`; - `id`;
@@ -1795,7 +1856,7 @@ Não transformar seções estáticas em componentes Livewire. Usar Blade quando
### 11.2 Formulário de briefing ### 11.2 Formulário de briefing
> **Estado atual:** o formulário é implementado em Blade + Controller (`POST /contato`, `ContactBriefingRequest`), conforme WEB-05, e essa é a abordagem aceita — não um estágio provisório. Os requisitos abaixo valem independentemente da tecnologia; a criação de Lead segue para a Fase 2. > **Estado atual:** o formulário de **Briefing** é Blade + Controller (`GET/POST /briefing`, `ContactBriefingRequest`), conforme WEB-05. A rota `/contato` é o canal de **Contato parceiro** (consulta não persistente), separado do briefing. A criação de Lead comercial persistente segue para a Fase 2.
O formulário deve: O formulário deve:

View File

@@ -8,20 +8,45 @@ use App\Models\SiteSetting;
final class PackageContactLink final class PackageContactLink
{ {
public const ORIENTATION_MESSAGE = 'Olá, ainda não sei qual modalidade de assessoria faz sentido para o meu casamento e gostaria de orientação.';
/** /**
* Resolve the contextual contact link for a wedding package modality. * Resolve the contextual contact link for a wedding package modality.
* *
* @return array{href: string, isWhatsapp: bool} * @return array{href: string, isWhatsapp: bool}
*/ */
public static function for(SiteSetting $settings, string $packageName): array public static function for(SiteSetting $settings, string $packageName): array
{
return self::resolve(
$settings,
'Olá, gostaria de conversar sobre a modalidade '.$packageName.' para meu casamento.',
['servico_interesse' => $packageName],
);
}
/**
* WhatsApp (or briefing fallback) when the visitor does not know which modality to choose.
*
* @return array{href: string, isWhatsapp: bool}
*/
public static function forOrientation(SiteSetting $settings): array
{
return self::resolve($settings, self::ORIENTATION_MESSAGE, []);
}
/**
* @param array<string, string> $briefingQuery
* @return array{href: string, isWhatsapp: bool}
*/
private static function resolve(SiteSetting $settings, string $whatsappMessage, array $briefingQuery): array
{ {
$digits = preg_replace('/\D/', '', (string) $settings->whatsapp_number); $digits = preg_replace('/\D/', '', (string) $settings->whatsapp_number);
$isWhatsapp = strlen($digits) >= 10; $isWhatsapp = strlen($digits) >= 10;
return [ return [
'href' => $isWhatsapp 'href' => $isWhatsapp
? 'https://wa.me/'.$digits.'?text='.rawurlencode('Olá, gostaria de conversar sobre a modalidade '.$packageName.' para meu casamento.') ? 'https://wa.me/'.$digits.'?text='.rawurlencode($whatsappMessage)
: route('briefing', ['servico_interesse' => $packageName]), : route('briefing', $briefingQuery),
'isWhatsapp' => $isWhatsapp, 'isWhatsapp' => $isWhatsapp,
]; ];
} }

View File

@@ -9,11 +9,10 @@ use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
/** /**
* Wedding package modalities shown on the home page (03 Amare Casamentos) * Wedding package modalities shown on the home page, services page and
* and the services page. Copy is approved from the structural model preview * `/pacotes/{slug}`. Public copy follows "Apresentação dos serviços Amare"
* (preview(2).html) and remains editable via the Filament resource. Detail * (conceito e momento). Commercial limits, price and third-party tools stay
* copy follows the complete-assessoria preview * off this seeder and off the public site.
* (amare-assessoria-completa.html) adapted to each modality.
*/ */
class WeddingPackagesSeeder extends Seeder class WeddingPackagesSeeder extends Seeder
{ {
@@ -27,141 +26,147 @@ class WeddingPackagesSeeder extends Seeder
'slug' => 'essenza', 'slug' => 'essenza',
'level' => '01 / COMPLETA', 'level' => '01 / COMPLETA',
'tag' => 'Assessoria completa', 'tag' => 'Assessoria completa',
'subtitle' => 'Do planejamento ao grande dia.', 'subtitle' => 'Do primeiro planejamento ao grande dia.',
'summary' => 'Para casais que desejam contar com a Amare desde o início ou ainda estão estruturando etapas importantes.', 'summary' => 'Ideal para casais que desejam contar com a Amare desde o início da organização ou que ainda estão estruturando etapas importantes do casamento.',
'scope_items' => [ 'scope_items' => [
'Estruturação do planejamento', 'Estruturação e acompanhamento do planejamento',
'Cronogramas, prazos e prioridades', 'Organização de cronogramas, prazos e prioridades',
'Orientação e gestão de fornecedores', 'Orientação na contratação e gestão de fornecedores',
'Alinhamento entre os envolvidos', 'Acompanhamento das etapas do projeto',
'Coordenação da execução', 'Organização e conferência de contratos e informações',
'Alinhamento entre casal, fornecedores e demais envolvidos',
'Construção do cronograma do grande dia',
'Coordenação e acompanhamento da execução do evento',
], ],
'cta_label' => 'Quero conhecer a Essenza', 'cta_label' => 'Quero conhecer a Essenza',
'compare_heading' => 'Começar com a Amare', 'compare_heading' => 'Começar com a Amare',
'compare_summary' => 'Acompanhamento mais amplo ao longo do planejamento.', 'compare_summary' => 'Acompanhamento do primeiro planejamento ao grande dia.',
'sort_order' => 1, 'sort_order' => 1,
'eyebrow' => 'Assessoria', 'eyebrow' => 'Assessoria',
'title_line' => 'Assessoria', 'title_line' => 'Assessoria',
'title_emphasis' => 'Completa', 'title_emphasis' => 'Completa',
'hero_lead' => 'Do planejamento ao grande dia, com calma, precisão e cuidado.', 'hero_lead' => 'A assessoria completa acompanha o projeto de forma estratégica e próxima, desde a construção do planejamento até a execução do evento.',
'benefits' => [ 'benefits' => [
['icon_key' => 'calendar', 'label' => 'Organização integral'], ['icon_key' => 'calendar', 'label' => 'Planejamento organizado'],
['icon_key' => 'checklist', 'label' => 'Planejamento personalizado'], ['icon_key' => 'checklist', 'label' => 'Decisões mais seguras'],
['icon_key' => 'users', 'label' => 'Fornecedores selecionados'], ['icon_key' => 'users', 'label' => 'Fornecedores alinhados'],
['icon_key' => 'heart', 'label' => 'Experiência leve e inesquecível'], ['icon_key' => 'heart', 'label' => 'Grande dia com leveza'],
], ],
'included_items' => [ 'included_items' => [
['icon_key' => 'heart', 'title' => 'Escuta e briefing', 'description' => 'Cada casal é único; começamos por ouvir.'], ['icon_key' => 'checklist', 'title' => 'Estruturação do planejamento', 'description' => 'Organização do projeto desde o início, no ritmo do casal.'],
['icon_key' => 'checklist', 'title' => 'Planejamento completo', 'description' => 'Cada etapa mapeada com clareza.'], ['icon_key' => 'calendar', 'title' => 'Cronogramas e prioridades', 'description' => 'Prazos e etapas visíveis para decidir com calma.'],
['icon_key' => 'map', 'title' => 'Gestão de fornecedores', 'description' => 'Contatos e prazos sob cuidado.'], ['icon_key' => 'map', 'title' => 'Contratação e gestão de fornecedores', 'description' => 'Orientação e acompanhamento de quem entra no projeto.'],
['icon_key' => 'calendar', 'title' => 'Acompanhamento contínuo', 'description' => 'Perto do casal em cada decisão.'], ['icon_key' => 'users', 'title' => 'Alinhamento entre envolvidos', 'description' => 'Casal, fornecedores e demais partes na mesma conversa.'],
['icon_key' => 'spark', 'title' => 'Produção e logística', 'description' => 'Dia a dia do evento organizado nos detalhes.'], ['icon_key' => 'spark', 'title' => 'Contratos e informações', 'description' => 'Conferência do que foi combinado antes do grande dia.'],
['icon_key' => 'users', 'title' => 'Dia do evento', 'description' => 'Coordenação total para o casal viver o momento.'], ['icon_key' => 'heart', 'title' => 'Coordenação da execução', 'description' => 'Acompanhamento do evento para o casal viver o momento.'],
], ],
'audience_heading' => 'Para quem é este pacote', 'audience_heading' => 'Para quem é esta modalidade',
'audience_intro' => 'Para quem quer uma assessoria completa, do início ao fim.', 'audience_intro' => 'Para quem deseja ter ao lado uma profissional conduzindo o processo como um todo, com organização, estratégia e tranquilidade.',
'audience_points' => [ 'audience_points' => [
'Casamentos', 'Casais no início da organização',
'Aniversários', 'Quem ainda está estruturando etapas importantes',
'Eventos sociais e celebrações', 'Quem quer acompanhamento do planejamento à execução',
'Eventos corporativos',
], ],
'final_cta_heading' => 'Pronta para começar?', 'final_cta_heading' => 'Vamos nos conhecer?',
'final_cta_body' => 'Conte seu evento para a Amare e receba uma proposta.', 'final_cta_body' => 'Antes de definir qualquer caminho, queremos conhecer vocês. Basta entrar em contato para uma conversa.',
], ],
[ [
'name' => 'Conduzione', 'name' => 'Conduzione',
'slug' => 'conduzione', 'slug' => 'conduzione',
'level' => '02 / PARCIAL', 'level' => '02 / PARCIAL',
'tag' => 'Assessoria parcial', 'tag' => 'Assessoria parcial',
'subtitle' => 'Para quem já começou e quer seguir acompanhado.', 'subtitle' => 'Para quem já começou, mas quer seguir acompanhado.',
'summary' => 'Para casais que já tomaram decisões e contrataram parte dos fornecedores, mas precisam estruturar o que falta.', 'summary' => 'Indicada para casais que já iniciaram o planejamento e possuem parte dos fornecedores ou decisões definidas, mas desejam organizar o que falta e conduzir as próximas etapas.',
'scope_items' => [ 'scope_items' => [
'Diagnóstico do planejamento existente', 'Diagnóstico do planejamento já realizado',
'Organização das pendências', 'Organização das etapas pendentes',
'Gestão dos fornecedores contratados', 'Gestão e acompanhamento dos fornecedores contratados',
'Orientação para próximas decisões', 'Orientação sobre as próximas decisões',
'Coordenação da execução', 'Acompanhamento de prazos e informações',
'Comunicação e alinhamento com fornecedores',
'Organização do cronograma do evento',
'Coordenação da execução no grande dia',
], ],
'cta_label' => 'Quero conhecer a Conduzione', 'cta_label' => 'Quero conhecer a Conduzione',
'compare_heading' => 'Trazer a Amare para o caminho', 'compare_heading' => 'Trazer a Amare para o caminho',
'compare_summary' => 'O planejamento existe, mas ainda há decisões e gestão pela frente.', 'compare_summary' => 'O planejamento existe; a Amare organiza o que falta e conduz as próximas etapas.',
'sort_order' => 2, 'sort_order' => 2,
'eyebrow' => 'Assessoria', 'eyebrow' => 'Assessoria',
'title_line' => 'Assessoria', 'title_line' => 'Assessoria',
'title_emphasis' => 'Parcial', 'title_emphasis' => 'Parcial',
'hero_lead' => 'Para retomar o planejamento, organizar o que falta e seguir com acompanhamento profissional.', 'hero_lead' => 'A Amare entra no momento em que vocês estão, compreende o que já foi construído e assume a gestão das etapas necessárias dentro do planejamento.',
'benefits' => [ 'benefits' => [
['icon_key' => 'checklist', 'label' => 'Diagnóstico completo'], ['icon_key' => 'checklist', 'label' => 'Diagnóstico do que já existe'],
['icon_key' => 'calendar', 'label' => 'Organização integral'], ['icon_key' => 'calendar', 'label' => 'Pendências organizadas'],
['icon_key' => 'users', 'label' => 'Fornecedores selecionados'], ['icon_key' => 'users', 'label' => 'Fornecedores acompanhados'],
['icon_key' => 'heart', 'label' => 'Experiência leve e inesquecível'], ['icon_key' => 'heart', 'label' => 'Próximas decisões com clareza'],
], ],
'included_items' => [ 'included_items' => [
['icon_key' => 'checklist', 'title' => 'Diagnóstico do planejamento', 'description' => 'Um olhar profissional sobre o que já existe.'], ['icon_key' => 'checklist', 'title' => 'Diagnóstico do planejamento', 'description' => 'Um olhar profissional sobre o que o casal já construiu.'],
['icon_key' => 'calendar', 'title' => 'Organização das pendências', 'description' => 'Prioridades claras, etapa a etapa.'], ['icon_key' => 'calendar', 'title' => 'Etapas pendentes', 'description' => 'O que falta ganha ordem, prazo e prioridade.'],
['icon_key' => 'map', 'title' => 'Gestão de fornecedores', 'description' => 'Contatos e prazos sob cuidado.'], ['icon_key' => 'map', 'title' => 'Fornecedores já contratados', 'description' => 'Gestão e comunicação com quem já está no projeto.'],
['icon_key' => 'spark', 'title' => 'Gestão dos próximos passos', 'description' => 'Um plano para chegar tranquilo ao grande dia.'], ['icon_key' => 'spark', 'title' => 'Próximas decisões', 'description' => 'Orientação para seguir sem retrabalho.'],
['icon_key' => 'users', 'title' => 'Acompanhamento contínuo', 'description' => 'Perto do casal em cada decisão.'], ['icon_key' => 'users', 'title' => 'Cronograma do evento', 'description' => 'A operação do grande dia começa a ficar visível.'],
['icon_key' => 'heart', 'title' => 'Dia do evento', 'description' => 'Coordenação total para o casal viver o momento.'], ['icon_key' => 'heart', 'title' => 'Coordenação no grande dia', 'description' => 'Execução acompanhada para o casal viver o momento.'],
], ],
'audience_heading' => 'Para quem é este pacote', 'audience_heading' => 'Para quem é esta modalidade',
'audience_intro' => 'Para quem já começou a planejar e quer retomar as rédeas com acompanhamento profissional.', 'audience_intro' => 'Para quem já deu os primeiros passos, mas deseja seguir com mais segurança, organização e tranquilidade.',
'audience_points' => [ 'audience_points' => [
'Casamentos em andamento', 'Casais que já iniciaram o planejamento',
'Casais com pouco tempo disponível', 'Quem já tem parte dos fornecedores ou decisões definidas',
'Quem precisa priorizar pendências', 'Quem quer condução profissional nas etapas que faltam',
'Quem busca fornecedores confiáveis',
], ],
'final_cta_heading' => 'Pronta para retomar?', 'final_cta_heading' => 'Vamos organizar o que falta?',
'final_cta_body' => 'Conte onde seu planejamento está e receba uma proposta.', 'final_cta_body' => 'Conte onde o planejamento está. A Amare entra nesse momento e conduz as próximas etapas.',
], ],
[ [
'name' => 'Grand Jour', 'name' => 'Grand Jour',
'slug' => 'grand-jour', 'slug' => 'grand-jour',
'level' => '03 / FINAL', 'level' => '03 / FINAL',
'tag' => 'Assessoria final', 'tag' => 'Assessoria final',
'subtitle' => 'O planejamento está pronto. Agora é hora de viver.', 'subtitle' => 'Para quem já planejou e precisa de alguém para assumir a operação.',
'summary' => 'Para casais na reta final que precisam de uma equipe para assumir alinhamentos, cronograma e operação.', 'summary' => 'Indicada para casais que estão na reta final dos preparativos e já possuem a maior parte do casamento definida.',
'scope_items' => [ 'scope_items' => [
'Imersão no planejamento existente', 'Imersão no planejamento existente',
'Conferência das informações', 'Conferência das informações e contratos dos fornecedores',
'Alinhamento dos fornecedores', 'Alinhamento e comunicação com os profissionais envolvidos',
'Organização do cronograma final', 'Organização do cronograma final',
'Gestão da operação do evento', 'Orientação sobre os últimos detalhes',
'Coordenação dos fornecedores no evento',
'Acompanhamento da montagem e dos momentos do casamento',
'Gestão da operação durante o grande dia',
], ],
'cta_label' => 'Quero conhecer a Grand Jour', 'cta_label' => 'Quero conhecer a Grand Jour',
'compare_heading' => 'Entregar a operação para a Amare', 'compare_heading' => 'Entregar a operação para a Amare',
'compare_summary' => 'O projeto está pronto e o foco passa a ser alinhamento e execução.', 'compare_summary' => 'O projeto está pronto; o foco passa a ser alinhamento e execução.',
'sort_order' => 3, 'sort_order' => 3,
'eyebrow' => 'Assessoria', 'eyebrow' => 'Assessoria',
'title_line' => 'Assessoria', 'title_line' => 'Assessoria',
'title_emphasis' => 'Final', 'title_emphasis' => 'Final',
'hero_lead' => 'Para assumir os alinhamentos e a operação quando o grande dia está próximo.', 'hero_lead' => 'A Amare assume a condução operacional, mergulha no planejamento construído pelo casal e transforma as informações em uma execução organizada e alinhada.',
'benefits' => [ 'benefits' => [
['icon_key' => 'checklist', 'label' => 'Imersão no que já existe'], ['icon_key' => 'checklist', 'label' => 'Imersão no planejamento'],
['icon_key' => 'users', 'label' => 'Alinhamento de fornecedores'], ['icon_key' => 'users', 'label' => 'Fornecedores alinhados'],
['icon_key' => 'calendar', 'label' => 'Cronograma final'], ['icon_key' => 'calendar', 'label' => 'Cronograma final'],
['icon_key' => 'heart', 'label' => 'Operação com leveza'], ['icon_key' => 'heart', 'label' => 'Operação no grande dia'],
], ],
'included_items' => [ 'included_items' => [
['icon_key' => 'checklist', 'title' => 'Imersão no planejamento', 'description' => 'Entendemos cada decisão já tomada.'], ['icon_key' => 'checklist', 'title' => 'Imersão no planejamento', 'description' => 'A Amare mergulha no que o casal já decidiu.'],
['icon_key' => 'map', 'title' => 'Alinhamento de fornecedores', 'description' => 'Contatos e prazos sob cuidado.'], ['icon_key' => 'map', 'title' => 'Contratos e informações', 'description' => 'Conferência do combinado com cada profissional.'],
['icon_key' => 'calendar', 'title' => 'Cronograma final', 'description' => 'Cada etapa posicionada na reta final.'], ['icon_key' => 'users', 'title' => 'Alinhamento dos envolvidos', 'description' => 'Comunicação centralizada na reta final.'],
['icon_key' => 'spark', 'title' => 'Ensaios e detalhes', 'description' => 'Produção e logística nos detalhes.'], ['icon_key' => 'calendar', 'title' => 'Cronograma final', 'description' => 'A operação do grande dia ganha ordem.'],
['icon_key' => 'users', 'title' => 'Operação do evento', 'description' => 'Gestão da equipe e do dia a dia.'], ['icon_key' => 'spark', 'title' => 'Últimos detalhes', 'description' => 'Orientação para fechar o que ainda falta.'],
['icon_key' => 'heart', 'title' => 'Dia do evento', 'description' => 'Coordenação total para o casal viver o momento.'], ['icon_key' => 'heart', 'title' => 'Gestão da operação', 'description' => 'Montagem, momentos e condução no dia do casamento.'],
], ],
'audience_heading' => 'Para quem é este pacote', 'audience_heading' => 'Para quem é esta modalidade',
'audience_intro' => 'Para casais já planejados que querem chegar ao grande dia com equipe e operação no lugar.', 'audience_intro' => 'Porque, na reta final, vocês não precisam de mais tarefas. Precisam de alguém que assuma a condução.',
'audience_points' => [ 'audience_points' => [
'Casamentos já planejados', 'Casais na reta final dos preparativos',
'Casais na reta final', 'Quem já definiu a maior parte do casamento',
'Quem precisa de operação no dia', 'Quem quer entregar a operação do grande dia',
'Quem quer viver o momento com leveza',
], ],
'final_cta_heading' => 'Pronta para o grande dia?', 'final_cta_heading' => 'Vamos assumir a operação?',
'final_cta_body' => 'Conte a data do seu evento e receba uma proposta.', 'final_cta_body' => 'Conte a data e o que já está definido. A Amare entra para conduzir o grande dia.',
], ],
]; ];

View File

@@ -1,51 +1,28 @@
# Domain Docs # Domain Docs
How the engineering skills should consume this repo's domain documentation when exploring the codebase. How engineering skills should consume this repo's domain documentation.
## Before exploring, read these ## Single source of truth
- **`CONTEXT.md`** at the repo root, or **`SPEC.md` §8** (especially **§8.0 Linguagem ubíqua**) is the glossary and domain model for Amare. There is no root `CONTEXT.md` on purpose — do not create one. Prefer `SPEC.md` over Linear copy or OpenSpec prose when terms conflict.
- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved. Also useful:
## File structure - **`PRODUCT.md`** — positioning and audience (not the glossary)
- **`openspec/specs/`** — current capability contracts
- **`docs/adr/README.md`** — index only; ADR-001010 live in `SPEC.md` §21
Single-context repo (most repos): ## Before exploring
``` 1. Read `SPEC.md` §8.0 for vocabulary; skim §5.1 for public routes and §8.2+ for persistence shape when relevant.
/ 2. If a skill expects `CONTEXT.md` / `CONTEXT-MAP.md` and finds neither, **proceed using `SPEC.md` §8** — do not invent a parallel glossary file.
├── CONTEXT.md
├── docs/adr/
│ ├── 0001-event-sourced-orders.md
│ └── 0002-postgres-for-write-model.md
└── src/
```
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
```
/
├── CONTEXT-MAP.md
├── docs/adr/ ← system-wide decisions
└── src/
├── ordering/
│ ├── CONTEXT.md
│ └── docs/adr/ ← context-specific decisions
└── billing/
├── CONTEXT.md
└── docs/adr/
```
## Use the glossary's vocabulary ## Use the glossary's vocabulary
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids. When output names a domain concept (issue title, refactor, hypothesis, test name), use the term as defined in `SPEC.md` §8.0. Don't drift to synonyms the glossary explicitly avoids (`_Avoid_`).
If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`). If the concept isn't in §8.0 yet, either you're inventing language the project doesn't use (reconsider) or there's a real gap — resolve it by updating `SPEC.md` §8.0 via `/grill-with-docs` / domain-modeling, not by adding `CONTEXT.md`.
## Flag ADR conflicts ## Flag ADR conflicts
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding: If output contradicts an ADR in `SPEC.md` §21, surface it explicitly rather than silently overriding.
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_

View File

@@ -14,9 +14,6 @@
'note' => 'Nomenclaturas exibidas conforme materiais/reunião; confirmar versão final antes da publicação.', 'note' => 'Nomenclaturas exibidas conforme materiais/reunião; confirmar versão final antes da publicação.',
]) ])
@php
$bandHref = $bandCtaHref ?? route('briefing');
@endphp
<section <section
aria-labelledby="packages-heading" aria-labelledby="packages-heading"
class="home-chapter border-b border-amare-border bg-amare-bg" class="home-chapter border-b border-amare-border bg-amare-bg"
@@ -72,12 +69,20 @@
</div> </div>
@endif @endif
@php($orientationLink = \App\Support\PackageContactLink::forOrientation($settings))
@php($bandHref = filled($bandCtaHref) ? $bandCtaHref : $orientationLink['href'])
@php($bandIsWhatsapp = ! filled($bandCtaHref) && $orientationLink['isWhatsapp'])
<div class="mt-6 flex flex-col justify-between gap-8 bg-amare-accent-deep p-8 text-amare-accent-text md:flex-row md:items-center md:p-12" data-reveal data-reveal-from="up"> <div class="mt-6 flex flex-col justify-between gap-8 bg-amare-accent-deep p-8 text-amare-accent-text md:flex-row md:items-center md:p-12" data-reveal data-reveal-from="up">
<div class="max-w-xl space-y-3"> <div class="max-w-xl space-y-3">
<h3 class="text-[clamp(1.5625rem,2.7vw,2.125rem)] font-medium leading-tight">Ainda não sabe qual modalidade é ideal?</h3> <h3 class="text-[clamp(1.5625rem,2.7vw,2.125rem)] font-medium leading-tight">Ainda não sabe qual modalidade é ideal?</h3>
<p class="text-amare-accent-text/80">{{ $bandBody }}</p> <p class="text-amare-accent-text/80">{{ $bandBody }}</p>
</div> </div>
<a href="{{ $bandHref }}" class="inline-flex min-h-[48px] items-center justify-center self-start border border-amare-accent-text px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-text transition-colors hover:bg-amare-accent-text hover:text-amare-accent-deep md:self-auto"> <a
data-testid="package-guidance-cta"
href="{{ $bandHref }}"
@if ($bandIsWhatsapp) target="_blank" rel="noopener noreferrer" @endif
class="inline-flex min-h-[48px] items-center justify-center self-start border border-amare-accent-text px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-text transition-colors hover:bg-amare-accent-text hover:text-amare-accent-deep md:self-auto"
>
Conversar com a Amare Conversar com a Amare
</a> </a>
</div> </div>

View File

@@ -47,7 +47,6 @@
:show-intro="false" :show-intro="false"
kicker="tag" kicker="tag"
:show-subtitle="true" :show-subtitle="true"
:band-cta-href="route('briefing')"
band-body="Conte um pouco sobre o casamento. A Amare entende o momento de vocês e orienta o melhor formato de acompanhamento sem depender de um quiz automático." band-body="Conte um pouco sobre o casamento. A Amare entende o momento de vocês e orienta o melhor formato de acompanhamento sem depender de um quiz automático."
:show-note="false" :show-note="false"
/> />

View File

@@ -38,6 +38,33 @@ class WeddingPackageTest extends TestCase
['Essenza', 'Conduzione', 'Grand Jour'], ['Essenza', 'Conduzione', 'Grand Jour'],
WeddingPackage::query()->published()->orderBy('sort_order')->pluck('name')->all(), WeddingPackage::query()->published()->orderBy('sort_order')->pluck('name')->all(),
); );
$essenza = WeddingPackage::query()->where('name', 'Essenza')->first();
$this->assertNotNull($essenza);
$this->assertSame('Assessoria completa', $essenza->tag);
$this->assertSame('Do primeiro planejamento ao grande dia.', $essenza->subtitle);
$this->assertStringContainsString('desde o início da organização', (string) $essenza->summary);
$this->assertContains('Estruturação e acompanhamento do planejamento', $essenza->scope_items);
$this->assertSame(
['Casais no início da organização', 'Quem ainda está estruturando etapas importantes', 'Quem quer acompanhamento do planejamento à execução'],
$essenza->audience_points,
);
$publicCopy = WeddingPackage::query()->published()->get()->map(fn (WeddingPackage $package): string => json_encode([
$package->summary,
$package->subtitle,
$package->hero_lead,
$package->scope_items,
$package->included_items,
$package->audience_intro,
$package->final_cta_body,
], JSON_THROW_ON_ERROR))->implode(' ');
$this->assertStringNotContainsString('R$', $publicCopy);
$this->assertStringNotContainsString('3.500', $publicCopy);
$this->assertStringNotContainsString('Assessoria VIP', $publicCopy);
$this->assertStringNotContainsString('90 km', $publicCopy);
$this->assertStringNotContainsString('3,49%', $publicCopy);
} }
public function test_factory_generates_a_slug_and_autofills_blank_slug_from_name(): void public function test_factory_generates_a_slug_and_autofills_blank_slug_from_name(): void

View File

@@ -8,6 +8,7 @@ use App\Models\PortfolioCase;
use App\Models\SiteSetting; use App\Models\SiteSetting;
use App\Models\Testimonial; use App\Models\Testimonial;
use App\Models\WeddingPackage; use App\Models\WeddingPackage;
use App\Support\PackageContactLink;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase; use Tests\TestCase;
@@ -213,7 +214,24 @@ class HomePageContentTest extends TestCase
false, false,
) )
->assertSee('target="_blank"', false) ->assertSee('target="_blank"', false)
->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false); ->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false)
->assertSee('data-testid="package-guidance-cta"', false)
->assertSee(
'https://wa.me/5511988887777?text='.rawurlencode(PackageContactLink::ORIENTATION_MESSAGE),
false,
);
}
public function test_orientation_cta_falls_back_to_briefing_without_whatsapp_number(): void
{
SiteSetting::instance()->update(['whatsapp_number' => null]);
$response = $this->get(route('home'));
$response
->assertOk()
->assertSee('href="'.route('briefing').'"', false)
->assertDontSee(PackageContactLink::ORIENTATION_MESSAGE, false);
} }
public function test_corporate_steps_and_placeholder_render_from_settings(): void public function test_corporate_steps_and_placeholder_render_from_settings(): void

View File

@@ -6,6 +6,7 @@ namespace Tests\Feature\PublicSite;
use App\Models\SiteSetting; use App\Models\SiteSetting;
use App\Models\WeddingPackage; use App\Models\WeddingPackage;
use App\Support\PackageContactLink;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase; use Tests\TestCase;
@@ -31,7 +32,12 @@ class ServicesPageCtaTest extends TestCase
false, false,
) )
->assertSee('target="_blank"', false) ->assertSee('target="_blank"', false)
->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false); ->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false)
->assertSee('data-testid="package-guidance-cta"', false)
->assertSee(
'https://wa.me/5511988887777?text='.rawurlencode(PackageContactLink::ORIENTATION_MESSAGE),
false,
);
} }
public function test_package_cta_falls_back_to_briefing_without_whatsapp_number(): void public function test_package_cta_falls_back_to_briefing_without_whatsapp_number(): void
@@ -50,4 +56,18 @@ class ServicesPageCtaTest extends TestCase
->assertSee('href="'.route('briefing', ['servico_interesse' => 'Essenza']).'"', false) ->assertSee('href="'.route('briefing', ['servico_interesse' => 'Essenza']).'"', false)
->assertDontSee('wa.me/', false); ->assertDontSee('wa.me/', false);
} }
public function test_orientation_cta_uses_distinct_whatsapp_message_when_a_number_is_configured(): void
{
SiteSetting::instance()->update(['whatsapp_number' => '+55 11 98888-7777']);
$response = $this->get(route('services.index'));
$response
->assertOk()
->assertSee(
'https://wa.me/5511988887777?text='.rawurlencode(PackageContactLink::ORIENTATION_MESSAGE),
false,
);
}
} }