From 7606b336a3eba1342140e5e13d2073a39742cda6 Mon Sep 17 00:00:00 2001 From: Rodribm10 Date: Mon, 13 Apr 2026 22:31:32 -0300 Subject: [PATCH] docs: adiciona plano de implementacao da Fase 1 (Fundacao) Plano detalhado em 13 tasks bite-sized para construir a fundacao do novo app reserva-1001: Vite + React 19 + TS + Tailwind v4 + Supabase + shadcn/ui base, com paleta premium aplicada e schema novo aplicado no banco. Entrega: app rodando com as 4 marcas vindas do Supabase. Fases subsequentes (backend Chatwoot, fluxo publico, admin, polish visual, deploy) viram planos separados. Co-Authored-By: Claude Opus 4.6 (1M context) --- ...2026-04-13-reserva-1001-fase-1-fundacao.md | 1540 +++++++++++++++++ 1 file changed, 1540 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-13-reserva-1001-fase-1-fundacao.md 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 new file mode 100644 index 000000000..f3a02acdb --- /dev/null +++ b/docs/superpowers/plans/2026-04-13-reserva-1001-fase-1-fundacao.md @@ -0,0 +1,1540 @@ +# Reserva Rede 1001 — Fase 1: Fundação (Plano de Implementação) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**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. + +**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 + +**Spec:** `docs/superpowers/specs/2026-04-13-reserva-1001-design.md` + +**Escopo desta fase:** somente fundação. Sem fluxo de reserva, sem checkout, sem admin funcional, sem animações avançadas. A entrega é: "app compila, roda, mostra marcas vindas do Supabase com paleta premium aplicada". + +--- + +## Pré-requisitos (você precisa fazer antes de começar) + +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.** + +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) + +3. **Supabase CLI instalado:** + ```bash + brew install supabase/tap/supabase + supabase --version # deve mostrar >= 1.200.0 + ``` + +4. **pnpm instalado:** + ```bash + which pnpm || npm install -g pnpm + pnpm --version # deve mostrar >= 9 + ``` + +5. **Node 18+:** + ```bash + node --version # v18 ou maior + ``` + +--- + +## File Structure + +Estrutura final ao fim da Fase 1: + +``` +reserva-1001/ +├── .env.local # credenciais (gitignored) +├── .env.local.example # template +├── .gitignore +├── .prettierrc +├── README.md +├── eslint.config.js +├── index.html # sem importmap esm.sh +├── package.json +├── pnpm-lock.yaml +├── tsconfig.json +├── tsconfig.app.json +├── tsconfig.node.json +├── vite.config.ts +├── _poc-reference/ # POC antigo, só pra consulta +│ └── hotel-1001-noites-prime---reserva/ +├── supabase/ +│ ├── config.toml +│ ├── migrations/ +│ │ └── 20260413000001_initial_schema.sql +│ └── seed.sql +└── src/ + ├── main.tsx # entrypoint + ├── App.tsx # shell simples exibindo marcas + ├── index.css # tokens Tailwind v4 + paleta premium + ├── lib/ + │ ├── supabase.ts # cliente Supabase + │ └── utils.ts # cn() helper (clsx + tailwind-merge) + ├── components/ + │ ├── FormField.tsx # migrado do POC, adaptado à paleta + │ ├── SelectField.tsx # migrado do POC, adaptado à paleta + │ └── ui/ + │ └── button.tsx # shadcn/ui Button base + ├── types/ + │ └── database.ts # gerado via supabase gen types + └── __tests__/ + └── App.test.tsx # smoke test Vitest +``` + +--- + +## Task 1: Preparar o diretório (mover POC pra referência, iniciar git) + +**Files:** +- Modify: `reserva-1001/` (estrutura) +- Create: `reserva-1001/.gitignore` + +- [ ] **Step 1: Mover o POC pra subpasta de referência** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +mkdir -p _poc-reference +mv hotel-1001-noites-prime---reserva _poc-reference/ +ls -la +``` + +Expected: agora `reserva-1001/` contém só `_poc-reference/`. + +- [ ] **Step 2: Inicializar git e criar .gitignore** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +git init +``` + +Create `.gitignore`: +``` +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Build +dist/ +build/ +*.tsbuildinfo + +# Env +.env +.env.local +.env.*.local + +# Editor +.vscode/ +.idea/ +*.swp +.DS_Store + +# Logs +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* + +# Test coverage +coverage/ + +# Supabase +supabase/.branches +supabase/.temp +``` + +- [ ] **Step 3: Primeiro commit (só o POC de referência e gitignore)** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +git add .gitignore _poc-reference/ +git commit -m "chore: inicializa repo com POC como referencia" +``` + +--- + +## Task 2: Scaffold do projeto Vite + React + TypeScript + +**Files:** +- Create: `reserva-1001/package.json` +- Create: `reserva-1001/vite.config.ts` +- Create: `reserva-1001/tsconfig.json` +- Create: `reserva-1001/tsconfig.node.json` +- Create: `reserva-1001/index.html` +- Create: `reserva-1001/src/main.tsx` +- Create: `reserva-1001/src/App.tsx` + +- [ ] **Step 1: Criar package.json manualmente (evita o prompt interativo do create-vite)** + +Create `reserva-1001/package.json`: +```json +{ + "name": "reserva-1001", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "lint": "eslint .", + "format": "prettier --write .", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "supabase:types": "supabase gen types typescript --local > src/types/database.ts" + }, + "dependencies": { + "@radix-ui/react-slot": "^1.1.0", + "@supabase/supabase-js": "^2.45.0", + "animejs": "^4.0.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.454.0", + "motion": "^12.4.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "tailwind-merge": "^2.5.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.0", + "@types/node": "^22.9.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^5.0.0", + "eslint": "^9.14.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.12.0", + "jsdom": "^25.0.1", + "prettier": "^3.3.3", + "tailwindcss": "^4.0.0", + "typescript": "^5.6.3", + "typescript-eslint": "^8.14.0", + "vite": "^6.0.0", + "vitest": "^2.1.5" + } +} +``` + +- [ ] **Step 2: Criar tsconfig.json** + +Create `reserva-1001/tsconfig.json`: +```json +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], + "compilerOptions": { + "skipLibCheck": true + } +} +``` + +Create `reserva-1001/tsconfig.app.json`: +```json +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "types": ["vitest/globals", "@testing-library/jest-dom"] + }, + "include": ["src"] +} +``` + +Create `reserva-1001/tsconfig.node.json`: +```json +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} +``` + +- [ ] **Step 3: Criar vite.config.ts com alias @ e Tailwind v4 plugin** + +Create `reserva-1001/vite.config.ts`: +```ts +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import path from 'node:path' + +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + test: { + environment: 'jsdom', + globals: true, + setupFiles: './src/__tests__/setup.ts', + }, +}) +``` + +- [ ] **Step 4: Criar index.html limpo (sem importmap esm.sh)** + +Create `reserva-1001/index.html`: +```html + + + + + + + Reserva Rede 1001 + + + + + +
+ + + +``` + +- [ ] **Step 5: Criar src/main.tsx** + +Create `reserva-1001/src/main.tsx`: +```tsx +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App' + +createRoot(document.getElementById('root')!).render( + + + +) +``` + +- [ ] **Step 6: Criar src/App.tsx placeholder** + +Create `reserva-1001/src/App.tsx`: +```tsx +export default function App() { + return ( +
+

Reserva Rede 1001

+
+ ) +} +``` + +- [ ] **Step 7: Instalar dependências** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +pnpm install +``` + +Expected: instala sem erros. Gera `pnpm-lock.yaml`. + +- [ ] **Step 8: Commit** + +```bash +git add package.json pnpm-lock.yaml tsconfig*.json vite.config.ts index.html src/main.tsx src/App.tsx +git commit -m "feat: scaffold inicial do projeto vite + react + typescript" +``` + +Nota: ainda vai dar erro no build porque `index.css` não existe — resolvido na Task 3. + +--- + +## Task 3: Configurar Tailwind v4 com paleta premium + +**Files:** +- Create: `reserva-1001/src/index.css` + +- [ ] **Step 1: Criar src/index.css com tokens do Tailwind v4 e paleta premium** + +Create `reserva-1001/src/index.css`: +```css +@import 'tailwindcss'; + +@theme { + /* Paleta premium Reserva 1001 */ + --color-obsidian: #0b0d12; + --color-midnight: #0f1a2e; + --color-champagne: #c9a961; + --color-rose-gold: #e8b4a0; + --color-ivory: #f5f1e8; + --color-slate: #6b7280; + --color-emerald: #10b981; + --color-ruby: #e11d48; + + /* Tipografia */ + --font-sans: 'Inter', system-ui, sans-serif; + --font-serif: 'Fraunces', Georgia, serif; + + /* Raios */ + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-2xl: 1.5rem; +} + +@layer base { + html, + body { + height: 100%; + margin: 0; + font-family: var(--font-sans); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + body { + background: linear-gradient(180deg, var(--color-obsidian) 0%, var(--color-midnight) 100%); + color: var(--color-ivory); + } + + h1, + h2, + h3 { + font-family: var(--font-serif); + letter-spacing: -0.02em; + } +} + +@layer utilities { + .text-gradient-gold { + background: linear-gradient(135deg, var(--color-champagne), var(--color-rose-gold)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } + + .glow-champagne { + box-shadow: 0 0 30px rgba(201, 169, 97, 0.4); + } +} +``` + +- [ ] **Step 2: Rodar o dev server pra validar visualmente** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +pnpm dev +``` + +Expected: abre em `http://localhost:5173`, mostra "Reserva Rede 1001" em Fraunces dourado (champagne) sobre fundo escuro com gradiente obsidian→midnight. + +Pare o server com `Ctrl+C` depois de validar. + +- [ ] **Step 3: Commit** + +```bash +git add src/index.css +git commit -m "feat: configura tailwind v4 com paleta premium (obsidian/champagne/rose-gold)" +``` + +--- + +## Task 4: Setup do cliente Supabase + variáveis de ambiente + +**Files:** +- Create: `reserva-1001/.env.local` +- Create: `reserva-1001/.env.local.example` +- Create: `reserva-1001/src/lib/supabase.ts` +- Create: `reserva-1001/src/lib/utils.ts` +- Create: `reserva-1001/src/types/database.ts` (placeholder inicial) + +- [ ] **Step 1: Criar .env.local.example (template commitado)** + +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 + +# 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** + +Create `reserva-1001/.env.local` (substitua os valores pelos do seu projeto Supabase): +``` +VITE_SUPABASE_URL=https://SEU_PROJETO.supabase.co +VITE_SUPABASE_ANON_KEY=eyJhbGc... +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). + +- [ ] **Step 3: Criar src/lib/utils.ts (cn helper)** + +Create `reserva-1001/src/lib/utils.ts`: +```ts +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} +``` + +- [ ] **Step 4: Criar src/types/database.ts inicial (vazio — será regenerado na Task 6)** + +Create `reserva-1001/src/types/database.ts`: +```ts +// Este arquivo é gerado automaticamente via `pnpm supabase:types`. +// Não edite à mão. Regenerar após cada migration. + +export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[] + +export type Database = { + public: { + Tables: Record + Views: Record + Functions: Record + Enums: Record + CompositeTypes: Record + } +} +``` + +- [ ] **Step 5: Criar src/lib/supabase.ts** + +Create `reserva-1001/src/lib/supabase.ts`: +```ts +import { createClient } from '@supabase/supabase-js' +import type { Database } from '@/types/database' + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL +const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY + +if (!supabaseUrl || !supabaseAnonKey) { + throw new Error( + 'Variáveis VITE_SUPABASE_URL e VITE_SUPABASE_ANON_KEY não estão definidas. Copie .env.local.example para .env.local e preencha.' + ) +} + +export const supabase = createClient(supabaseUrl, supabaseAnonKey) +``` + +- [ ] **Step 6: Validar que o typecheck passa** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +pnpm typecheck +``` + +Expected: sem erros. + +- [ ] **Step 7: Commit** + +```bash +git add .env.local.example src/lib/supabase.ts src/lib/utils.ts src/types/database.ts +git commit -m "feat: configura cliente supabase com variaveis de ambiente" +``` + +--- + +## Task 5: Inicializar Supabase CLI local e criar a migration + +**Files:** +- Create: `reserva-1001/supabase/config.toml` (via CLI) +- Create: `reserva-1001/supabase/migrations/20260413000001_initial_schema.sql` +- Create: `reserva-1001/supabase/seed.sql` + +- [ ] **Step 1: Inicializar Supabase CLI no projeto** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +supabase init +``` + +Expected: cria `supabase/config.toml` e `supabase/` folder. Se perguntar sobre VSCode settings, responda N. + +- [ ] **Step 2: Criar migration 001 — schema inicial** + +Create `reserva-1001/supabase/migrations/20260413000001_initial_schema.sql`: +```sql +-- Reserva Rede 1001 — schema inicial +-- Paleta de tabelas: brands, hotel_units, suite_categories, suite_images, +-- pricing, extras, reservations, reservation_extras + +-- === 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() +); + +-- === 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 idx_hotel_units_brand on public.hotel_units(brand_id); +create index idx_hotel_units_chatwoot_unit on public.hotel_units(chatwoot_unit_id); + +-- === 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() +); + +create index idx_suite_categories_unit on public.suite_categories(unit_id); + +-- === 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 idx_suite_images_category on public.suite_images(category_id); + +-- === 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'); + +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) +); + +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) +``` + +- [ ] **Step 3: Criar seed.sql com as 4 marcas** + +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** + +```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" +``` + +--- + +## Task 6: Gerar types TypeScript a partir do schema + +**Files:** +- Modify: `reserva-1001/src/types/database.ts` + +- [ ] **Step 1: Gerar os tipos a partir do projeto remoto** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +supabase gen types typescript --linked > src/types/database.ts +``` + +Expected: `src/types/database.ts` agora contém as definições completas de `Database` com as tabelas `brands`, `hotel_units`, `suite_categories`, etc. + +- [ ] **Step 2: Validar typecheck** + +```bash +pnpm typecheck +``` + +Expected: passa sem erros. + +- [ ] **Step 3: Commit** + +```bash +git add src/types/database.ts +git commit -m "feat: gera tipos typescript do supabase" +``` + +--- + +## Task 7: Renderizar marcas reais no App.tsx + +**Files:** +- Modify: `reserva-1001/src/App.tsx` + +- [ ] **Step 1: Reescrever App.tsx para buscar e exibir marcas do Supabase** + +Replace `reserva-1001/src/App.tsx`: +```tsx +import { useEffect, useState } from 'react' +import { supabase } from '@/lib/supabase' +import type { Database } from '@/types/database' + +type Brand = Database['public']['Tables']['brands']['Row'] + +export default function App() { + const [brands, setBrands] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + async function loadBrands() { + const { data, error } = await supabase + .from('brands') + .select('*') + .order('name', { ascending: true }) + + if (error) { + setError(error.message) + } else { + setBrands(data ?? []) + } + setLoading(false) + } + loadBrands() + }, []) + + return ( +
+
+

+ Experiência exclusiva +

+

+ Reserva Rede 1001 +

+

Escolha uma das nossas marcas para começar

+
+ + {loading &&

Carregando marcas...

} + + {error && ( +
+ Erro ao carregar: {error} +
+ )} + + {!loading && !error && ( +
    + {brands.map((brand) => ( +
  • +

    {brand.name}

    +

    {brand.slug}

    +
  • + ))} +
+ )} + +
+ © 2026 Reserva Rede 1001 · Fase 1 · Fundação +
+
+ ) +} +``` + +- [ ] **Step 2: Validar visualmente no browser** + +```bash +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. + +Pare com `Ctrl+C`. + +- [ ] **Step 3: Commit** + +```bash +git add src/App.tsx +git commit -m "feat: renderiza marcas reais do supabase na pagina inicial" +``` + +--- + +## Task 8: Migrar FormField e SelectField do POC com paleta premium + +**Files:** +- Create: `reserva-1001/src/components/FormField.tsx` +- Create: `reserva-1001/src/components/SelectField.tsx` + +- [ ] **Step 1: Ler os componentes do POC como referência** + +```bash +cat _poc-reference/hotel-1001-noites-prime---reserva/components/FormField.tsx +cat _poc-reference/hotel-1001-noites-prime---reserva/components/SelectField.tsx +``` + +- [ ] **Step 2: Criar FormField.tsx adaptado à nova paleta** + +Create `reserva-1001/src/components/FormField.tsx`: +```tsx +import { forwardRef, type InputHTMLAttributes } from 'react' +import { cn } from '@/lib/utils' + +interface FormFieldProps extends InputHTMLAttributes { + label: string + error?: string + required?: boolean +} + +export const FormField = forwardRef( + ({ label, error, required, className, id, ...props }, ref) => { + const inputId = id ?? `field-${label.toLowerCase().replace(/\s+/g, '-')}` + + return ( +
+ + + {error && {error}} +
+ ) + } +) + +FormField.displayName = 'FormField' +``` + +- [ ] **Step 3: Criar SelectField.tsx adaptado à nova paleta** + +Create `reserva-1001/src/components/SelectField.tsx`: +```tsx +import { forwardRef, type SelectHTMLAttributes } from 'react' +import { ChevronDown } from 'lucide-react' +import { cn } from '@/lib/utils' + +interface Option { + value: string + label: string +} + +interface SelectFieldProps extends Omit, 'children'> { + label: string + options: Option[] + placeholder?: string + error?: string + required?: boolean +} + +export const SelectField = forwardRef( + ( + { label, options, placeholder = 'Selecione...', error, required, className, id, ...props }, + ref + ) => { + const selectId = id ?? `select-${label.toLowerCase().replace(/\s+/g, '-')}` + + return ( +
+ +
+ + +
+ {error && {error}} +
+ ) + } +) + +SelectField.displayName = 'SelectField' +``` + +- [ ] **Step 4: Validar typecheck** + +```bash +pnpm typecheck +``` + +Expected: passa. + +- [ ] **Step 5: Commit** + +```bash +git add src/components/FormField.tsx src/components/SelectField.tsx +git commit -m "feat: migra FormField e SelectField com paleta premium" +``` + +--- + +## Task 9: Criar componente Button base (shadcn/ui style) + +**Files:** +- Create: `reserva-1001/src/components/ui/button.tsx` + +- [ ] **Step 1: Criar Button com cva e variantes premium** + +Create `reserva-1001/src/components/ui/button.tsx`: +```tsx +import { forwardRef, type ButtonHTMLAttributes } from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 rounded-lg font-sans font-semibold transition-all duration-200 disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-champagne/60', + { + variants: { + variant: { + primary: + 'bg-gradient-to-r from-champagne to-rose-gold text-obsidian hover:glow-champagne hover:scale-[1.02]', + secondary: + 'border border-champagne/30 bg-midnight/60 text-ivory hover:border-champagne hover:glow-champagne', + ghost: 'text-champagne hover:bg-champagne/10', + destructive: 'bg-ruby text-ivory hover:bg-ruby/90', + }, + size: { + sm: 'h-9 px-4 text-sm', + md: 'h-11 px-6 text-base', + lg: 'h-14 px-8 text-lg', + }, + }, + defaultVariants: { + variant: 'primary', + size: 'md', + }, + } +) + +interface ButtonProps + extends ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +export const Button = forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button' + return ( + + ) + } +) + +Button.displayName = 'Button' + +export { buttonVariants } +``` + +- [ ] **Step 2: Validar typecheck** + +```bash +pnpm typecheck +``` + +Expected: passa. + +- [ ] **Step 3: Commit** + +```bash +git add src/components/ui/button.tsx +git commit -m "feat: componente Button base com variantes premium" +``` + +--- + +## Task 10: Configurar Vitest + setup file + smoke test + +**Files:** +- Create: `reserva-1001/src/__tests__/setup.ts` +- Create: `reserva-1001/src/__tests__/App.test.tsx` + +- [ ] **Step 1: Criar setup file do Vitest** + +Create `reserva-1001/src/__tests__/setup.ts`: +```ts +import '@testing-library/jest-dom/vitest' +import { cleanup } from '@testing-library/react' +import { afterEach, vi } from 'vitest' + +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, + }) + ), + })), + })), + }, +})) +``` + +- [ ] **Step 2: Escrever smoke test do App.tsx (TDD: test primeiro)** + +Create `reserva-1001/src/__tests__/App.test.tsx`: +```tsx +import { render, screen, waitFor } from '@testing-library/react' +import { describe, it, expect } from 'vitest' +import App from '@/App' + +describe('App', () => { + it('renderiza título premium', () => { + render() + expect(screen.getByRole('heading', { name: /reserva rede 1001/i })).toBeInTheDocument() + }) + + it('exibe marcas retornadas do supabase após carregar', async () => { + render() + await waitFor(() => { + expect(screen.getByText('Hotel 1001 Noites')).toBeInTheDocument() + expect(screen.getByText('Dolce Amore')).toBeInTheDocument() + }) + }) + + it('não mostra estado de loading após fetch completar', async () => { + render() + await waitFor(() => { + expect(screen.queryByText(/carregando marcas/i)).not.toBeInTheDocument() + }) + }) +}) +``` + +- [ ] **Step 3: Rodar os testes** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +pnpm test +``` + +Expected: 3 testes passando. Se não, o App.tsx já foi escrito na Task 7 de forma a passar — provavelmente só um ajuste no mock. + +- [ ] **Step 4: Commit** + +```bash +git add src/__tests__/ +git commit -m "test: smoke test do App com mock do supabase" +``` + +--- + +## Task 11: Configurar ESLint + Prettier + +**Files:** +- Create: `reserva-1001/eslint.config.js` +- Create: `reserva-1001/.prettierrc` +- Create: `reserva-1001/.prettierignore` + +- [ ] **Step 1: Criar eslint.config.js (flat config)** + +Create `reserva-1001/eslint.config.js`: +```js +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import prettier from 'eslint-config-prettier' + +export default tseslint.config( + { ignores: ['dist', '_poc-reference', 'supabase'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended, prettier], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2022, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + }, + } +) +``` + +- [ ] **Step 2: Instalar dependências faltantes do ESLint** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +pnpm add -D @eslint/js +``` + +- [ ] **Step 3: Criar .prettierrc** + +Create `reserva-1001/.prettierrc`: +```json +{ + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 100, + "tabWidth": 2, + "arrowParens": "always", + "endOfLine": "lf" +} +``` + +- [ ] **Step 4: Criar .prettierignore** + +Create `reserva-1001/.prettierignore`: +``` +dist +node_modules +_poc-reference +supabase/.branches +supabase/.temp +pnpm-lock.yaml +``` + +- [ ] **Step 5: Rodar lint e format** + +```bash +pnpm lint +pnpm format +``` + +Expected: lint passa sem erros. Prettier formata os arquivos. + +- [ ] **Step 6: Commit** + +```bash +git add eslint.config.js .prettierrc .prettierignore package.json pnpm-lock.yaml src/ +git commit -m "chore: configura eslint flat config + prettier" +``` + +--- + +## Task 12: Criar README do projeto + +**Files:** +- Create: `reserva-1001/README.md` + +- [ ] **Step 1: Escrever README** + +Create `reserva-1001/README.md`: +```markdown +# Reserva Rede 1001 + +Página pública de reserva para as marcas do Grupo Nova (Hotel 1001 Noites, Prime, Express, Dolce Amore). + +**Status:** Fase 1 — Fundação + +## Stack + +- Vite 6 + React 19 + TypeScript +- Tailwind v4 com paleta premium (obsidian/champagne/rose-gold) +- Supabase (Postgres + Auth + Storage) +- framer-motion + anime.js +- Vitest + Testing Library + +## Setup local + +1. Instale as dependências: + ```bash + pnpm install + ``` + +2. Copie as variáveis de ambiente: + ```bash + cp .env.local.example .env.local + ``` + Preencha `VITE_SUPABASE_URL` e `VITE_SUPABASE_ANON_KEY` com os dados do seu projeto Supabase. + +3. Rode o dev server: + ```bash + pnpm dev + ``` + Abre em `http://localhost:5173`. + +## Comandos + +| Comando | Descrição | +|---|---| +| `pnpm dev` | Dev server com HMR | +| `pnpm build` | Build de produção | +| `pnpm preview` | Preview do build | +| `pnpm lint` | Roda ESLint | +| `pnpm format` | Formata com Prettier | +| `pnpm test` | Roda testes (Vitest) | +| `pnpm test:watch` | Testes em watch mode | +| `pnpm typecheck` | TypeScript check | +| `pnpm supabase:types` | Regenera tipos do Supabase | + +## Estrutura + +``` +src/ +├── components/ # Componentes React +│ └── ui/ # Primitivos shadcn/ui +├── lib/ # Clientes (supabase) e utils +├── types/ # Tipos gerados do Supabase +└── __tests__/ # Testes Vitest +supabase/ +├── migrations/ # SQL de schema +└── seed.sql # Dados iniciais +_poc-reference/ # POC antigo — só pra consultar +``` + +## Paleta premium + +| Token | Hex | Uso | +|---|---|---| +| `obsidian` | `#0B0D12` | Fundo principal | +| `midnight` | `#0F1A2E` | Superfícies elevadas | +| `champagne` | `#C9A961` | Ação primária, luxo | +| `rose-gold` | `#E8B4A0` | Acento secundário | +| `ivory` | `#F5F1E8` | Texto principal | +| `slate` | `#6B7280` | Texto secundário | +| `emerald` | `#10B981` | Sucesso | +| `ruby` | `#E11D48` | Erro | + +## Integração com Chatwoot + +Esta fase **não** integra com Chatwoot ainda. A geração de PIX acontece na Fase 2, onde um endpoint novo é criado no Chatwoot (`POST /public/api/v1/captain/public_reservations`) e esta app passa a consumir. + +## Referências + +- Spec de design: `chatwoot/docs/superpowers/specs/2026-04-13-reserva-1001-design.md` +- Plano Fase 1: `chatwoot/docs/superpowers/plans/2026-04-13-reserva-1001-fase-1-fundacao.md` +``` + +- [ ] **Step 2: Commit** + +```bash +git add README.md +git commit -m "docs: adiciona README da fase 1" +``` + +--- + +## Task 13: Validação final — rodar tudo e conferir + +- [ ] **Step 1: Typecheck** + +```bash +cd /Users/user/Dev/Produtos/Chatwoot-fazer-ai/fazer-ai-kanban/reserva-1001 +pnpm typecheck +``` + +Expected: sem erros. + +- [ ] **Step 2: Lint** + +```bash +pnpm lint +``` + +Expected: sem erros. + +- [ ] **Step 3: Testes** + +```bash +pnpm test +``` + +Expected: 3 testes passando. + +- [ ] **Step 4: Build** + +```bash +pnpm build +``` + +Expected: build sem erros, gera `dist/`. + +- [ ] **Step 5: Dev server + inspeção visual** + +```bash +pnpm dev +``` + +Abra `http://localhost:5173`: +- Título "Reserva Rede 1001" em gradiente dourado ✓ +- 4 marcas 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 ✓ + +Pare com `Ctrl+C`. + +- [ ] **Step 6: git log pra conferir a trilha de commits** + +```bash +git log --oneline +``` + +Expected: algo parecido com: +``` +abc1234 docs: adiciona README da fase 1 +bcd2345 chore: configura eslint flat config + prettier +cde3456 test: smoke test do App com mock do supabase +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 +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 +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 +- [ ] Paleta premium visível e consistente +- [ ] Tipografia Fraunces + Inter carregada +- [ ] FormField, SelectField e Button prontos para uso (mesmo que não usados ainda) +- [ ] Schema Supabase aplicado e tipos gerados +- [ ] README funcional +- [ ] Commits atômicos e descritivos + +## Próxima fase + +**Fase 2 — Backend Chatwoot:** criar o controller `PublicReservationsController` no repo do Chatwoot com os endpoints `POST /public_reservations` e `GET /public_reservations/:id/status`, com token auth e testes RSpec. Plano separado: `docs/superpowers/plans/2026-04-13-reserva-1001-fase-2-backend-chatwoot.md` (a escrever).