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) <noreply@anthropic.com>
This commit is contained in:
Rodribm10 2026-04-13 22:53:28 -03:00
parent 7606b336a3
commit b37e96465a

View File

@ -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. **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 **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:** **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.
- **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.**
2. **Credenciais Supabase do projeto novo:** **Credenciais:**
- Anote `Project URL` (ex: `https://abcd1234.supabase.co`) - URL: `https://acdvblhzzaneddlxqyst.supabase.co`
- Anote `anon public key` (em Settings → API) - Anon key: ver `.env.local` (criado na Task 4)
- Anote `service_role key` (só usado no backend, guardar seguro) - Schema: `reserva_hotel`
3. **Supabase CLI instalado:** **Tabelas PT-BR disponíveis:**
```bash `marcas`, `unidades`, `suites`, `suites_unidades`, `precos`, `contas_pagamento`, `reservas`, `fotos_categoria`, `extras`, `reserva_extras`, `vw_precos_completa`, `vw_reservas_completa`.
brew install supabase/tap/supabase
supabase --version # deve mostrar >= 1.200.0
```
4. **pnpm instalado:** **⚠️ 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.
```bash
which pnpm || npm install -g pnpm
pnpm --version # deve mostrar >= 9
```
5. **Node 18+:** **Ferramental local (já validado):** Node v20, pnpm 9.14, supabase CLI 2.48.
```bash
node --version # v18 ou maior
```
--- ---
@ -68,9 +55,8 @@ reserva-1001/
│ └── hotel-1001-noites-prime---reserva/ │ └── hotel-1001-noites-prime---reserva/
├── supabase/ ├── supabase/
│ ├── config.toml │ ├── config.toml
│ ├── migrations/ │ └── migrations/
│ │ └── 20260413000001_initial_schema.sql │ └── 20260413000001_reserva_1001_additive_schema.sql
│ └── seed.sql
└── src/ └── src/
├── main.tsx # entrypoint ├── main.tsx # entrypoint
├── App.tsx # shell simples exibindo marcas ├── 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`: Create `reserva-1001/.env.local.example`:
``` ```
# Supabase — pegue em app.supabase.com > Settings > API # Supabase — projeto InAudit Hotel
VITE_SUPABASE_URL=https://SEU_PROJETO.supabase.co VITE_SUPABASE_URL=https://acdvblhzzaneddlxqyst.supabase.co
VITE_SUPABASE_ANON_KEY=sua_anon_key_aqui VITE_SUPABASE_ANON_KEY=
VITE_SUPABASE_SCHEMA=reserva_hotel
# Chatwoot — token de integração (Fase 2) # Chatwoot — token de integração (Fase 2)
VITE_CHATWOOT_API_URL=https://chatwoot.fazer.ai VITE_CHATWOOT_API_URL=https://chatwoot.fazer.ai
VITE_CHATWOOT_API_TOKEN= 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_URL=https://acdvblhzzaneddlxqyst.supabase.co
VITE_SUPABASE_ANON_KEY=eyJhbGc... VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFjZHZibGh6emFuZWRkbHhxeXN0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTA4MTMyNzcsImV4cCI6MjA2NjM4OTI3N30.wwmr0TMYuds-QLrTESR4kx34umssseDyEsPjVRt02qI
VITE_SUPABASE_SCHEMA=reserva_hotel
VITE_CHATWOOT_API_URL=https://chatwoot.fazer.ai VITE_CHATWOOT_API_URL=https://chatwoot.fazer.ai
VITE_CHATWOOT_API_TOKEN= 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)** - [ ] **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 supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
const supabaseSchema = import.meta.env.VITE_SUPABASE_SCHEMA ?? 'reserva_hotel'
if (!supabaseUrl || !supabaseAnonKey) { if (!supabaseUrl || !supabaseAnonKey) {
throw new Error( throw new Error(
@ -560,7 +549,9 @@ if (!supabaseUrl || !supabaseAnonKey) {
) )
} }
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey) export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey, {
db: { schema: supabaseSchema as 'reserva_hotel' },
})
``` ```
- [ ] **Step 6: Validar que o typecheck passa** - [ ] **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:** **Files:**
- Create: `reserva-1001/supabase/config.toml` (via CLI) - Create: `reserva-1001/supabase/config.toml` (via CLI)
- Create: `reserva-1001/supabase/migrations/20260413000001_initial_schema.sql` - Create: `reserva-1001/supabase/migrations/20260413000001_reserva_1001_additive_schema.sql`
- Create: `reserva-1001/supabase/seed.sql`
- [ ] **Step 1: Inicializar Supabase CLI no projeto** - [ ] **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. 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 ```sql
-- Reserva Rede 1001 — schema inicial -- Reserva Rede 1001 — adições ao schema reserva_hotel existente
-- Paleta de tabelas: brands, hotel_units, suite_categories, suite_images, -- Já aplicada via MCP. Este arquivo é a fonte de verdade histórica.
-- pricing, extras, reservations, reservation_extras -- Zero alteração destrutiva. Tudo aditivo.
-- === Marcas === -- === 1. Fotos por categoria de suíte (linked por unidade + nome da categoria) ===
create table public.brands ( create table if not exists reserva_hotel.fotos_categoria (
id bigserial primary key, id uuid primary key default gen_random_uuid(),
name text not null, id_unidade uuid not null references reserva_hotel.unidades(id) on delete cascade,
slug text not null unique, categoria text not null,
logo_url text, url_foto text not null,
primary_color text, alt text,
created_at timestamptz not null default now() 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 index if not exists idx_fotos_categoria_unidade on reserva_hotel.fotos_categoria(id_unidade);
create table public.hotel_units ( create index if not exists idx_fotos_categoria_lookup on reserva_hotel.fotos_categoria(id_unidade, categoria);
id bigserial primary key,
brand_id bigint not null references public.brands(id) on delete cascade, -- === 2. Extras (adicionais por marca) ===
name text not null, create table if not exists reserva_hotel.extras (
slug text not null, id uuid primary key default gen_random_uuid(),
chatwoot_unit_id bigint not null, id_marca uuid not null references reserva_hotel.marcas(id) on delete cascade,
active boolean not null default true, titulo text not null,
created_at timestamptz not null default now(), descricao text,
unique (brand_id, slug) 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 if not exists idx_extras_marca on reserva_hotel.extras(id_marca);
create index idx_hotel_units_chatwoot_unit on public.hotel_units(chatwoot_unit_id);
-- === Categorias de suíte === -- === 3. Junction reserva ↔ extras ===
create table public.suite_categories ( create table if not exists reserva_hotel.reserva_extras (
id bigserial primary key, id_reserva uuid not null references reserva_hotel.reservas(id) on delete cascade,
unit_id bigint not null references public.hotel_units(id) on delete cascade, id_extra uuid not null references reserva_hotel.extras(id),
name text not null, preco numeric not null check (preco >= 0),
description text, created_at timestamptz not null default now(),
sort_order int not null default 0, primary key (id_reserva, id_extra)
active boolean not null default true,
created_at timestamptz not null default now()
); );
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 index if not exists idx_unidades_chatwoot_unit on reserva_hotel.unidades(chatwoot_unit_id);
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 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 === alter table reserva_hotel.reservas
create type public.day_range_enum as enum ('weekday', 'weekend'); add column if not exists chatwoot_conversation_id bigint;
create type public.stay_type_enum as enum ('2hrs', '3hrs', '4hrs', 'pernoite', 'diaria');
create table public.pricing ( alter table reserva_hotel.reservas
id bigserial primary key, add column if not exists chatwoot_pix_charge_id bigint;
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)
);
create index idx_pricing_category on public.pricing(category_id); 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);
-- === 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)
``` ```
- [ ] **Step 3: Criar seed.sql com as 4 marcas** - [ ] **Step 3: Commit**
Create `reserva-1001/supabase/seed.sql`: 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.
```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**
```bash ```bash
cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 git add supabase/config.toml supabase/migrations/
supabase link --project-ref SEU_PROJECT_REF git commit -m "feat: documenta migration aditiva do schema reserva_hotel"
```
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"
``` ```
--- ---
## Task 6: Gerar types TypeScript a partir do schema ## Task 6: Gerar types TypeScript do schema reserva_hotel
**Files:** **Files:**
- Modify: `reserva-1001/src/types/database.ts` - 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 ```bash
cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 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 ```bash
pnpm typecheck pnpm typecheck
@ -831,11 +701,25 @@ pnpm typecheck
Expected: passa sem erros. 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 ```bash
git add src/types/database.ts git add src/types/database.ts package.json
git commit -m "feat: gera tipos typescript do supabase" 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:** **Files:**
- Modify: `reserva-1001/src/App.tsx` - 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`: Replace `reserva-1001/src/App.tsx`:
```tsx ```tsx
@ -853,28 +737,29 @@ import { useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase' import { supabase } from '@/lib/supabase'
import type { Database } from '@/types/database' 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() { export default function App() {
const [brands, setBrands] = useState<Brand[]>([]) const [marcas, setMarcas] = useState<Marca[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
useEffect(() => { useEffect(() => {
async function loadBrands() { async function loadMarcas() {
const { data, error } = await supabase const { data, error } = await supabase
.from('brands') .from('marcas')
.select('*') .select('*')
.order('name', { ascending: true }) .eq('ativa', true)
.order('nome', { ascending: true })
if (error) { if (error) {
setError(error.message) setError(error.message)
} else { } else {
setBrands(data ?? []) setMarcas(data ?? [])
} }
setLoading(false) setLoading(false)
} }
loadBrands() loadMarcas()
}, []) }, [])
return ( return (
@ -897,15 +782,24 @@ export default function App() {
</div> </div>
)} )}
{!loading && !error && ( {!loading && !error && marcas.length === 0 && (
<p className="text-slate">Nenhuma marca cadastrada ainda.</p>
)}
{!loading && !error && marcas.length > 0 && (
<ul className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full max-w-2xl"> <ul className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full max-w-2xl">
{brands.map((brand) => ( {marcas.map((marca) => (
<li <li
key={brand.id} key={marca.id}
className="rounded-xl border border-champagne/20 bg-midnight/60 backdrop-blur p-6 text-center transition hover:border-champagne hover:glow-champagne" className="rounded-xl border border-champagne/20 bg-midnight/60 backdrop-blur p-6 text-center transition hover:border-champagne hover:glow-champagne"
> >
<h2 className="font-serif text-2xl text-champagne">{brand.name}</h2> <h2 className="font-serif text-2xl text-champagne">{marca.nome}</h2>
<p className="text-slate text-sm mt-1">{brand.slug}</p> {marca.descricao && <p className="text-slate text-sm mt-1">{marca.descricao}</p>}
{marca.categorias && marca.categorias.length > 0 && (
<p className="text-rose-gold text-xs mt-2 uppercase tracking-widest">
{marca.categorias.join(' · ')}
</p>
)}
</li> </li>
))} ))}
</ul> </ul>
@ -925,7 +819,7 @@ export default function App() {
pnpm dev 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`. Pare com `Ctrl+C`.
@ -1186,24 +1080,40 @@ afterEach(() => {
cleanup() cleanup()
}) })
// Mock do Supabase client // Mock do Supabase client — imita from('marcas').select('*').eq('ativa', true).order('nome')
vi.mock('@/lib/supabase', () => ({ vi.mock('@/lib/supabase', () => {
supabase: { const mockMarcas = [
from: vi.fn(() => ({ {
select: vi.fn(() => ({ id: '3fac5ed4-100f-4c0a-82ce-06110758b9c9',
order: vi.fn(() => nome: 'Hotel 1001 Noites',
Promise.resolve({ categorias: ['Standard', 'Superior', 'Luxo'],
data: [ permanencias: ['3hrs', '6hrs', 'Pernoite'],
{ id: 1, name: 'Hotel 1001 Noites', slug: 'hotel-1001-noites', logo_url: null, primary_color: '#C9A961', created_at: '2026-04-13T00:00:00Z' }, descricao: null,
{ id: 2, name: 'Dolce Amore', slug: 'dolce-amore', logo_url: null, primary_color: '#E8B4A0', created_at: '2026-04-13T00:00:00Z' }, ativa: true,
], created_at: '2026-04-13T00:00:00Z',
error: null, 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)** - [ ] **Step 2: Escrever smoke test do App.tsx (TDD: test primeiro)**
@ -1413,8 +1323,7 @@ src/
├── types/ # Tipos gerados do Supabase ├── types/ # Tipos gerados do Supabase
└── __tests__/ # Testes Vitest └── __tests__/ # Testes Vitest
supabase/ supabase/
├── migrations/ # SQL de schema └── migrations/ # SQL de schema (source of truth)
└── seed.sql # Dados iniciais
_poc-reference/ # POC antigo — só pra consultar _poc-reference/ # POC antigo — só pra consultar
``` ```
@ -1493,7 +1402,7 @@ pnpm dev
Abra `http://localhost:5173`: Abra `http://localhost:5173`:
- Título "Reserva Rede 1001" em gradiente dourado ✓ - 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 ✓ - Hover nos cards faz borda brilhar ✓
- Fundo com gradiente obsidian→midnight ✓ - Fundo com gradiente obsidian→midnight ✓
- Fonte serif (Fraunces) nos títulos, sans (Inter) no resto ✓ - 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 efg5678 feat: migra FormField e SelectField com paleta premium
fgh6789 feat: renderiza marcas reais do supabase na pagina inicial fgh6789 feat: renderiza marcas reais do supabase na pagina inicial
ghi7890 feat: gera tipos typescript do supabase 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 ijk9012 feat: configura cliente supabase com variaveis de ambiente
jkl0123 feat: configura tailwind v4 com paleta premium (obsidian/champagne/rose-gold) jkl0123 feat: configura tailwind v4 com paleta premium (obsidian/champagne/rose-gold)
klm1234 feat: scaffold inicial do projeto vite + react + typescript 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 ## Critérios de conclusão da Fase 1
- [ ] `pnpm typecheck`, `pnpm lint`, `pnpm test`, `pnpm build` todos passam - [ ] `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 - [ ] Paleta premium visível e consistente
- [ ] Tipografia Fraunces + Inter carregada - [ ] Tipografia Fraunces + Inter carregada
- [ ] FormField, SelectField e Button prontos para uso (mesmo que não usados ainda) - [ ] FormField, SelectField e Button prontos para uso (mesmo que não usados ainda)