33 KiB
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 |
-
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.mdproject structure;__init__.pyvazio em cada subpacote Python.
- Done when: Todos os diretórios existem conforme
-
T002 Criar
backend/pyproject.tomlcom dependências Flask, SQLAlchemy, Flask-Migrate, Flask-CORS, Pydantic v2, psycopg2-binary, python-dotenv, pytest, pytest-flask —backend/pyproject.toml- Done when:
uv syncexecuta sem erro;uv run python -c "import flask; import pydantic"passa.
- Done when:
-
T003 Criar
backend/app/config.pycomDevelopmentConfig,ProductionConfig,TestingConfiglendoDATABASE_URL,SECRET_KEY,CORS_ORIGINSde variáveis de ambiente —backend/app/config.py- Done when:
from app.config import configimporta sem erro; chave ausente levantaKeyErrorexplícito.
- Done when:
-
T004 Criar
backend/app/extensions.pycom instâncias únicasdb = SQLAlchemy(),migrate = Migrate(),cors = CORS()—backend/app/extensions.py- Done when:
from app.extensions import db, migrate, corsimporta sem erro; nenhuma extensão é inicializada neste arquivo (apenas instanciada).
- Done when:
-
T005 Criar
backend/.env.examplecomDATABASE_URL,SECRET_KEY,CORS_ORIGINS,FLASK_ENV—backend/.env.example- Done when: Arquivo presente sem valores reais; todos os campos obrigatórios do
config.pycobertos.
- Done when: Arquivo presente sem valores reais; todos os campos obrigatórios do
-
T006 [P] Criar projeto frontend com
npm create vite@latest frontend -- --template react-ts—frontend/- Done when:
cd frontend && npm run devsobe emlocalhost:5173sem erros.
- Done when:
-
T007 Instalar dependências do frontend:
tailwindcss,postcss,autoprefixer,axios,react-router-dom,@types/react-router-dom—frontend/package.json- Done when:
npm installcompleta;node_modules/tailwindcssenode_modules/axiosexistem;npm run buildpassa.
- Done when:
-
T008 Criar
frontend/tailwind.config.tscom todos os tokens deDESIGN.md: coresmkt-black,panel-dark,surface-elevated,brand-indigo,accent-violet,accent-hover, pesosmedium: 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/nullcompila sem aviso; classebg-mkt-blacketext-brand-indigoexistem no output CSS gerado.
- Done when:
-
T009 Configurar
frontend/src/index.css: importar Tailwind (@tailwind base/components/utilities),@importInter Variable via Google Fonts,@layer basecomfont-feature-settings: "cv01", "ss03"ebody { @apply bg-mkt-black text-text-primary font-sans antialiased }—frontend/src/index.css- Done when: Página abre com fundo
#08090ae fonte Inter Variable sem inline style.
- Done when: Página abre com fundo
-
T010 [P] Configurar
frontend/vite.config.tscom proxy/api→http://localhost:5000echangeOrigin: true—frontend/vite.config.ts- Done when:
npm run buildpassa sem erros TypeScript; proxy configurado no blocoserver.proxy.
- Done when:
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 |
-
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; copiarbackend/.env.exampleparabackend/.enve preencher com valores locais —backend/.env- Done when:
docker psmostra containersaas_imob_dbRunning;psql postgresql://imob:imob_dev@localhost:5432/saas_imobiliaria -c "\l"lista o banco.
- Done when:
-
T012 Criar
backend/app/__init__.pycomcreate_app(config_name="default")que inicializadb,migrate,corse registra blueprints deapp.routes—backend/app/__init__.py- Done when:
uv run flask --app app shellabre sem traceback;db.engine.connect()no shell retorna sem erro.
- Done when:
-
T013 Inicializar Flask-Migrate:
uv run flask --app app db initdentro debackend/—backend/migrations/- Done when: Pasta
backend/migrations/criada comenv.pyeversions/pelo Alembic.
- Done when: Pasta
-
T014 Confirmar conexão DB no shell Flask:
db.engine.connect()— nenhum arquivo criado- Done when: Comando retorna
Connectionsem exceção; PostgreSQL aceita a conexão comDATABASE_URLdo.env.
- Done when: Comando retorna
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 |
-
T015 [P] [US4] Criar
backend/app/models/property.pycom classesProperty(UUID pk, title, slug, address, price Numeric(12,2), type enumvenda|aluguel, bedrooms, bathrooms, area_m2, is_featured, is_active, created_at) ePropertyPhoto(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, PropertyPhotoimporta sem erro; campos declarados exatamente conformeplan.md §2.1;priceusaNumeric(12,2), nuncaFloat.
- Done when:
-
T016 [P] [US4] Criar
backend/app/models/homepage.pycom classeHomepageConfig(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 HomepageConfigimporta sem erro;hero_headlineé NOT NULL no modelo;featured_properties_limittem default 6.
- Done when:
-
T017 [US4] Criar
backend/app/schemas/property.pycomPropertyPhotoOutePropertyOut(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;priceserializa comoDecimal(não float).
- Done when:
-
T018 [US4] Criar
backend/app/schemas/homepage.pycomHomepageConfigOuteHomepageConfigIn(Pydantic v2);HomepageConfigIncom@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="")levantaValidationError;HomepageConfigIn(featured_properties_limit=13)levantaValidationError; instância válida passa.
- Done when:
-
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 tabelasproperties,property_photos,homepage_confige enumproperty_typepresentes.
- Done when: Arquivo de migração criado em
-
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;
\dtno psql lista as três tabelas.
- Done when: Ambos os comandos executam sem erro; tabelas existem no banco após upgrade final;
-
T021 [US4] Criar
backend/app/routes/homepage.pycom Blueprinthomepage_bpe rotaGET /api/v1/homepage-configretornandoHomepageConfigOut.model_validate(config).model_dump()ou404se nenhum registro —backend/app/routes/homepage.py- Done when:
curl http://localhost:5000/api/v1/homepage-configretorna200com JSON contendohero_headlineapós seeder; retorna404se tabela vazia.
- Done when:
-
T022 [US4] Criar
backend/app/routes/properties.pycom Blueprintproperties_bpe rotaGET /api/v1/propertiesque filtra poris_active=True; quandofeatured=true, filtra poris_featured=True, ordena porcreated_at DESC, aplicalimitdeHomepageConfig.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.
- Done when:
-
T023 [US4] Registrar
homepage_bpeproperties_bpnocreate_app()debackend/app/__init__.py; importar models depropertyehomepagepara garantir que Flask-Migrate os detecte —backend/app/__init__.py- Done when:
flask routeslista/api/v1/homepage-confige/api/v1/properties; CORS permitehttp://localhost:5173.
- Done when:
-
T024 [US4] Criar
backend/seeds/seed.pyque apaga e recria: 1HomepageConfigcom headline/subheadline/cta configurados e 6Propertycomis_featured=True, tipos variados (venda/aluguel), e pelo menos 1PropertyPhotopor imóvel usando URLs dopicsum.photos—backend/seeds/seed.py- Done when:
uv run python seeds/seed.pyexecuta sem erro;GET /api/v1/properties?featured=trueretorna exatamente 6 imóveis; cada imóvel temphotoscom pelo menos 1 entrada.
- Done when:
-
T025 [US4] Criar
backend/tests/conftest.pycom fixtureapp(usandoTestingConfigcom SQLite em memória ou PostgreSQL de teste) e fixtureclient(app.test_client()) —backend/tests/conftest.py- Done when:
uv run pytest --collect-onlydescobre conftest sem error; fixtureclientdisponível nos testes.
- Done when:
-
T026 [P] [US4] Criar
backend/tests/test_homepage.pycom testes: (1)GET /api/v1/homepage-config→200com campos obrigatórios; (2)GET /api/v1/homepage-configsem registro →404; (3)HomepageConfigIn(hero_headline="")→ValidationError—backend/tests/test_homepage.py- Done when:
uv run pytest tests/test_homepage.py -vpassa com 3 testes verdes.
- Done when:
-
T027 [P] [US4] Criar
backend/tests/test_properties.pycom testes: (1)GET /api/v1/properties?featured=true→200com array; (2) resultado contém camposid,title,slug,price,type,bedrooms,bathrooms,area_m2,photos; (3) sem imóveis featured →200com[](não 500) —backend/tests/test_properties.py- Done when:
uv run pytest tests/test_properties.py -vpassa com 3 testes verdes.
- Done when:
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 |
-
T028 [P] [US1] Criar
frontend/src/types/homepage.tscom interfaceHomepageConfig(hero_headline, hero_subheadlinestring | 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.
-
T029 [P] [US1] Criar
frontend/src/types/property.tscom interfacesPropertyPhoto(url, alt_text, display_order) eProperty(id, title, slug, pricestring, 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).
- Done when: Arquivo exporta ambas as interfaces sem erro TypeScript;
-
T030 [US1] Criar
frontend/src/services/api.tscom instância Axios (baseURL: '/api/v1',timeout: 8000, headerContent-Type: application/json) —frontend/src/services/api.ts- Done when:
import { api } from './api'compila sem erro; baseURL aponta para/api/v1.
- Done when:
-
T031 [US1] Criar
frontend/src/services/homepage.tscomgetHomepageConfig(): Promise<HomepageConfig>chamandoapi.get<HomepageConfig>('/homepage-config')—frontend/src/services/homepage.ts- Done when: Função exportada compila sem erro TypeScript; retorna tipo
Promise<HomepageConfig>.
- Done when: Função exportada compila sem erro TypeScript; retorna tipo
-
T032 [US1] Criar
frontend/src/services/properties.tscomgetFeaturedProperties(): Promise<Property[]>chamandoapi.get<Property[]>('/properties', { params: { featured: 'true' } })—frontend/src/services/properties.ts- Done when: Função exportada compila sem erro TypeScript; retorna tipo
Promise<Property[]>.
- Done when: Função exportada compila sem erro TypeScript; retorna tipo
-
T033 [US1] Criar
frontend/src/components/Navbar.tsx: header semântico (<header role="banner"><nav aria-label="Navegação principal">), fundorgba(8,9,10,0.85)+backdrop-blur-navbar(classe Tailwind), stickyz-50, border-bottomborder-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.
- Done when: Navbar visível sticky ao scroll; hamburger aparece em
-
T034 [US1] Criar
frontend/src/components/HeroSection.tsxcom props{ headline, subheadline, ctaLabel, ctaUrl }: fundo#08090a+ gradiente radialrgba(94,106,210,0.08), headline comtext-[72px] tracking-display-xl font-mediumem desktop /text-[48px]tablet /text-[40px]mobile, subheadlinetext-xl text-text-secondary font-light(não renderizado quandonull), botão CTAbg-brand-indigo hover:bg-accent-hover rounded text-white font-semibold transition-colors duration-200com focus ring, skeleton de 3 linhas animadas quandoisLoading=true—frontend/src/components/HeroSection.tsx- Done when: Hero renderiza headline + subheadline da API;
subheadline=nullnão insere elemento vazio; tipografia escala nos 3 breakpoints; CTA navega paractaUrl; foco visível no botão (NFR-008); skeleton exibido quandoisLoading.
- Done when: Hero renderiza headline + subheadline da API;
-
T035 [US1] Criar
frontend/src/pages/HomePage.tsxcomFALLBACK_CONFIGestá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_CONFIGsilenciosamente (sem erro visível);isLoadingpassado para HeroSection durante fetch.
- Done when: Página carrega com conteúdo da API após 1 request; com API indisponível, exibe
-
T036 [US1] Criar
frontend/src/App.tsxcom<BrowserRouter><Routes><Route path="/" element={<HomePage />} /></Routes></BrowserRouter>—frontend/src/App.tsx- Done when:
npm run buildcompila sem erro;http://localhost:5173/renderizaHomePage.
- Done when:
-
T037 [US1] Atualizar
frontend/src/main.tsxpara usar<React.StrictMode><App /></React.StrictMode>com importação de./index.css—frontend/src/main.tsx- Done when:
npm run devnão lança erro de StrictMode; CSS global carregado.
- Done when:
-
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 emhttp://localhost:5173/placeholder-property.jpg; ≤ 100 KB.
- Done when: Arquivo presente em
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 |
-
T039 [US2] Criar
frontend/src/components/PropertyCard.tsxcom props{ property: Property }: containerrounded-xl border border-white/5 bg-surface-elevated overflow-hidden cursor-pointer hover:border-white/[0.08] transition-all duration-200, fotoaspect-[16/9] w-full object-cover rounded-t-xl(usaplaceholder-property.jpgquandophotos.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 comIntl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' })emtext-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/slugfunciona; alt text presente em todas as imagens (NFR-007).
- Done when: Card renderiza todos os campos obrigatórios (FR-007); placeholder exibido quando sem foto (FR-011); preço formatado como
-
T040 [US2] Criar
frontend/src/components/PropertyCardSkeleton.tsxcom estrutura idêntica ao PropertyCard usandoanimate-pulse, blocosbg-surface-secondary roundedno 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.
-
T041 [US2] Criar
frontend/src/components/FeaturedProperties.tsxcom estadosloading | success | error | emptygerenciados viauseEffect(() => getFeaturedProperties()...): loading → 3<PropertyCardSkeleton>, success com dados →grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6de<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).
-
T042 [US2] Integrar
<FeaturedProperties />emfrontend/src/pages/HomePage.tsxapó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.tsxcom fundobg-panel-dark, título "Sobre Nós"text-3xl font-medium tracking-h1 text-text-primary, parágrafo de descrição da agênciatext-base text-text-secondary leading-relaxed, padding verticalpy-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
#0f1011confirmado; padding conforme DESIGN.md.
- Done when: Seção visível ao rolar; nome e descrição presentes; background
-
T044 [US3] Criar
frontend/src/components/CTASection.tsxcom fundobg-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 verticalpy-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.tsxcom<footer role="contentinfo">(NFR-010), fundobg-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.
- Done when: Tag semântica
-
T046 [US3] Integrar
<AboutSection />,<CTASection />e<Footer />emfrontend/src/pages/HomePage.tsxapó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-configno shell e confirmar que reload da homepage reflete novo texto (FR-005, US4 scenario 1); confirmar que CORS aceita apenashttp://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)
FeaturedPropertiescom 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
altdescritivos (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)
#f7f8f8sobre#08090a≥ 4,5:1 ✅; (b)#d0d6e0sobre#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#828fffse necessário (plan.md §4.3) — possível ajuste emfrontend/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 embackend/seeds/seed.pypara usar URLs de imagens otimizadas- Done when: Lighthouse LCP < 2,5s; API < 500ms em máquina local; imagens do seeder de
picsum.photoscom dimensões adequadas (max 300 KB cada).
- Done when: Lighthouse LCP < 2,5s; API < 500ms em máquina local; imagens do seeder de
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
PropertyCardeFeaturedProperties). 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
# 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)
# 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
# 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:
- ✅ Phase 1 (Setup)
- ✅ Phase 2 (Foundational)
- ✅ Phase 3 (US4 — API backend)
- ✅ Phase 4 (US1 — Hero + Nav)
- ✅ Phase 5 (US2 — Featured Properties)
- ⏭️ Phase 6 (US3 — P2, pode ser adicionado depois)
- ⏭️ 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