sass-imobiliaria/.specify/features/001-homepage/tasks.md

449 lines
33 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Tasks: Homepage (Página Inicial)
**Feature**: `001-homepage`
**Branch**: `001-homepage`
**Input**: `spec.md`, `plan.md`, `DESIGN.md`, `.specify/memory/constitution.md`
**Generated**: 2026-04-13
**Status**: Ready for implementation
---
## Format
```
- [ ] T[NNN] [P?] [USN?] Description — path/to/file.ext
```
- **[P]** — Paralelizável (arquivo diferente, sem dependência de tarefa incompleta)
- **[USN]** — User Story associada (US1US4)
- IDs sequenciais na ordem de execução recomendada
---
## Phase 1: Setup — Scaffolding do Projeto
**Objetivo**: Criar a estrutura de pastas, dependências e arquivos de configuração. Nenhuma lógica de negócio ainda.
| ID | Complexidade | Deps | spec_ref |
|----|-------------|------|----------|
| T001 | S | — | plan.md §1.1 |
| T002 | S | T001 | plan.md §1.1 |
| T003 | S | T001 | plan.md §1.1 |
| T004 | S | T001 | plan.md §1.1 |
| T005 | S | T001 | plan.md §1.1 |
| T006 | M | — | plan.md §1.3 |
| T007 | S | T006 | plan.md §1.3 |
| T008 | M | T007 | plan.md §3.1, DESIGN.md |
| T009 | S | T007 | plan.md §3.1 |
| T010 | S | T006 | plan.md §1.3 |
- [X] T001 Criar estrutura de diretórios do backend — `backend/app/models/`, `backend/app/schemas/`, `backend/app/routes/`, `backend/seeds/`, `backend/tests/`, `backend/migrations/`
- **Done when**: Todos os diretórios existem conforme `plan.md` project structure; `__init__.py` vazio em cada subpacote Python.
- [X] T002 Criar `backend/pyproject.toml` com dependências Flask, SQLAlchemy, Flask-Migrate, Flask-CORS, Pydantic v2, psycopg2-binary, python-dotenv, pytest, pytest-flask — `backend/pyproject.toml`
- **Done when**: `uv sync` executa sem erro; `uv run python -c "import flask; import pydantic"` passa.
- [X] T003 Criar `backend/app/config.py` com `DevelopmentConfig`, `ProductionConfig`, `TestingConfig` lendo `DATABASE_URL`, `SECRET_KEY`, `CORS_ORIGINS` de variáveis de ambiente — `backend/app/config.py`
- **Done when**: `from app.config import config` importa sem erro; chave ausente levanta `KeyError` explícito.
- [X] T004 Criar `backend/app/extensions.py` com instâncias únicas `db = SQLAlchemy()`, `migrate = Migrate()`, `cors = CORS()``backend/app/extensions.py`
- **Done when**: `from app.extensions import db, migrate, cors` importa sem erro; nenhuma extensão é inicializada neste arquivo (apenas instanciada).
- [X] T005 Criar `backend/.env.example` com `DATABASE_URL`, `SECRET_KEY`, `CORS_ORIGINS`, `FLASK_ENV``backend/.env.example`
- **Done when**: Arquivo presente sem valores reais; todos os campos obrigatórios do `config.py` cobertos.
- [X] T006 [P] Criar projeto frontend com `npm create vite@latest frontend -- --template react-ts``frontend/`
- **Done when**: `cd frontend && npm run dev` sobe em `localhost:5173` sem erros.
- [X] T007 Instalar dependências do frontend: `tailwindcss`, `postcss`, `autoprefixer`, `axios`, `react-router-dom`, `@types/react-router-dom``frontend/package.json`
- **Done when**: `npm install` completa; `node_modules/tailwindcss` e `node_modules/axios` existem; `npm run build` passa.
- [X] T008 Criar `frontend/tailwind.config.ts` com todos os tokens de `DESIGN.md`: cores `mkt-black`, `panel-dark`, `surface-elevated`, `brand-indigo`, `accent-violet`, `accent-hover`, pesos `medium: 510`, `semibold: 590`, letter-spacing para display sizes, fontFamily Inter Variable — `frontend/tailwind.config.ts`
- **Done when**: `npx tailwindcss --input src/index.css --output /dev/null` compila sem aviso; classe `bg-mkt-black` e `text-brand-indigo` existem no output CSS gerado.
- [X] T009 Configurar `frontend/src/index.css`: importar Tailwind (`@tailwind base/components/utilities`), `@import` Inter Variable via Google Fonts, `@layer base` com `font-feature-settings: "cv01", "ss03"` e `body { @apply bg-mkt-black text-text-primary font-sans antialiased }``frontend/src/index.css`
- **Done when**: Página abre com fundo `#08090a` e fonte Inter Variable sem inline style.
- [X] T010 [P] Configurar `frontend/vite.config.ts` com proxy `/api``http://localhost:5000` e `changeOrigin: true``frontend/vite.config.ts`
- **Done when**: `npm run build` passa sem erros TypeScript; proxy configurado no bloco `server.proxy`.
**Checkpoint Phase 1**: `uv sync` e `npm run dev` funcionam; estrutura de pastas completa conforme `plan.md`.
---
## Phase 2: Foundational — Infraestrutura Flask + PostgreSQL
**Objetivo**: Flask app factory funcional, banco de dados conectado, Flask-Migrate inicializado. Bloqueia todas as fases de user story.
**⚠️ CRÍTICO**: Nenhuma User Story pode ser implementada antes desta fase estar completa.
| ID | Complexidade | Deps | spec_ref |
|----|-------------|------|----------|
| T011 | S | T005 | plan.md §1.2 |
| T012 | M | T002, T003, T004 | plan.md §1.1 |
| T013 | S | T011, T012 | plan.md §1.2 |
| T014 | S | T013 | plan.md §1.2 |
- [X] T011 Iniciar container PostgreSQL local: `docker run -d --name saas_imob_db -e POSTGRES_USER=imob -e POSTGRES_PASSWORD=imob_dev -e POSTGRES_DB=saas_imobiliaria -p 5432:5432 postgres:16-alpine`; copiar `backend/.env.example` para `backend/.env` e preencher com valores locais — `backend/.env`
- **Done when**: `docker ps` mostra container `saas_imob_db` Running; `psql postgresql://imob:imob_dev@localhost:5432/saas_imobiliaria -c "\l"` lista o banco.
- [X] T012 Criar `backend/app/__init__.py` com `create_app(config_name="default")` que inicializa `db`, `migrate`, `cors` e registra blueprints de `app.routes``backend/app/__init__.py`
- **Done when**: `uv run flask --app app shell` abre sem traceback; `db.engine.connect()` no shell retorna sem erro.
- [X] T013 Inicializar Flask-Migrate: `uv run flask --app app db init` dentro de `backend/``backend/migrations/`
- **Done when**: Pasta `backend/migrations/` criada com `env.py` e `versions/` pelo Alembic.
- [X] T014 Confirmar conexão DB no shell Flask: `db.engine.connect()` — nenhum arquivo criado
- **Done when**: Comando retorna `Connection` sem exceção; PostgreSQL aceita a conexão com `DATABASE_URL` do `.env`.
**Checkpoint Phase 2**: `uv run flask --app app db init` ok; `uv run pytest` passa (0 testes, setup ok).
---
## Phase 3: User Story 4 — Admin Configura Conteúdo da Homepage (Priority: P1)
**Goal**: API backend completamente funcional: `GET /api/v1/homepage-config` e `GET /api/v1/properties?featured=true` retornando JSON válido com dados do seeder.
**Independent Test**: `curl http://localhost:5000/api/v1/homepage-config` retorna `200` com JSON contendo `hero_headline`; `curl "http://localhost:5000/api/v1/properties?featured=true"` retorna array de até 6 imóveis; `uv run pytest` passa nos dois arquivos de teste.
| ID | Complexidade | Deps | spec_ref |
|----|-------------|------|----------|
| T015 | M | T012 | plan.md §2.1, spec.md US4, FR-006, FR-008 |
| T016 | S | T012 | plan.md §2.1, spec.md US4, FR-005 |
| T017 | M | T015 | plan.md §2.2, spec.md FR-007 |
| T018 | M | T016 | plan.md §2.2, spec.md FR-005, FR-016 |
| T019 | S | T015, T016 | plan.md §2.4 |
| T020 | S | T019 | plan.md §2.4 |
| T021 | M | T018, T020 | plan.md §2.3, spec.md US4 scenario 1 |
| T022 | M | T017, T020 | plan.md §2.3, spec.md US2, FR-006009 |
| T023 | S | T021, T022 | plan.md §1.1 |
| T024 | M | T023 | plan.md §2.5, spec.md US4 scenario 2 |
| T025 | S | T012 | plan.md §2 |
| T026 | M | T025, T021 | plan.md §2, spec.md US4 scenarios 13 |
| T027 | M | T025, T022 | plan.md §2, spec.md US2 scenarios 12, FR-010 |
- [X] T015 [P] [US4] Criar `backend/app/models/property.py` com classes `Property` (UUID pk, title, slug, address, price Numeric(12,2), type enum `venda|aluguel`, bedrooms, bathrooms, area_m2, is_featured, is_active, created_at) e `PropertyPhoto` (id, property_id FK CASCADE, url, alt_text, display_order) com relationship order_by display_order — `backend/app/models/property.py`
- **Done when**: `from app.models.property import Property, PropertyPhoto` importa sem erro; campos declarados exatamente conforme `plan.md §2.1`; `price` usa `Numeric(12,2)`, nunca `Float`.
- [X] T016 [P] [US4] Criar `backend/app/models/homepage.py` com classe `HomepageConfig` (id Integer PK, hero_headline String(120) NOT NULL, hero_subheadline String(240) nullable, hero_cta_label String(40) default "Ver Imóveis", hero_cta_url String(200) default "/imoveis", featured_properties_limit Integer default 6, updated_at DateTime server_default+onupdate) — `backend/app/models/homepage.py`
- **Done when**: `from app.models.homepage import HomepageConfig` importa sem erro; `hero_headline` é NOT NULL no modelo; `featured_properties_limit` tem default 6.
- [X] T017 [US4] Criar `backend/app/schemas/property.py` com `PropertyPhotoOut` e `PropertyOut` (Pydantic v2, `model_config = ConfigDict(from_attributes=True)`, `price: Decimal`, `type: Literal["venda", "aluguel"]`, `photos: list[PropertyPhotoOut]`) — `backend/app/schemas/property.py`
- **Done when**: `PropertyOut.model_validate(property_instance)` funciona em teste manual; `price` serializa como `Decimal` (não float).
- [X] T018 [US4] Criar `backend/app/schemas/homepage.py` com `HomepageConfigOut` e `HomepageConfigIn` (Pydantic v2); `HomepageConfigIn` com `@field_validator("hero_headline")` rejeitando string vazia e `@field_validator("featured_properties_limit")` rejeitando valores fora de 112 — `backend/app/schemas/homepage.py`
- **Done when**: `HomepageConfigIn(hero_headline="")` levanta `ValidationError`; `HomepageConfigIn(featured_properties_limit=13)` levanta `ValidationError`; instância válida passa.
- [X] T019 [US4] Gerar migração inicial com Flask-Migrate: `uv run flask --app app db migrate -m "initial schema: properties, property_photos, homepage_config"``backend/migrations/versions/`
- **Done when**: Arquivo de migração criado em `backend/migrations/versions/`; revisão manual confirma tabelas `properties`, `property_photos`, `homepage_config` e enum `property_type` presentes.
- [X] T020 [US4] Aplicar migração e testar ciclo upgrade/downgrade: `flask db upgrade`, `flask db downgrade base`, `flask db upgrade` — banco de dados
- **Done when**: Ambos os comandos executam sem erro; tabelas existem no banco após upgrade final; `\dt` no psql lista as três tabelas.
- [X] T021 [US4] Criar `backend/app/routes/homepage.py` com Blueprint `homepage_bp` e rota `GET /api/v1/homepage-config` retornando `HomepageConfigOut.model_validate(config).model_dump()` ou `404` se nenhum registro — `backend/app/routes/homepage.py`
- **Done when**: `curl http://localhost:5000/api/v1/homepage-config` retorna `200` com JSON contendo `hero_headline` após seeder; retorna `404` se tabela vazia.
- [X] T022 [US4] Criar `backend/app/routes/properties.py` com Blueprint `properties_bp` e rota `GET /api/v1/properties` que filtra por `is_active=True`; quando `featured=true`, filtra por `is_featured=True`, ordena por `created_at DESC`, aplica `limit` de `HomepageConfig.featured_properties_limit` (fallback 6) — `backend/app/routes/properties.py`
- **Done when**: `curl "http://localhost:5000/api/v1/properties?featured=true"` retorna array JSON; quando nenhum imóvel featured, retorna `[]` (não 500); limite máximo respeitado conforme config.
- [X] T023 [US4] Registrar `homepage_bp` e `properties_bp` no `create_app()` de `backend/app/__init__.py`; importar models de `property` e `homepage` para garantir que Flask-Migrate os detecte — `backend/app/__init__.py`
- **Done when**: `flask routes` lista `/api/v1/homepage-config` e `/api/v1/properties`; CORS permite `http://localhost:5173`.
- [X] T024 [US4] Criar `backend/seeds/seed.py` que apaga e recria: 1 `HomepageConfig` com headline/subheadline/cta configurados e 6 `Property` com `is_featured=True`, tipos variados (venda/aluguel), e pelo menos 1 `PropertyPhoto` por imóvel usando URLs do `picsum.photos``backend/seeds/seed.py`
- **Done when**: `uv run python seeds/seed.py` executa sem erro; `GET /api/v1/properties?featured=true` retorna exatamente 6 imóveis; cada imóvel tem `photos` com pelo menos 1 entrada.
- [X] T025 [US4] Criar `backend/tests/conftest.py` com fixture `app` (usando `TestingConfig` com SQLite em memória ou PostgreSQL de teste) e fixture `client` (`app.test_client()`) — `backend/tests/conftest.py`
- **Done when**: `uv run pytest --collect-only` descobre conftest sem error; fixture `client` disponível nos testes.
- [X] T026 [P] [US4] Criar `backend/tests/test_homepage.py` com testes: (1) `GET /api/v1/homepage-config``200` com campos obrigatórios; (2) `GET /api/v1/homepage-config` sem registro → `404`; (3) `HomepageConfigIn(hero_headline="")``ValidationError``backend/tests/test_homepage.py`
- **Done when**: `uv run pytest tests/test_homepage.py -v` passa com 3 testes verdes.
- [X] T027 [P] [US4] Criar `backend/tests/test_properties.py` com testes: (1) `GET /api/v1/properties?featured=true``200` com array; (2) resultado contém campos `id`, `title`, `slug`, `price`, `type`, `bedrooms`, `bathrooms`, `area_m2`, `photos`; (3) sem imóveis featured → `200` com `[]` (não 500) — `backend/tests/test_properties.py`
- **Done when**: `uv run pytest tests/test_properties.py -v` passa com 3 testes verdes.
**Checkpoint Phase 3 (US4)**: `uv run pytest` passa; ambos os endpoints retornam JSON válido; seeder popula 6 imóveis.
---
## Phase 4: User Story 1 — Visitante Experimenta o Hero e a Navegação (Priority: P1) 🎯 MVP
**Goal**: Navbar sticky e HeroSection renderizando com conteúdo real da API; fallback silencioso quando API falha; responsivo em todos os breakpoints.
**Independent Test**: Abrir `http://localhost:5173` com backend rodando — Navbar exibe logo + links; Hero exibe headline da API; CTA redireciona para `/imoveis`; sem backend, FALLBACK_CONFIG é exibido silenciosamente.
| ID | Complexidade | Deps | spec_ref |
|----|-------------|------|----------|
| T028 | S | T010 | plan.md §3.2, spec.md US1 |
| T029 | S | T010 | plan.md §3.2, spec.md US2 |
| T030 | S | T028 | plan.md §3.3 |
| T031 | S | T030 | plan.md §3.3 |
| T032 | S | T030 | plan.md §3.3 |
| T033 | M | T008, T009 | plan.md §3.4, spec.md FR-001003, NFR-004 |
| T034 | L | T031, T033 | plan.md §3.4, spec.md FR-004, US1 scenarios 15, NFR-006 |
| T035 | M | T031, T034 | plan.md §3.5, spec.md US1 scenario 4, edge-cases |
| T036 | S | T035 | plan.md §3.6 |
| T037 | S | T036 | plan.md §3.6 |
| T038 | S | T007 | plan.md §3.4, spec.md FR-011 |
- [X] T028 [P] [US1] Criar `frontend/src/types/homepage.ts` com interface `HomepageConfig` (hero_headline, hero_subheadline `string | null`, hero_cta_label, hero_cta_url, featured_properties_limit) — `frontend/src/types/homepage.ts`
- **Done when**: Arquivo exporta interface sem erro TypeScript; todos os campos opcionais/nullable corretamente tipados.
- [X] T029 [P] [US1] Criar `frontend/src/types/property.ts` com interfaces `PropertyPhoto` (url, alt_text, display_order) e `Property` (id, title, slug, price `string`, type `'venda' | 'aluguel'`, bedrooms, bathrooms, area_m2, is_featured, photos) — `frontend/src/types/property.ts`
- **Done when**: Arquivo exporta ambas as interfaces sem erro TypeScript; `price` é `string` (Decimal serializado do backend).
- [X] T030 [US1] Criar `frontend/src/services/api.ts` com instância Axios (`baseURL: '/api/v1'`, `timeout: 8000`, header `Content-Type: application/json`) — `frontend/src/services/api.ts`
- **Done when**: `import { api } from './api'` compila sem erro; baseURL aponta para `/api/v1`.
- [X] T031 [US1] Criar `frontend/src/services/homepage.ts` com `getHomepageConfig(): Promise<HomepageConfig>` chamando `api.get<HomepageConfig>('/homepage-config')``frontend/src/services/homepage.ts`
- **Done when**: Função exportada compila sem erro TypeScript; retorna tipo `Promise<HomepageConfig>`.
- [X] T032 [US1] Criar `frontend/src/services/properties.ts` com `getFeaturedProperties(): Promise<Property[]>` chamando `api.get<Property[]>('/properties', { params: { featured: 'true' } })``frontend/src/services/properties.ts`
- **Done when**: Função exportada compila sem erro TypeScript; retorna tipo `Promise<Property[]>`.
- [X] T033 [US1] Criar `frontend/src/components/Navbar.tsx`: header semântico (`<header role="banner"><nav aria-label="Navegação principal">`), fundo `rgba(8,9,10,0.85)` + `backdrop-blur-navbar` (classe Tailwind), sticky `z-50`, border-bottom `border-white/5`, logo à esquerda, links "Imóveis"/"Sobre"/"Contato" à direita (`text-sm text-text-secondary hover:text-text-primary`), hamburger menu em mobile (<768px) com toggle de estado `frontend/src/components/Navbar.tsx`
- **Done when**: Navbar visível sticky ao scroll; hamburger aparece em `<768px`; links navegáveis por teclado com foco visível (NFR-008); fundo com blur confirmado visualmente.
- [X] T034 [US1] Criar `frontend/src/components/HeroSection.tsx` com props `{ headline, subheadline, ctaLabel, ctaUrl }`: fundo `#08090a` + gradiente radial `rgba(94,106,210,0.08)`, headline com `text-[72px] tracking-display-xl font-medium` em desktop / `text-[48px]` tablet / `text-[40px]` mobile, subheadline `text-xl text-text-secondary font-light` (não renderizado quando `null`), botão CTA `bg-brand-indigo hover:bg-accent-hover rounded text-white font-semibold transition-colors duration-200` com focus ring, skeleton de 3 linhas animadas quando `isLoading=true` `frontend/src/components/HeroSection.tsx`
- **Done when**: Hero renderiza headline + subheadline da API; `subheadline=null` não insere elemento vazio; tipografia escala nos 3 breakpoints; CTA navega para `ctaUrl`; foco visível no botão (NFR-008); skeleton exibido quando `isLoading`.
- [X] T035 [US1] Criar `frontend/src/pages/HomePage.tsx` com `FALLBACK_CONFIG` estático, `useState<HomepageConfig>(FALLBACK_CONFIG)`, `useEffect(() => getHomepageConfig().then(setConfig).catch(() => {}), [])`, e renderização de `<Navbar>` + `<HeroSection>` com props do config `frontend/src/pages/HomePage.tsx`
- **Done when**: Página carrega com conteúdo da API após 1 request; com API indisponível, exibe `FALLBACK_CONFIG` silenciosamente (sem erro visível); `isLoading` passado para HeroSection durante fetch.
- [X] T036 [US1] Criar `frontend/src/App.tsx` com `<BrowserRouter><Routes><Route path="/" element={<HomePage />} /></Routes></BrowserRouter>` `frontend/src/App.tsx`
- **Done when**: `npm run build` compila sem erro; `http://localhost:5173/` renderiza `HomePage`.
- [X] T037 [US1] Atualizar `frontend/src/main.tsx` para usar `<React.StrictMode><App /></React.StrictMode>` com importação de `./index.css` `frontend/src/main.tsx`
- **Done when**: `npm run dev` não lança erro de StrictMode; CSS global carregado.
- [X] T038 [US1] Adicionar imagem placeholder `frontend/public/placeholder-property.jpg` (imagem minimalista 100 KB, 16:9, tema imobiliário ou cinza neutro) `frontend/public/placeholder-property.jpg`
- **Done when**: Arquivo presente em `public/`; acessível em `http://localhost:5173/placeholder-property.jpg`; 100 KB.
**Checkpoint Phase 4 (US1)**: Homepage abre com Navbar + Hero; hero exibe dados reais; fallback silencioso funciona; responsivo mobile/tablet/desktop.
---
## Phase 5: User Story 2 — Visitante Explora Imóveis em Destaque (Priority: P1)
**Goal**: Grade de PropertyCards responsiva (123 colunas) renderizando dados reais da API, com skeleton durante loading, fallback para grade vazia e placeholder para imóveis sem foto.
**Independent Test**: Abrir homepage com seeder rodado grade exibe 6 cards com foto, título, preço formatado em BRL, badge de tipo, quartos/banheiros/área; durante load exibe 3 skeletons; com array vazio exibe mensagem; imóvel sem foto exibe placeholder.
| ID | Complexidade | Deps | spec_ref |
|----|-------------|------|----------|
| T039 | L | T029, T008 | plan.md §3.4, spec.md FR-007, US2 scenarios 14, NFR-007 |
| T040 | S | T039 | plan.md §3.4, spec.md edge-cases |
| T041 | M | T032, T039, T040 | plan.md §3.4, spec.md FR-006011, US2, NFR-005 |
| T042 | S | T041, T035 | plan.md §3.5 |
- [X] T039 [US2] Criar `frontend/src/components/PropertyCard.tsx` com props `{ property: Property }`: container `rounded-xl border border-white/5 bg-surface-elevated overflow-hidden cursor-pointer hover:border-white/[0.08] transition-all duration-200`, foto `aspect-[16/9] w-full object-cover rounded-t-xl` (usa `placeholder-property.jpg` quando `photos.length === 0`, `alt={property.title}` NFR-007), badge de tipo pill (`rounded-full text-xs font-medium px-2.5 py-1`, Venda: `bg-brand-indigo/20 text-accent-violet`, Aluguel: `bg-white/5 text-text-secondary border border-white/10`), preço formatado com `Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' })` em `text-lg font-medium text-text-primary`, stats (quartos/banheiros/área) com ícone SVG + `text-sm text-text-secondary`, clique navega para `/imoveis/{property.slug}` `frontend/src/components/PropertyCard.tsx`
- **Done when**: Card renderiza todos os campos obrigatórios (FR-007); placeholder exibido quando sem foto (FR-011); preço formatado como `R$ 750.000,00`; navegação para `/imoveis/slug` funciona; alt text presente em todas as imagens (NFR-007).
- [X] T040 [US2] Criar `frontend/src/components/PropertyCardSkeleton.tsx` com estrutura idêntica ao PropertyCard usando `animate-pulse`, blocos `bg-surface-secondary rounded` no lugar de foto, badge, preço e stats `frontend/src/components/PropertyCardSkeleton.tsx`
- **Done when**: Componente renderiza sem props; altura/largura compatíveis com PropertyCard; animação pulso visível.
- [X] T041 [US2] Criar `frontend/src/components/FeaturedProperties.tsx` com estados `loading | success | error | empty` gerenciados via `useEffect(() => getFeaturedProperties()...)`: loading 3 `<PropertyCardSkeleton>`, success com dados `grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6` de `<PropertyCard>`, success sem dados (`[]`) mensagem "Nenhum imóvel em destaque no momento" centralizada, error mensagem de fallback sem stack trace `frontend/src/components/FeaturedProperties.tsx`
- **Done when**: 3 skeletons exibidos durante fetch; grid 123 colunas conforme breakpoints (NFR-005); estado vazio não quebra layout; estado error mostra mensagem amigável (spec edge-case: API indisponível).
- [X] T042 [US2] Integrar `<FeaturedProperties />` em `frontend/src/pages/HomePage.tsx` após `<HeroSection>` `frontend/src/pages/HomePage.tsx`
- **Done when**: Seção de imóveis em destaque visível abaixo do hero; título da seção "Imóveis em Destaque" presente; dados reais exibidos após fetch.
**Checkpoint Phase 5 (US2)**: Grade responsiva funcionando com dados reais; skeleton durante loading; placeholder para fotos ausentes; vazio sem erro.
---
## Phase 6: User Story 3 — Visitante Descobre a Agência e Inicia Contato (Priority: P2)
**Goal**: Seções About, CTA e Footer implementadas com conteúdo estático, visualmente alinhadas ao DESIGN.md.
**Independent Test**: Rolar a homepage além da grade de imóveis AboutSection exibe nome + descrição da agência; CTASection exibe convite ao contato com elemento acionável; Footer exibe informações de contato e links de navegação.
| ID | Complexidade | Deps | spec_ref |
|----|-------------|------|----------|
| T043 | S | T008 | plan.md §3.4, spec.md FR-012, US3 scenario 1 |
| T044 | S | T008 | plan.md §3.4, spec.md FR-013, US3 scenario 2 |
| T045 | S | T008 | plan.md §3.4, spec.md FR-014, US3 scenario 3, NFR-010 |
| T046 | S | T042 | plan.md §3.5 |
- [ ] T043 [US3] Criar `frontend/src/components/AboutSection.tsx` com fundo `bg-panel-dark`, título "Sobre Nós" `text-3xl font-medium tracking-h1 text-text-primary`, parágrafo de descrição da agência `text-base text-text-secondary leading-relaxed`, padding vertical `py-20 md:py-[80px]`, max-width 1200px centralizado `frontend/src/components/AboutSection.tsx`
- **Done when**: Seção visível ao rolar; nome e descrição presentes; background `#0f1011` confirmado; padding conforme DESIGN.md.
- [ ] T044 [US3] Criar `frontend/src/components/CTASection.tsx` com fundo `bg-surface-elevated border-t border-white/5`, título de convite ao contato, telefone ou e-mail como elemento acionável (`<a href="tel:..."` ou `<a href="mailto:..."`) estilizado como botão outline ou link proeminente, padding vertical `py-20` `frontend/src/components/CTASection.tsx`
- **Done when**: Elemento acionável presente e clicável; navegável por teclado com foco visível (NFR-008); seção visível antes do rodapé.
- [ ] T045 [US3] Criar `frontend/src/components/Footer.tsx` com `<footer role="contentinfo">` (NFR-010), fundo `bg-panel-dark border-t border-white/5`, informações de contato (e-mail e/ou telefone), links de navegação "Imóveis"/"Sobre"/"Contato" `text-xs text-text-tertiary hover:text-text-secondary`, copyright com ano atual `frontend/src/components/Footer.tsx`
- **Done when**: Tag semântica `<footer role="contentinfo">` presente; informações de contato exibidas; links navegáveis por teclado; copyright visível.
- [ ] T046 [US3] Integrar `<AboutSection />`, `<CTASection />` e `<Footer />` em `frontend/src/pages/HomePage.tsx` após `<FeaturedProperties>` `frontend/src/pages/HomePage.tsx`
- **Done when**: Todas as seções visíveis ao rolar a página completa; ordem: Navbar Hero FeaturedProperties AboutSection CTASection Footer.
**Checkpoint Phase 6 (US3)**: Homepage completa visualmente; todas as seções visíveis; estrutura semântica HTML5 correta em toda a página.
---
## Phase 7: Polish & Cross-Cutting Concerns
**Objetivo**: Integração E2E verificada, edge cases cobertos, conformidade visual com DESIGN.md, acessibilidade WCAG 2.1 AA, performance.
| ID | Complexidade | Deps | spec_ref |
|----|-------------|------|----------|
| T047 | M | T046 | plan.md §4.1, spec.md US4 scenarios 12 |
| T048 | S | T047 | plan.md §4.2, spec.md edge-cases |
| T049 | M | T047 | plan.md §4.2, spec.md edge-cases |
| T050 | M | T046 | plan.md §4.3, spec.md NFR-004006 |
| T051 | M | T046 | plan.md §4.3, spec.md NFR-007010 |
| T052 | S | T046 | plan.md §4.3, spec.md NFR-009 |
| T053 | M | T047 | plan.md §4.4, spec.md NFR-001003 |
- [ ] T047 Verificar integração E2E completa com backend e frontend rodando: hero exibe headline real da API, grade exibe 6 imóveis do seeder, atualizar headline via `PATCH /api/v1/homepage-config` no shell e confirmar que reload da homepage reflete novo texto (FR-005, US4 scenario 1); confirmar que CORS aceita apenas `http://localhost:5173` nenhum arquivo criado
- **Done when**: Todos os dados dinâmicos vêm da API; headline atualizado via API reflete no próximo reload; CORS não aceita origens não configuradas.
- [ ] T048 [P] Verificar fallback e estados de erro: (1) parar backend e confirmar que FALLBACK_CONFIG é exibido silenciosamente; (2) `FeaturedProperties` com API indisponível exibe mensagem de fallback (não 500); (3) nenhum stack trace visível ao usuário nenhum arquivo criado
- **Done when**: Página renderiza versão degradada sem crash visível; console do browser não lança erros não tratados.
- [ ] T049 [P] Verificar edge cases: (a) subheadline `null` HeroSection não renderiza elemento vazio; (b) headline com 120 caracteres sem overflow no hero; (c) imóvel sem photo placeholder exibido; (d) featured vazio (`[]`) mensagem "Nenhum imóvel em destaque" sem colapso de seção nenhum arquivo criado
- **Done when**: Todos os 4 casos verificados visualmente; nenhum layout quebrado.
- [ ] T050 [P] Verificar responsividade nos breakpoints 320px, 375px, 768px, 1024px, 1280px, 1440px via DevTools: tipografia do hero escala (40px/48px/72px conforme NFR-006), grade 123 colunas (NFR-005), Navbar hamburger em <768px, nenhum overflow horizontal nenhum arquivo criado
- **Done when**: Todos os 6 breakpoints verificados sem overflow; tipografia e grade conforme NFR-004006.
- [ ] T051 [P] Verificar acessibilidade: (a) todas as imagens têm `alt` descritivos (NFR-007); (b) tab order lógico por toda a página; (c) botões/links com foco visível (NFR-008); (d) elementos semânticos `<header>`, `<nav>`, `<main>`, `<footer>` presentes (NFR-010); (e) roles ARIA corretos nenhum arquivo criado
- **Done when**: Axe DevTools (ou similar) sem violações críticas; tab navigation cobre todos os elementos interativos.
- [ ] T052 Verificar contraste WCAG 2.1 AA: (a) `#f7f8f8` sobre `#08090a` 4,5:1 ✅; (b) `#d0d6e0` sobre `#08090a` 4,5:1 ✅; (c) branco sobre `#5e6ad2` (botão CTA) verificar com tool; (d) cor do badge Aluguel sobre fundo do card verificar com tool; corrigir para `#828fff` se necessário (plan.md §4.3) possível ajuste em `frontend/src/components/PropertyCard.tsx`
- **Done when**: Todos os pares de cor críticos 4,5:1 para texto de corpo; 3:1 para componentes UI (NFR-009).
- [ ] T053 Executar Lighthouse no modo "Mobile" com throttling 4G: LCP < 2,5s (NFR-001); verificar no DevTools Network que `GET /api/v1/properties?featured=true` < 500ms (NFR-002); confirmar nenhuma imagem do seeder > 300 KB (NFR-003) — possível ajuste em `backend/seeds/seed.py` para usar URLs de imagens otimizadas
- **Done when**: Lighthouse LCP < 2,5s; API < 500ms em máquina local; imagens do seeder de `picsum.photos` com dimensões adequadas (max 300 KB cada).
**Checkpoint Phase 7**: Integração E2E completa; edge cases tratados; WCAG 2.1 AA ok; responsividade verificada; performance dentro das metas.
---
## Dependencies & Execution Order
### Phase Dependencies
```
Phase 1 (Setup)
└── Phase 2 (Foundational — BLOQUEIA tudo)
├── Phase 3 (US4 — Backend API)
│ └── Phase 4 (US1 — Hero/Nav) *requires T031
│ └── Phase 5 (US2 — Featured Properties) *requires T032
└── Phase 4 pode iniciar em paralelo com Phase 3
(frontend não depende do backend, só de tipos e services)
Phase 4 ── completa ──┐
Phase 5 ── completa ──┼── Phase 6 (US3 — About/CTA/Footer)
└── Phase 7 (Polish — requer Phases 36)
```
### User Story Dependencies
- **US4 (Phase 3)**: Pode iniciar após Phase 2. Independente dos outros US.
- **US1 (Phase 4)**: Pode iniciar após Phase 2 (frontend não precisa do backend rodando para ser codado). Requer backend para teste E2E.
- **US2 (Phase 5)**: Pode iniciar após Phase 4 (depende de `PropertyCard` e `FeaturedProperties`). Independente de US3.
- **US3 (Phase 6)**: Pode iniciar após Phase 5 ou em paralelo (componentes independentes). Independente de US1/US2 no código.
- **Polish (Phase 7)**: Requer todas as fases anteriores completas.
### Within Each User Story
- Types/interfaces Services Components Page integration
- Models Schemas Routes seed/tests (backend)
---
## Parallel Execution Examples
### Paralelo possível na Phase 1
```bash
# Terminal 1 — Backend
cd backend
uv sync
uv run flask --app app shell
# Terminal 2 — Frontend (independente)
npm create vite@latest frontend -- --template react-ts
cd frontend && npm install && npm run dev
```
### Paralelo possível na Phase 3 (US4)
```bash
# T015 e T016 podem ser escritos por pessoas diferentes simultaneamente
# (arquivos diferentes, sem dependência entre si)
# T017 depende de T015; T018 depende de T016
# T026 e T027 podem ser escritos em paralelo após T025
```
### Paralelo possível nas Phases 46
```bash
# Após Phase 2 concluída:
# Phase 4 (US1) e Phase 3 (US4) podem rodar em paralelo
# (frontend e backend são totalmente desacoplados até o teste E2E)
# Dentro da Phase 4:
# T028 (types/homepage) e T029 (types/property) são paralelos
# T033 (Navbar) e T034 (HeroSection) são paralelos após T030/T031
# Dentro da Phase 6:
# T043, T044, T045 são paralelos (arquivos independentes)
```
---
## Implementation Strategy
### MVP Scope (entregável mínimo para validação)
Completar apenas as fases de **P1** para ter uma homepage funcional:
1. Phase 1 (Setup)
2. Phase 2 (Foundational)
3. Phase 3 (US4 API backend)
4. Phase 4 (US1 Hero + Nav)
5. Phase 5 (US2 Featured Properties)
6. Phase 6 (US3 P2, pode ser adicionado depois)
7. Phase 7 (Polish verificação final antes de deploy)
O MVP entrega: homepage com hero dinâmico, grade de imóveis em destaque, responsividade, fallback silencioso.
### Incremental Delivery Order
```
Sprint 1: T001T014 (Setup + Foundational)
Sprint 2: T015T027 (Backend completo com testes)
Sprint 3: T028T038 (Frontend: tipos, services, Navbar, Hero)
Sprint 4: T039T042 (Frontend: PropertyCard, FeaturedProperties)
Sprint 5: T043T046 (Frontend: About, CTA, Footer)
Sprint 6: T047T053 (Polish, integração, QA)
```
---
## Summary
| Métrica | Valor |
|---------|-------|
| **Total de tarefas** | 53 |
| **US1 (Hero/Nav)** | 11 tarefas (T028T038) |
| **US2 (Featured Properties)** | 4 tarefas (T039T042) |
| **US3 (About/CTA/Footer)** | 4 tarefas (T043T046) |
| **US4 (Backend API)** | 13 tarefas (T015T027) |
| **Setup + Foundational** | 14 tarefas (T001T014) |
| **Polish** | 7 tarefas (T047T053) |
| **Tarefas paralelizáveis [P]** | 22 |
| **Complexidade S** | 26 |
| **Complexidade M** | 19 |
| **Complexidade L** | 2 (T034, T039) |
### Format Validation
Todas as 53 tarefas seguem o formato obrigatório:
- Checkbox `- [ ]`
- Task ID sequencial (`T001``T053`)
- Marker `[P]` onde aplicável
- Label `[USN]` em todas as tarefas de user story
- Caminhos de arquivo explícitos em cada tarefa
- Critério "Done when" em cada tarefa