From b37e96465aad771e4274b138b4ae55da578d131f Mon Sep 17 00:00:00 2001 From: Rodribm10 Date: Mon, 13 Apr 2026 22:53:28 -0300 Subject: [PATCH] docs: adapta plano da Fase 1 para reusar schema reserva_hotel Em vez de criar schema novo reserva_1001, reaproveita o schema reserva_hotel existente no projeto Supabase acdvblhzzaneddlxqyst (InAudit Hotel). Migration aditiva (3 tabelas + 4 colunas Chatwoot) ja aplicada via MCP antes do plano iniciar. Adaptacoes: - Credenciais reais do projeto em .env.local - Cliente Supabase com db.schema = reserva_hotel - Tipos gerados com --schema reserva_hotel - App.tsx le tabela 'marcas' (pt-br) em vez de 'brands' - Mock do Vitest atualizado - Task 5 vira "documenta migration aplicada" (sem db push) - Task 6 usa supabase link + gen types --schema Co-Authored-By: Claude Opus 4.6 (1M context) --- ...2026-04-13-reserva-1001-fase-1-fundacao.md | 453 +++++++----------- 1 file changed, 181 insertions(+), 272 deletions(-) diff --git a/docs/superpowers/plans/2026-04-13-reserva-1001-fase-1-fundacao.md b/docs/superpowers/plans/2026-04-13-reserva-1001-fase-1-fundacao.md index f3a02acdb..312b72b5f 100644 --- a/docs/superpowers/plans/2026-04-13-reserva-1001-fase-1-fundacao.md +++ b/docs/superpowers/plans/2026-04-13-reserva-1001-fase-1-fundacao.md @@ -4,7 +4,7 @@ **Goal:** Criar o novo projeto `reserva-1001` do zero como Vite + React 19 + TypeScript + Tailwind v4 + Supabase, com a paleta premium aplicada, schema novo no Supabase, e uma página de teste renderizando marcas reais do banco. -**Architecture:** App Vite novo em `/Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001/`, separado do Chatwoot. POC antigo fica como `_poc-reference/` para consulta. Supabase com schema reformado e seeds. Nenhuma integração com Chatwoot ainda — isso é Fase 2. +**Architecture:** App Vite novo em `/Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001/`, separado do Chatwoot. POC antigo fica como `_poc-reference/` para consulta. Reutiliza o schema `reserva_hotel` existente no projeto Supabase `acdvblhzzaneddlxqyst` (InAudit Hotel), com migração aditiva para fotos, extras e integração Chatwoot. Nenhuma integração com Chatwoot ainda — isso é Fase 2. **Tech Stack:** Vite 6 · React 19 · TypeScript 5.6 · Tailwind v4 · shadcn/ui (cva + Radix) · Supabase JS 2 · framer-motion 12 · anime.js 4 · Vitest · ESLint · Prettier @@ -14,34 +14,21 @@ --- -## Pré-requisitos (você precisa fazer antes de começar) +## Pré-requisitos (já resolvidos) -1. **Decisão sobre Supabase project:** - - **Recomendado:** criar um projeto novo em [app.supabase.com](https://app.supabase.com) chamado `reserva-1001` na região `sa-east-1` (São Paulo) - - Alternativa: reutilizar o projeto existente do POC (`qdpzlxqsjbyxcajinixi`) — se escolher isso, nas migrations use `CREATE TABLE IF NOT EXISTS` e rode em um schema separado `reserva_v2` - - **Este plano assume projeto novo.** +**Supabase:** projeto **InAudit Hotel** (`acdvblhzzaneddlxqyst`), schema **`reserva_hotel`** reaproveitado. Migration aditiva (3 tabelas novas: `fotos_categoria`, `extras`, `reserva_extras` + colunas `chatwoot_*`) já aplicada via MCP antes de iniciar o plano. -2. **Credenciais Supabase do projeto novo:** - - Anote `Project URL` (ex: `https://abcd1234.supabase.co`) - - Anote `anon public key` (em Settings → API) - - Anote `service_role key` (só usado no backend, guardar seguro) +**Credenciais:** +- URL: `https://acdvblhzzaneddlxqyst.supabase.co` +- Anon key: ver `.env.local` (criado na Task 4) +- Schema: `reserva_hotel` -3. **Supabase CLI instalado:** - ```bash - brew install supabase/tap/supabase - supabase --version # deve mostrar >= 1.200.0 - ``` +**Tabelas PT-BR disponíveis:** +`marcas`, `unidades`, `suites`, `suites_unidades`, `precos`, `contas_pagamento`, `reservas`, `fotos_categoria`, `extras`, `reserva_extras`, `vw_precos_completa`, `vw_reservas_completa`. -4. **pnpm instalado:** - ```bash - which pnpm || npm install -g pnpm - pnpm --version # deve mostrar >= 9 - ``` +**⚠️ Ação manual do usuário necessária:** no Supabase Dashboard → Project Settings → API → **Data API settings** → **Exposed schemas**, adicionar `reserva_hotel` à lista (se ainda não estiver). Sem isso, o cliente JS não consegue ler as tabelas. -5. **Node 18+:** - ```bash - node --version # v18 ou maior - ``` +**Ferramental local (já validado):** Node v20, pnpm 9.14, supabase CLI 2.48. --- @@ -68,9 +55,8 @@ reserva-1001/ │ └── hotel-1001-noites-prime---reserva/ ├── supabase/ │ ├── config.toml -│ ├── migrations/ -│ │ └── 20260413000001_initial_schema.sql -│ └── seed.sql +│ └── migrations/ +│ └── 20260413000001_reserva_1001_additive_schema.sql └── src/ ├── main.tsx # entrypoint ├── App.tsx # shell simples exibindo marcas @@ -491,26 +477,28 @@ git commit -m "feat: configura tailwind v4 com paleta premium (obsidian/champagn Create `reserva-1001/.env.local.example`: ``` -# Supabase — pegue em app.supabase.com > Settings > API -VITE_SUPABASE_URL=https://SEU_PROJETO.supabase.co -VITE_SUPABASE_ANON_KEY=sua_anon_key_aqui +# Supabase — projeto InAudit Hotel +VITE_SUPABASE_URL=https://acdvblhzzaneddlxqyst.supabase.co +VITE_SUPABASE_ANON_KEY= +VITE_SUPABASE_SCHEMA=reserva_hotel # Chatwoot — token de integração (Fase 2) VITE_CHATWOOT_API_URL=https://chatwoot.fazer.ai VITE_CHATWOOT_API_TOKEN= ``` -- [ ] **Step 2: Criar .env.local com as credenciais reais** +- [ ] **Step 2: Criar .env.local com as credenciais reais do projeto InAudit Hotel** -Create `reserva-1001/.env.local` (substitua os valores pelos do seu projeto Supabase): +Create `reserva-1001/.env.local`: ``` -VITE_SUPABASE_URL=https://SEU_PROJETO.supabase.co -VITE_SUPABASE_ANON_KEY=eyJhbGc... +VITE_SUPABASE_URL=https://acdvblhzzaneddlxqyst.supabase.co +VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFjZHZibGh6emFuZWRkbHhxeXN0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTA4MTMyNzcsImV4cCI6MjA2NjM4OTI3N30.wwmr0TMYuds-QLrTESR4kx34umssseDyEsPjVRt02qI +VITE_SUPABASE_SCHEMA=reserva_hotel VITE_CHATWOOT_API_URL=https://chatwoot.fazer.ai VITE_CHATWOOT_API_TOKEN= ``` -**IMPORTANTE:** este arquivo está no `.gitignore` e NÃO vai pro git. Se você não tem as credenciais ainda, crie o projeto Supabase antes (pré-requisito 1). +**IMPORTANTE:** este arquivo está no `.gitignore` e NÃO vai pro git. - [ ] **Step 3: Criar src/lib/utils.ts (cn helper)** @@ -553,6 +541,7 @@ import type { Database } from '@/types/database' const supabaseUrl = import.meta.env.VITE_SUPABASE_URL const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY +const supabaseSchema = import.meta.env.VITE_SUPABASE_SCHEMA ?? 'reserva_hotel' if (!supabaseUrl || !supabaseAnonKey) { throw new Error( @@ -560,7 +549,9 @@ if (!supabaseUrl || !supabaseAnonKey) { ) } -export const supabase = createClient(supabaseUrl, supabaseAnonKey) +export const supabase = createClient(supabaseUrl, supabaseAnonKey, { + db: { schema: supabaseSchema as 'reserva_hotel' }, +}) ``` - [ ] **Step 6: Validar que o typecheck passa** @@ -581,12 +572,13 @@ git commit -m "feat: configura cliente supabase com variaveis de ambiente" --- -## Task 5: Inicializar Supabase CLI local e criar a migration +## Task 5: Documentar a migration aditiva (schema já aplicado via MCP) + +**Contexto:** A migration aditiva ao schema `reserva_hotel` já foi aplicada no projeto Supabase antes do plano começar (via MCP). Esta task **só cria o arquivo SQL** como fonte de verdade/histórico no repositório — **não roda `supabase db push`**. **Files:** - Create: `reserva-1001/supabase/config.toml` (via CLI) -- Create: `reserva-1001/supabase/migrations/20260413000001_initial_schema.sql` -- Create: `reserva-1001/supabase/seed.sql` +- Create: `reserva-1001/supabase/migrations/20260413000001_reserva_1001_additive_schema.sql` - [ ] **Step 1: Inicializar Supabase CLI no projeto** @@ -597,233 +589,111 @@ supabase init Expected: cria `supabase/config.toml` e `supabase/` folder. Se perguntar sobre VSCode settings, responda N. -- [ ] **Step 2: Criar migration 001 — schema inicial** +- [ ] **Step 2: Criar o arquivo de migration (DDL da migration já aplicada)** -Create `reserva-1001/supabase/migrations/20260413000001_initial_schema.sql`: +Create `reserva-1001/supabase/migrations/20260413000001_reserva_1001_additive_schema.sql`: ```sql --- Reserva Rede 1001 — schema inicial --- Paleta de tabelas: brands, hotel_units, suite_categories, suite_images, --- pricing, extras, reservations, reservation_extras +-- Reserva Rede 1001 — adições ao schema reserva_hotel existente +-- Já aplicada via MCP. Este arquivo é a fonte de verdade histórica. +-- Zero alteração destrutiva. Tudo aditivo. --- === Marcas === -create table public.brands ( - id bigserial primary key, - name text not null, - slug text not null unique, - logo_url text, - primary_color text, - created_at timestamptz not null default now() +-- === 1. Fotos por categoria de suíte (linked por unidade + nome da categoria) === +create table if not exists reserva_hotel.fotos_categoria ( + id uuid primary key default gen_random_uuid(), + id_unidade uuid not null references reserva_hotel.unidades(id) on delete cascade, + categoria text not null, + url_foto text not null, + alt text, + ordem int not null default 0, + ativa boolean not null default true, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() ); --- === Unidades === -create table public.hotel_units ( - id bigserial primary key, - brand_id bigint not null references public.brands(id) on delete cascade, - name text not null, - slug text not null, - chatwoot_unit_id bigint not null, - active boolean not null default true, - created_at timestamptz not null default now(), - unique (brand_id, slug) +create index if not exists idx_fotos_categoria_unidade on reserva_hotel.fotos_categoria(id_unidade); +create index if not exists idx_fotos_categoria_lookup on reserva_hotel.fotos_categoria(id_unidade, categoria); + +-- === 2. Extras (adicionais por marca) === +create table if not exists reserva_hotel.extras ( + id uuid primary key default gen_random_uuid(), + id_marca uuid not null references reserva_hotel.marcas(id) on delete cascade, + titulo text not null, + descricao text, + preco numeric not null check (preco >= 0), + imagem_url text, + ativo boolean not null default true, + ordem int not null default 0, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() ); -create index idx_hotel_units_brand on public.hotel_units(brand_id); -create index idx_hotel_units_chatwoot_unit on public.hotel_units(chatwoot_unit_id); +create index if not exists idx_extras_marca on reserva_hotel.extras(id_marca); --- === Categorias de suíte === -create table public.suite_categories ( - id bigserial primary key, - unit_id bigint not null references public.hotel_units(id) on delete cascade, - name text not null, - description text, - sort_order int not null default 0, - active boolean not null default true, - created_at timestamptz not null default now() +-- === 3. Junction reserva ↔ extras === +create table if not exists reserva_hotel.reserva_extras ( + id_reserva uuid not null references reserva_hotel.reservas(id) on delete cascade, + id_extra uuid not null references reserva_hotel.extras(id), + preco numeric not null check (preco >= 0), + created_at timestamptz not null default now(), + primary key (id_reserva, id_extra) ); -create index idx_suite_categories_unit on public.suite_categories(unit_id); +-- === 4. Integração Chatwoot em unidades === +alter table reserva_hotel.unidades + add column if not exists chatwoot_unit_id bigint; --- === Fotos de categoria === -create table public.suite_images ( - id bigserial primary key, - category_id bigint not null references public.suite_categories(id) on delete cascade, - storage_path text not null, - alt text, - sort_order int not null default 0, - created_at timestamptz not null default now() -); +create index if not exists idx_unidades_chatwoot_unit on reserva_hotel.unidades(chatwoot_unit_id); -create index idx_suite_images_category on public.suite_images(category_id); +-- === 5. Integração Chatwoot em reservas === +alter table reserva_hotel.reservas + add column if not exists chatwoot_contact_id bigint; --- === Preços === -create type public.day_range_enum as enum ('weekday', 'weekend'); -create type public.stay_type_enum as enum ('2hrs', '3hrs', '4hrs', 'pernoite', 'diaria'); +alter table reserva_hotel.reservas + add column if not exists chatwoot_conversation_id bigint; -create table public.pricing ( - id bigserial primary key, - category_id bigint not null references public.suite_categories(id) on delete cascade, - day_range public.day_range_enum not null, - stay_type public.stay_type_enum not null, - price_cents int not null check (price_cents >= 0), - created_at timestamptz not null default now(), - unique (category_id, day_range, stay_type) -); +alter table reserva_hotel.reservas + add column if not exists chatwoot_pix_charge_id bigint; -create index idx_pricing_category on public.pricing(category_id); - --- === Extras === -create table public.extras ( - id bigserial primary key, - brand_id bigint not null references public.brands(id) on delete cascade, - title text not null, - description text, - price_cents int not null check (price_cents >= 0), - image_url text, - active boolean not null default true, - sort_order int not null default 0, - created_at timestamptz not null default now() -); - -create index idx_extras_brand on public.extras(brand_id); - --- === Reservas === -create type public.reservation_status_enum as enum ('pending', 'paid', 'expired', 'canceled'); - -create table public.reservations ( - id bigserial primary key, - brand_id bigint not null references public.brands(id), - unit_id bigint not null references public.hotel_units(id), - category_id bigint not null references public.suite_categories(id), - stay_type public.stay_type_enum not null, - checkin_at timestamptz not null, - customer_name text not null, - customer_phone text not null, - customer_cpf text not null, - customer_email text, - notes text, - total_cents int not null check (total_cents >= 0), - deposit_cents int not null check (deposit_cents >= 0), - chatwoot_contact_id bigint, - chatwoot_conversation_id bigint, - chatwoot_pix_charge_id bigint, - pix_txid text, - status public.reservation_status_enum not null default 'pending', - created_at timestamptz not null default now(), - paid_at timestamptz -); - -create index idx_reservations_status on public.reservations(status); -create index idx_reservations_pix_txid on public.reservations(pix_txid); -create index idx_reservations_created_at on public.reservations(created_at desc); - --- === Extras escolhidos na reserva === -create table public.reservation_extras ( - reservation_id bigint not null references public.reservations(id) on delete cascade, - extra_id bigint not null references public.extras(id), - price_cents int not null check (price_cents >= 0), - primary key (reservation_id, extra_id) -); - --- === RLS === -alter table public.brands enable row level security; -alter table public.hotel_units enable row level security; -alter table public.suite_categories enable row level security; -alter table public.suite_images enable row level security; -alter table public.pricing enable row level security; -alter table public.extras enable row level security; -alter table public.reservations enable row level security; -alter table public.reservation_extras enable row level security; - --- Leitura pública para catálogo -create policy "public_read_brands" on public.brands - for select using (true); - -create policy "public_read_units" on public.hotel_units - for select using (active = true); - -create policy "public_read_categories" on public.suite_categories - for select using (active = true); - -create policy "public_read_images" on public.suite_images - for select using (true); - -create policy "public_read_pricing" on public.pricing - for select using (true); - -create policy "public_read_extras" on public.extras - for select using (active = true); - --- reservations e reservation_extras: somente service_role pode ler/escrever --- (deliberadamente sem policies pra anon — o backend controla) +create index if not exists idx_reservas_chatwoot_conversation on reserva_hotel.reservas(chatwoot_conversation_id); +create index if not exists idx_reservas_txid_pix on reserva_hotel.reservas(txid_pix); ``` -- [ ] **Step 3: Criar seed.sql com as 4 marcas** +- [ ] **Step 3: Commit** -Create `reserva-1001/supabase/seed.sql`: -```sql --- Seed inicial: 4 marcas do Grupo Nova -insert into public.brands (name, slug, primary_color) values - ('Hotel 1001 Noites', 'hotel-1001-noites', '#C9A961'), - ('Hotel 1001 Noites Prime', 'hotel-1001-noites-prime', '#C9A961'), - ('Hotel 1001 Noites Express','hotel-1001-noites-express','#C9A961'), - ('Dolce Amore', 'dolce-amore', '#E8B4A0') -on conflict (slug) do nothing; -``` - -- [ ] **Step 4: Linkar o projeto local ao projeto Supabase remoto** +Nota: não há seed.sql neste plano — o schema `reserva_hotel` já contém 1 marca de seed e o usuário vai cadastrar o resto via admin (Fase 4). Também não rodamos `supabase db push` porque a migration já foi aplicada via MCP antes do plano. ```bash -cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 -supabase link --project-ref SEU_PROJECT_REF -``` - -Substitua `SEU_PROJECT_REF` pelo ID do projeto (a parte antes de `.supabase.co` na URL). Vai pedir a senha do banco — use a que você definiu ao criar o projeto. - -Expected: `Finished supabase link.` - -- [ ] **Step 5: Aplicar a migration no Supabase remoto** - -```bash -supabase db push -``` - -Expected: aplica a migration. Se perguntar confirmação, responda Y. - -- [ ] **Step 6: Rodar o seed via SQL editor do Supabase** - -O `supabase db push` não roda `seed.sql` em projetos remotos por padrão. O caminho mais seguro: - -1. Abra o projeto em [app.supabase.com](https://app.supabase.com) -2. Vá em **SQL Editor** → **New query** -3. Cole o conteúdo de `supabase/seed.sql` -4. Clique em **Run** - -Expected: resposta `Success. No rows returned` e 4 linhas inseridas. Confira em **Table Editor** → `brands` — deve listar as 4 marcas. - -- [ ] **Step 7: Commit** - -```bash -git add supabase/config.toml supabase/migrations/ supabase/seed.sql -git commit -m "feat: migration inicial do schema + seed das 4 marcas" +git add supabase/config.toml supabase/migrations/ +git commit -m "feat: documenta migration aditiva do schema reserva_hotel" ``` --- -## Task 6: Gerar types TypeScript a partir do schema +## Task 6: Gerar types TypeScript do schema reserva_hotel **Files:** - Modify: `reserva-1001/src/types/database.ts` -- [ ] **Step 1: Gerar os tipos a partir do projeto remoto** +- [ ] **Step 1: Linkar CLI ao projeto InAudit Hotel** ```bash cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 -supabase gen types typescript --linked > src/types/database.ts +supabase link --project-ref acdvblhzzaneddlxqyst ``` -Expected: `src/types/database.ts` agora contém as definições completas de `Database` com as tabelas `brands`, `hotel_units`, `suite_categories`, etc. +Vai pedir a senha do banco. Use a senha do projeto. Se não souber, peça pro usuário. -- [ ] **Step 2: Validar typecheck** +- [ ] **Step 2: Gerar os tipos do schema reserva_hotel** + +```bash +supabase gen types typescript --linked --schema reserva_hotel > src/types/database.ts +``` + +Expected: `src/types/database.ts` contém `Database` tipado com as tabelas `marcas`, `unidades`, `suites`, `precos`, `reservas`, `fotos_categoria`, `extras`, `reserva_extras`, `contas_pagamento` sob o namespace `reserva_hotel`. + +**Nota:** o cliente Supabase configurado em Task 4 já aponta para o schema `reserva_hotel` via `db: { schema }`. Logo, ao fazer `supabase.from('marcas')`, ele automaticamente resolve `reserva_hotel.marcas`. + +- [ ] **Step 3: Validar typecheck** ```bash pnpm typecheck @@ -831,11 +701,25 @@ pnpm typecheck Expected: passa sem erros. -- [ ] **Step 3: Commit** +- [ ] **Step 4: Atualizar o script `supabase:types` em package.json** + +Edit `reserva-1001/package.json`, campo `scripts`: + +De: +```json +"supabase:types": "supabase gen types typescript --local > src/types/database.ts" +``` + +Para: +```json +"supabase:types": "supabase gen types typescript --linked --schema reserva_hotel > src/types/database.ts" +``` + +- [ ] **Step 5: Commit** ```bash -git add src/types/database.ts -git commit -m "feat: gera tipos typescript do supabase" +git add src/types/database.ts package.json +git commit -m "feat: gera tipos typescript do schema reserva_hotel" ``` --- @@ -845,7 +729,7 @@ git commit -m "feat: gera tipos typescript do supabase" **Files:** - Modify: `reserva-1001/src/App.tsx` -- [ ] **Step 1: Reescrever App.tsx para buscar e exibir marcas do Supabase** +- [ ] **Step 1: Reescrever App.tsx para buscar e exibir marcas do Supabase (schema reserva_hotel, tabela `marcas`)** Replace `reserva-1001/src/App.tsx`: ```tsx @@ -853,28 +737,29 @@ import { useEffect, useState } from 'react' import { supabase } from '@/lib/supabase' import type { Database } from '@/types/database' -type Brand = Database['public']['Tables']['brands']['Row'] +type Marca = Database['reserva_hotel']['Tables']['marcas']['Row'] export default function App() { - const [brands, setBrands] = useState([]) + const [marcas, setMarcas] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { - async function loadBrands() { + async function loadMarcas() { const { data, error } = await supabase - .from('brands') + .from('marcas') .select('*') - .order('name', { ascending: true }) + .eq('ativa', true) + .order('nome', { ascending: true }) if (error) { setError(error.message) } else { - setBrands(data ?? []) + setMarcas(data ?? []) } setLoading(false) } - loadBrands() + loadMarcas() }, []) return ( @@ -897,15 +782,24 @@ export default function App() { )} - {!loading && !error && ( + {!loading && !error && marcas.length === 0 && ( +

Nenhuma marca cadastrada ainda.

+ )} + + {!loading && !error && marcas.length > 0 && (
    - {brands.map((brand) => ( + {marcas.map((marca) => (
  • -

    {brand.name}

    -

    {brand.slug}

    +

    {marca.nome}

    + {marca.descricao &&

    {marca.descricao}

    } + {marca.categorias && marca.categorias.length > 0 && ( +

    + {marca.categorias.join(' · ')} +

    + )}
  • ))}
@@ -925,7 +819,7 @@ export default function App() { pnpm dev ``` -Abra `http://localhost:5173`. Expected: vê as 4 marcas em cards escuros com borda dourada sutil, título "Reserva Rede 1001" em gradiente dourado. Hover nos cards faz a borda brilhar. +Abra `http://localhost:5173`. Expected: vê as marcas existentes no schema `reserva_hotel` (hoje 1 marca: "Hotel 1001 Noites") em cards escuros com borda dourada sutil, título "Reserva Rede 1001" em gradiente dourado. Hover nos cards faz a borda brilhar. Pare com `Ctrl+C`. @@ -1186,24 +1080,40 @@ afterEach(() => { cleanup() }) -// Mock do Supabase client -vi.mock('@/lib/supabase', () => ({ - supabase: { - from: vi.fn(() => ({ - select: vi.fn(() => ({ - order: vi.fn(() => - Promise.resolve({ - data: [ - { id: 1, name: 'Hotel 1001 Noites', slug: 'hotel-1001-noites', logo_url: null, primary_color: '#C9A961', created_at: '2026-04-13T00:00:00Z' }, - { id: 2, name: 'Dolce Amore', slug: 'dolce-amore', logo_url: null, primary_color: '#E8B4A0', created_at: '2026-04-13T00:00:00Z' }, - ], - error: null, - }) - ), - })), - })), - }, -})) +// Mock do Supabase client — imita from('marcas').select('*').eq('ativa', true).order('nome') +vi.mock('@/lib/supabase', () => { + const mockMarcas = [ + { + id: '3fac5ed4-100f-4c0a-82ce-06110758b9c9', + nome: 'Hotel 1001 Noites', + categorias: ['Standard', 'Superior', 'Luxo'], + permanencias: ['3hrs', '6hrs', 'Pernoite'], + descricao: null, + ativa: true, + created_at: '2026-04-13T00:00:00Z', + updated_at: '2026-04-13T00:00:00Z', + }, + { + id: '11111111-1111-1111-1111-111111111111', + nome: 'Dolce Amore', + categorias: ['Suite Master', 'Apartamento'], + permanencias: ['3hrs', '4hrs'], + descricao: null, + ativa: true, + created_at: '2026-04-13T00:00:00Z', + updated_at: '2026-04-13T00:00:00Z', + }, + ] + + const orderFn = vi.fn(() => Promise.resolve({ data: mockMarcas, error: null })) + const eqFn = vi.fn(() => ({ order: orderFn })) + const selectFn = vi.fn(() => ({ eq: eqFn, order: orderFn })) + const fromFn = vi.fn(() => ({ select: selectFn })) + + return { + supabase: { from: fromFn }, + } +}) ``` - [ ] **Step 2: Escrever smoke test do App.tsx (TDD: test primeiro)** @@ -1413,8 +1323,7 @@ src/ ├── types/ # Tipos gerados do Supabase └── __tests__/ # Testes Vitest supabase/ -├── migrations/ # SQL de schema -└── seed.sql # Dados iniciais +└── migrations/ # SQL de schema (source of truth) _poc-reference/ # POC antigo — só pra consultar ``` @@ -1493,7 +1402,7 @@ pnpm dev Abra `http://localhost:5173`: - Título "Reserva Rede 1001" em gradiente dourado ✓ -- 4 marcas listadas em cards escuros com borda champagne ✓ +- Marcas do schema `reserva_hotel` listadas em cards escuros com borda champagne ✓ - Hover nos cards faz borda brilhar ✓ - Fundo com gradiente obsidian→midnight ✓ - Fonte serif (Fraunces) nos títulos, sans (Inter) no resto ✓ @@ -1515,7 +1424,7 @@ def4567 feat: componente Button base com variantes premium efg5678 feat: migra FormField e SelectField com paleta premium fgh6789 feat: renderiza marcas reais do supabase na pagina inicial ghi7890 feat: gera tipos typescript do supabase -hij8901 feat: migration inicial do schema + seed das 4 marcas +hij8901 feat: documenta migration aditiva do schema reserva_hotel ijk9012 feat: configura cliente supabase com variaveis de ambiente jkl0123 feat: configura tailwind v4 com paleta premium (obsidian/champagne/rose-gold) klm1234 feat: scaffold inicial do projeto vite + react + typescript @@ -1527,7 +1436,7 @@ lmn2345 chore: inicializa repo com POC como referencia ## Critérios de conclusão da Fase 1 - [ ] `pnpm typecheck`, `pnpm lint`, `pnpm test`, `pnpm build` todos passam -- [ ] Dev server sobe e renderiza a página inicial com as 4 marcas vindas do Supabase +- [ ] Dev server sobe e renderiza a página inicial com as marcas vindas do schema `reserva_hotel` - [ ] Paleta premium visível e consistente - [ ] Tipografia Fraunces + Inter carregada - [ ] FormField, SelectField e Button prontos para uso (mesmo que não usados ainda)