449 lines
33 KiB
Markdown
449 lines
33 KiB
Markdown
# 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 (US1–US4)
|
||
- 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-006–009 |
|
||
| 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 1–3 |
|
||
| T027 | M | T025, T022 | plan.md §2, spec.md US2 scenarios 1–2, 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 1–12 — `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-001–003, NFR-004 |
|
||
| T034 | L | T031, T033 | plan.md §3.4, spec.md FR-004, US1 scenarios 1–5, 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 (1→2→3 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 1–4, 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-006–011, 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 1→2→3 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 1–2 |
|
||
| 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-004–006 |
|
||
| T051 | M | T046 | plan.md §4.3, spec.md NFR-007–010 |
|
||
| T052 | S | T046 | plan.md §4.3, spec.md NFR-009 |
|
||
| T053 | M | T047 | plan.md §4.4, spec.md NFR-001–003 |
|
||
|
||
- [ ] 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 1→2→3 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-004–006.
|
||
|
||
- [ ] 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 3–6)
|
||
```
|
||
|
||
### 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 4–6
|
||
|
||
```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: T001–T014 (Setup + Foundational)
|
||
Sprint 2: T015–T027 (Backend completo com testes)
|
||
Sprint 3: T028–T038 (Frontend: tipos, services, Navbar, Hero)
|
||
Sprint 4: T039–T042 (Frontend: PropertyCard, FeaturedProperties)
|
||
Sprint 5: T043–T046 (Frontend: About, CTA, Footer)
|
||
Sprint 6: T047–T053 (Polish, integração, QA)
|
||
```
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
| Métrica | Valor |
|
||
|---------|-------|
|
||
| **Total de tarefas** | 53 |
|
||
| **US1 (Hero/Nav)** | 11 tarefas (T028–T038) |
|
||
| **US2 (Featured Properties)** | 4 tarefas (T039–T042) |
|
||
| **US3 (About/CTA/Footer)** | 4 tarefas (T043–T046) |
|
||
| **US4 (Backend API)** | 13 tarefas (T015–T027) |
|
||
| **Setup + Foundational** | 14 tarefas (T001–T014) |
|
||
| **Polish** | 7 tarefas (T047–T053) |
|
||
| **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
|