feat: add full project - backend, frontend, docker, specs and configs
This commit is contained in:
parent
b77c7d5a01
commit
e6cb06255b
24489 changed files with 61341 additions and 36 deletions
37
.specify/features/001-homepage/checklists/requirements.md
Normal file
37
.specify/features/001-homepage/checklists/requirements.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Specification Quality Checklist: Homepage (Página Inicial)
|
||||
|
||||
**Purpose**: Validar a completude e qualidade da especificação antes de prosseguir para o planejamento
|
||||
**Created**: 2026-04-13
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- A spec está pronta para prosseguir para `/speckit.plan`.
|
||||
- User Story 4 (Admin Configura Conteúdo) depende da feature de autenticação do admin — esse pré-requisito está documentado nas Assumptions.
|
||||
- As seções Sobre, CTA e Rodapé têm conteúdo estático nesta versão; configurabilidade via admin é explicitamente Out of Scope.
|
||||
- O contrato de API inclui endpoints admin por completude, mas sua especificação completa de autenticação/autorização pertence à feature spec do Admin Panel.
|
||||
1148
.specify/features/001-homepage/plan.md
Normal file
1148
.specify/features/001-homepage/plan.md
Normal file
File diff suppressed because it is too large
Load diff
391
.specify/features/001-homepage/spec.md
Normal file
391
.specify/features/001-homepage/spec.md
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
# Feature Specification: Homepage (Página Inicial)
|
||||
|
||||
**Feature Branch**: `001-homepage`
|
||||
**Created**: 2026-04-13
|
||||
**Status**: Draft
|
||||
|
||||
---
|
||||
|
||||
## User Scenarios & Testing
|
||||
|
||||
### User Story 1 — Visitante Experimenta o Hero e a Navegação (Priority: P1)
|
||||
|
||||
Um visitante chega ao site da imobiliária e é imediatamente recebido por uma barra de navegação clara e uma seção hero visualmente impactante. O headline comunica a proposta de valor da agência, o subheadline fornece contexto de suporte, e um botão CTA proeminente convida o visitante a explorar os imóveis disponíveis.
|
||||
|
||||
**Why this priority**: O hero é a primeira impressão do site. Sem ele renderizar corretamente com conteúdo atualizado, nenhuma outra seção entrega valor significativo. Ele ancora a identidade visual e orienta o engajamento do visitante.
|
||||
|
||||
**Independent Test**: Pode ser testado de forma independente carregando a URL da homepage em qualquer navegador — a barra de navegação deve exibir o logotipo e os links, o hero deve mostrar o headline e subheadline configurados pelo admin, e o botão CTA deve estar visível e navegável para a listagem de imóveis.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** o visitante abre a URL da homepage, **when** a página carrega, **then** a barra de navegação é visível com o logotipo da agência à esquerda e os links "Imóveis", "Sobre" e "Contato" à direita.
|
||||
2. **Given** a página está carregada, **when** o visitante visualiza a seção hero, **then** ela exibe o headline atual, o subheadline (quando configurado), e um botão CTA com o rótulo configurado pelo admin (padrão: "Ver Imóveis").
|
||||
3. **Given** o visitante clica no botão CTA do hero, **when** o clique é processado, **then** o navegador redireciona para a página de listagem de imóveis (`/imoveis`).
|
||||
4. **Given** um admin atualizou o headline via painel administrativo, **when** o visitante carrega a homepage, **then** a seção hero exibe o novo headline sem necessidade de redeploy.
|
||||
5. **Given** a homepage é acessada em dispositivo móvel (viewport 320px–428px), **when** a página renderiza a seção hero, **then** todo o texto é legível, o botão CTA é tocável, e não há overflow horizontal.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 — Visitante Explora Imóveis em Destaque (Priority: P1)
|
||||
|
||||
Um visitante rola além da seção hero e encontra uma grade de imóveis curados em destaque. Cada card de imóvel fornece informações suficientes (foto, preço, tipo, estatísticas principais) para o visitante avaliar rapidamente o interesse e decidir clicar para ver mais detalhes.
|
||||
|
||||
**Why this priority**: Os imóveis em destaque são o conteúdo comercial primário da homepage. Esta seção conduz diretamente as consultas de imóveis e a conversão de visitante para lead. É a função de negócio central da homepage.
|
||||
|
||||
**Independent Test**: Pode ser testado de forma independente carregando a homepage e verificando que pelo menos um card de imóvel aparece na grade em destaque, com todos os campos obrigatórios visíveis (foto, título, preço, badge de tipo, quartos/banheiros/área). O teste pode ser executado contra um banco de dados com imóveis pré-configurados como destaque.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** existem imóveis no sistema com o flag "featured" habilitado, **when** o visitante carrega a homepage, **then** a seção de imóveis em destaque exibe uma grade mostrando até N imóveis (configurável pelo admin), cada card contendo: foto principal, título do imóvel, preço formatado (R$), badge de tipo (Venda ou Aluguel), contagem de quartos, banheiros e área total em m².
|
||||
2. **Given** nenhum imóvel está configurado como destaque, **when** o visitante carrega a homepage, **then** a seção de imóveis em destaque é ocultada ou exibe a mensagem "Nenhum imóvel em destaque no momento" — a grade não é renderizada vazia ou quebrada.
|
||||
3. **Given** um card de imóvel está visível, **when** o visitante clica no card, **then** o navegador navega para a URL de detalhe daquele imóvel (`/imoveis/{slug}`).
|
||||
4. **Given** um imóvel em destaque não possui foto cadastrada, **when** o card é renderizado, **then** ele exibe uma imagem placeholder em vez de um elemento de imagem quebrado.
|
||||
5. **Given** a grade de imóveis em destaque é visualizada em tablet (768px–1023px), **when** o layout renderiza, **then** a grade se adapta para 2 colunas sem overflow ou quebra de alinhamento.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 — Visitante Descobre a Agência e Inicia Contato (Priority: P2)
|
||||
|
||||
Um visitante que está interessado mas ainda não está pronto para navegar pelos imóveis rola pela seção Sobre para conhecer o background da agência, e em seguida encontra a seção CTA com informações de contato claras ou um convite para entrar em contato.
|
||||
|
||||
**Why this priority**: As seções Sobre e CTA suportam a construção de confiança e a geração de leads para visitantes que precisam de mais contexto antes de engajar com as listagens. São valiosas, mas não bloqueiam a funcionalidade MVP — a homepage funciona sem elas, embora as taxas de conversão se beneficiem significativamente de sua presença.
|
||||
|
||||
**Independent Test**: Pode ser testado de forma independente rolando a homepage além da seção de imóveis em destaque e verificando que (a) uma seção Sobre com nome e descrição da agência aparece, e (b) uma seção CTA com um convite de contato aparece antes do rodapé.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** o visitante rola além da seção de imóveis em destaque, **when** a seção Sobre entra na viewport, **then** ela exibe o nome da agência e pelo menos um parágrafo de descrição.
|
||||
2. **Given** o visitante continua rolando, **when** a seção CTA entra na viewport, **then** ela exibe um convite claro para contato com ao menos um elemento acionável (número de telefone, link de e-mail ou botão "Fale Conosco").
|
||||
3. **Given** a página é rolada até o final, **when** o visitante chega ao rodapé, **then** o rodapé exibe as informações de contato da agência (ao mínimo: e-mail ou telefone) e links de navegação (Imóveis, Sobre, Contato).
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 — Admin Configura o Conteúdo da Homepage (Priority: P1)
|
||||
|
||||
Um administrador faz login no painel administrativo e atualiza o headline e subheadline do hero da homepage. O admin também seleciona quais imóveis aparecem na grade em destaque habilitando ou desabilitando o flag "featured" nos imóveis individuais.
|
||||
|
||||
**Why this priority**: A capacidade de atualizar o conteúdo da homepage sem alterações de código é um requisito central do painel SaaS. Sem essa capacidade, a homepage é estática e o painel administrativo não entrega valor para o gerenciamento da homepage.
|
||||
|
||||
**Independent Test**: Pode ser testado de forma independente em dois cenários: (1) atualizar o headline via formulário de configurações da homepage no painel e verificar que a mudança aparece na homepage pública; (2) alternar o flag "featured" de um imóvel e verificar que a grade de destaque é atualizada. Ambas as ações podem ser testadas contra o painel com chamadas reais de API.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** o admin está autenticado e na página de configurações da homepage, **when** o admin atualiza o headline e o subheadline e salva, **then** a homepage pública reflete o novo texto no próximo carregamento sem necessidade de redeploy.
|
||||
2. **Given** o admin navega para a lista de imóveis no painel, **when** o admin habilita ou desabilita o flag "featured" em um imóvel e salva, **then** aquele imóvel aparece ou é removido da grade em destaque na homepage pública.
|
||||
3. **Given** o admin tenta salvar a configuração da homepage com headline vazio, **when** o formulário é submetido, **then** o sistema rejeita o salvamento e exibe uma mensagem de validação — headline vazio não é permitido.
|
||||
4. **Given** o admin seleciona imóveis em destaque, **when** mais imóveis que o máximo configurável são marcados como destaque, **then** o sistema exibe apenas até o limite definido na homepage (padrão: 6), priorizando por data de marcação.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- **Nenhum imóvel em destaque**: Se nenhum imóvel está marcado como featured, a seção deve ser oculta ou exibir "Nenhum imóvel em destaque no momento" — a página não deve lançar erro de renderização.
|
||||
- **Imóvel sem foto**: Se um imóvel em destaque não possui foto, um placeholder genérico é exibido — o card não deve renderizar um elemento de imagem quebrado.
|
||||
- **Headline muito longo**: Se o admin inserir um headline muito longo (120+ caracteres), a seção hero deve adaptar-se sem overflow de texto ou quebra de layout.
|
||||
- **Estado de carregamento / rede lenta**: Enquanto a resposta da API de imóveis em destaque está pendente, a seção deve exibir skeleton loaders — sem flash de conteúdo ou espaço em branco.
|
||||
- **Subheadline vazio**: O subheadline é opcional. Um subheadline vazio deve resultar na renderização do hero sem o elemento subheadline — não uma string vazia visível.
|
||||
- **API indisponível**: Se o endpoint de configuração da homepage ou de imóveis falhar, a página deve renderizar uma versão degradada (conteúdo de fallback estático) em vez de uma tela de erro completa.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: O sistema DEVE renderizar uma barra de navegação contendo o logotipo da agência e links para as seções/páginas Imóveis, Sobre e Contato.
|
||||
- **FR-002**: A barra de navegação DEVE ser sticky (visível durante a rolagem) em viewports desktop (≥768px).
|
||||
- **FR-003**: A barra de navegação DEVE colapsar para um menu hamburger em viewports abaixo de 768px.
|
||||
- **FR-004**: O sistema DEVE renderizar uma seção hero exibindo um headline, um subheadline opcional, e um botão CTA que navega para a listagem de imóveis.
|
||||
- **FR-005**: O headline e o subheadline do hero DEVEM ser configuráveis por um admin autenticado via painel administrativo sem alterações de código ou redeploy.
|
||||
- **FR-006**: O sistema DEVE renderizar uma seção de Imóveis em Destaque exibindo uma grade de imóveis marcados como "featured" por um admin.
|
||||
- **FR-007**: Cada card de imóvel na grade DEVE exibir: uma foto principal, título do imóvel, preço formatado em R$ (pt-BR), badge de tipo (Venda ou Aluguel), contagem de quartos, contagem de banheiros e área total em m².
|
||||
- **FR-008**: A grade de imóveis em destaque DEVE ser populada dinamicamente via API de backend — a lista NÃO DEVE exigir redeploy para ser atualizada.
|
||||
- **FR-009**: O sistema DEVE limitar a grade em destaque a um número máximo configurável de entradas (padrão: 6, máximo suportado: 12).
|
||||
- **FR-010**: Se nenhum imóvel estiver configurado como destaque, o sistema DEVE tratar esse estado graciosamente sem erros de renderização, ocultando a seção ou exibindo uma mensagem adequada.
|
||||
- **FR-011**: Cards de imóvel DEVEM exibir uma imagem placeholder quando nenhuma foto estiver disponível.
|
||||
- **FR-012**: O sistema DEVE renderizar uma seção Sobre/Empresa contendo ao mínimo o nome da agência e uma descrição breve.
|
||||
- **FR-013**: O sistema DEVE renderizar uma seção Call-to-Action contendo um convite para contato com ao mínimo um elemento acionável.
|
||||
- **FR-014**: O sistema DEVE renderizar um rodapé contendo informações de contato e links de navegação.
|
||||
- **FR-015**: O painel administrativo DEVE fornecer uma interface para selecionar quais imóveis são exibidos na grade em destaque.
|
||||
- **FR-016**: O painel administrativo DEVE rejeitar salvamentos de configuração de homepage com headline vazio, exibindo mensagem de validação ao admin.
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
#### Performance
|
||||
|
||||
- **NFR-001**: A homepage DEVE atingir um Largest Contentful Paint (LCP) abaixo de 2,5 segundos em conexão banda larga padrão (>10 Mbps).
|
||||
- **NFR-002**: Os endpoints de API que suprem dados da homepage DEVEM responder em até 500ms sob carga normal (até 100 requisições concorrentes).
|
||||
- **NFR-003**: Imagens de imóveis em destaque DEVEM ser servidas em formato web-otimizado — nenhuma imagem de thumbnail de card deve exceder 300 KB.
|
||||
|
||||
#### Responsiveness
|
||||
|
||||
- **NFR-004**: A homepage DEVE ser completamente funcional e visualmente íntegra nos seguintes breakpoints: 320px (mobile S), 375px (mobile M), 768px (tablet), 1024px (laptop), 1280px (desktop), 1440px (wide).
|
||||
- **NFR-005**: A grade de imóveis em destaque DEVE se adaptar de 1 coluna em mobile para 2 colunas em tablet e 3 colunas em desktop (≥1024px).
|
||||
- **NFR-006**: A tipografia do hero DEVE escalar responsivamente: 72px em desktop, 48px em tablet, 40px em mobile.
|
||||
|
||||
#### Accessibility
|
||||
|
||||
- **NFR-007**: Todas as imagens DEVEM ter texto `alt` descritivo (título do imóvel para fotos de imóveis; descrição para o logotipo).
|
||||
- **NFR-008**: Todos os elementos interativos (links, botões) DEVEM ser navegáveis por teclado e ter estados de foco visíveis.
|
||||
- **NFR-009**: Relações de contraste de cor DEVEM atender aos padrões WCAG 2.1 AA: mínimo 4,5:1 para texto de corpo, 3:1 para texto grande e componentes de UI.
|
||||
- **NFR-010**: A página DEVE usar elementos HTML5 semânticos apropriados e papéis ARIA landmark (`<nav>`, `<main>`, `<footer>`, `<header>`) em toda a estrutura.
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **HomepageConfig**: Representa o conteúdo configurável pelo admin para a homepage. Atributos principais: `hero_headline` (obrigatório, máx. 120 caracteres), `hero_subheadline` (opcional, máx. 240 caracteres), `hero_cta_label` (opcional, máx. 40 caracteres, padrão: "Ver Imóveis"), `hero_cta_url` (opcional, caminho relativo, padrão: "/imoveis"), `featured_properties_limit` (inteiro, padrão: 6, máximo: 12).
|
||||
- **Property**: Representa um anúncio imobiliário. Atributos relevantes para a homepage: `id` (UUID), `title` (string), `type` (enum: `venda` | `aluguel`), `price` (decimal), `area_m2` (inteiro), `bedrooms` (inteiro), `bathrooms` (inteiro), `is_featured` (boolean), `slug` (string única). A entidade Property é compartilhada com a feature de listagem de imóveis.
|
||||
- **PropertyPhoto**: Representa uma foto associada a um imóvel. A homepage usa apenas a foto principal (primeira por ordem de display). Atributos: `url` (string), `alt_text` (string), `display_order` (inteiro).
|
||||
|
||||
---
|
||||
|
||||
## Design Specifications
|
||||
|
||||
Toda a especificação visual segue o tema dark inspirado no Linear documentado em `DESIGN.md`.
|
||||
|
||||
### Color Application
|
||||
|
||||
| Element | Token | Value |
|
||||
|---------|-------|-------|
|
||||
| Page background | Marketing Black | `#08090a` |
|
||||
| Navigation background (sticky) | Marketing Black semi-transparente | `rgba(8,9,10,0.85)` + backdrop-blur |
|
||||
| Hero background (gradiente padrão) | Radial brand indigo fade | `radial-gradient(ellipse at center, rgba(94,106,210,0.08) 0%, #08090a 70%)` |
|
||||
| Card/container background | Level 2 Surface | `rgba(255,255,255,0.03)` |
|
||||
| Card border | Border Standard | `1px solid rgba(255,255,255,0.08)` |
|
||||
| Card border hover | Border Standard brightened | `1px solid rgba(255,255,255,0.12)` |
|
||||
| Primary text | Near-white | `#f7f8f8` |
|
||||
| Secondary text | Silver-gray | `#d0d6e0` |
|
||||
| Tertiary text / metadata | Muted gray | `#8a8f98` |
|
||||
| CTA button background | Brand Indigo | `#5e6ad2` |
|
||||
| CTA button hover | Accent Hover | `#828fff` |
|
||||
| Badge tipo Venda | Brand Indigo pill | `rgba(94,106,210,0.15)` + borda `rgba(94,106,210,0.3)` |
|
||||
| Badge tipo Aluguel | Neutral pill | `rgba(255,255,255,0.05)` + borda `rgba(255,255,255,0.1)` |
|
||||
| Section dividers | Line Tint | sem divisor visível — espaçamento separa as seções |
|
||||
| Footer background | Panel Dark | `#0f1011` |
|
||||
|
||||
### Typography Application
|
||||
|
||||
| Element | Style from DESIGN.md | Size | Weight | Letter Spacing |
|
||||
|---------|----------------------|------|--------|----------------|
|
||||
| Hero headline (desktop) | Display XL | 72px | 510 | -1.584px |
|
||||
| Hero headline (tablet) | Display | 48px | 510 | -1.056px |
|
||||
| Hero headline (mobile) | Heading 1 | 40px | 510 | -0.704px |
|
||||
| Hero subheadline | Body Large | 18px | 400 | -0.165px |
|
||||
| CTA button label | Label | 14px | 590 | normal |
|
||||
| Section headings (ex: "Imóveis em Destaque") | Display | 48px (desktop) / 32px (mobile) | 510 | -1.056px / -0.704px |
|
||||
| Property card title | Heading 3 | 20px | 590 | -0.24px |
|
||||
| Property price | Body Semibold | 16px | 590 | normal |
|
||||
| Property stats (quartos, banheiros, área) | Caption Large | 14px | 510 | -0.182px |
|
||||
| Navigation links | Link Small | 14px | 510 | -0.182px |
|
||||
| Footer links | Link Caption | 13px | 510 | -0.13px |
|
||||
| About / CTA body text | Body | 16px | 400 | normal |
|
||||
|
||||
Toda a tipografia usa `Inter Variable` com OpenType features `"cv01", "ss03"` habilitadas globalmente via `font-feature-settings: "cv01", "ss03"`.
|
||||
|
||||
### Layout
|
||||
|
||||
- **Largura máxima do conteúdo**: 1200px, centralizado horizontalmente com margens `auto`.
|
||||
- **Hero section**: mínimo 100vh de altura, layout de coluna única centralizado, padding: 120px superior / 80px inferior em desktop; 80px / 60px em mobile.
|
||||
- **Grade de Imóveis em Destaque**: 3 colunas em desktop (≥1024px), 2 colunas em tablet (768px–1023px), 1 coluna em mobile (<768px). Gap: 24px.
|
||||
- **Espaçamento vertical entre seções**: 80px padding-top e padding-bottom em desktop; 60px em mobile.
|
||||
- **Border radius dos cards**: 12px (Panel radius conforme DESIGN.md).
|
||||
- **Fotos dos cards**: border-radius `12px 12px 0 0` (arredondado só no topo), aspect-ratio 16:9.
|
||||
|
||||
### Component Notes
|
||||
|
||||
- **Navigation bar**: fundo `rgba(8,9,10,0.85)` com `backdrop-filter: blur(12px)`, `border-bottom: 1px solid rgba(255,255,255,0.05)`. Posição: sticky, `z-index` elevado.
|
||||
- **Property card**: fundo `rgba(255,255,255,0.03)`, borda `1px solid rgba(255,255,255,0.08)`, radius 12px, com transição de hover aumentando levemente a opacidade do fundo e a borda.
|
||||
- **CTA button (Primary)**: Background `#5e6ad2`, padding `10px 20px`, radius 6px, texto branco weight 590. Hover: `#828fff`.
|
||||
- **Type badges**: pills com radius `9999px`, padding `2px 10px`, fonte 12px weight 510.
|
||||
|
||||
---
|
||||
|
||||
## API Contract
|
||||
|
||||
Os seguintes endpoints devem ser implementados pelo backend Flask para suportar a homepage.
|
||||
|
||||
### `GET /api/v1/homepage-config`
|
||||
|
||||
Retorna o conteúdo configurável pelo admin para a seção hero da homepage.
|
||||
|
||||
**Autenticação**: Nenhuma (endpoint público)
|
||||
**Cache**: As respostas podem ser cacheadas por até 60 segundos (header `Cache-Control: public, max-age=60`).
|
||||
|
||||
**Response `200 OK`**:
|
||||
```json
|
||||
{
|
||||
"hero": {
|
||||
"headline": "Encontre o imóvel dos seus sonhos",
|
||||
"subheadline": "Imóveis para comprar e alugar com a melhor assessoria da região.",
|
||||
"cta_label": "Ver Imóveis",
|
||||
"cta_url": "/imoveis"
|
||||
},
|
||||
"featured_properties_limit": 6
|
||||
}
|
||||
```
|
||||
|
||||
**Response `500 Internal Server Error`**:
|
||||
```json
|
||||
{
|
||||
"error": "internal_server_error",
|
||||
"message": "An unexpected error occurred."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/properties?featured=true`
|
||||
|
||||
Retorna a lista de imóveis marcados como destaque, ordenados por prioridade de exibição.
|
||||
|
||||
**Autenticação**: Nenhuma (endpoint público)
|
||||
|
||||
**Query Parameters**:
|
||||
|
||||
| Parâmetro | Tipo | Obrigatório | Descrição |
|
||||
|-----------|------|-------------|-----------|
|
||||
| `featured` | boolean | sim (para este use case) | `true` filtra apenas imóveis em destaque |
|
||||
| `limit` | integer | não | Máximo de resultados. Padrão: valor de `featured_properties_limit` da config. Máximo: 12. |
|
||||
|
||||
**Response `200 OK`**:
|
||||
```json
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"title": "Apartamento Moderno no Centro",
|
||||
"type": "venda",
|
||||
"price": 450000.00,
|
||||
"price_formatted": "R$ 450.000,00",
|
||||
"area_m2": 85,
|
||||
"bedrooms": 3,
|
||||
"bathrooms": 2,
|
||||
"primary_photo_url": "https://cdn.example.com/properties/abc123/main.jpg",
|
||||
"primary_photo_alt": "Apartamento Moderno no Centro — foto principal",
|
||||
"slug": "apartamento-moderno-no-centro",
|
||||
"detail_url": "/imoveis/apartamento-moderno-no-centro"
|
||||
}
|
||||
],
|
||||
"total": 4
|
||||
}
|
||||
```
|
||||
|
||||
**Response `400 Bad Request`** (parâmetros inválidos):
|
||||
```json
|
||||
{
|
||||
"error": "invalid_parameter",
|
||||
"message": "Parameter 'limit' must be an integer between 1 and 12."
|
||||
}
|
||||
```
|
||||
|
||||
**Response `500 Internal Server Error`**:
|
||||
```json
|
||||
{
|
||||
"error": "internal_server_error",
|
||||
"message": "An unexpected error occurred."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `PUT /api/v1/admin/homepage-config`
|
||||
|
||||
Atualiza a configuração da homepage. Reservado para o painel administrativo (autenticação de admin obrigatória).
|
||||
|
||||
**Autenticação**: Bearer token (JWT), role `admin` obrigatória.
|
||||
**Nota**: Este endpoint é referenciado para completude do contrato; sua especificação completa (autenticação, sessões, permissões) é definida na feature spec do Admin Panel.
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"hero": {
|
||||
"headline": "string (obrigatório, máx. 120 caracteres)",
|
||||
"subheadline": "string (opcional, máx. 240 caracteres)",
|
||||
"cta_label": "string (opcional, máx. 40 caracteres)",
|
||||
"cta_url": "string (opcional, caminho relativo válido)"
|
||||
},
|
||||
"featured_properties_limit": "integer (opcional, 1–12)"
|
||||
}
|
||||
```
|
||||
|
||||
**Response `200 OK`**: Retorna o objeto `HomepageConfig` atualizado com o mesmo schema de `GET /api/v1/homepage-config`.
|
||||
|
||||
**Response `400 Bad Request`** (validação falhou):
|
||||
```json
|
||||
{
|
||||
"error": "validation_error",
|
||||
"fields": {
|
||||
"hero.headline": "This field is required and cannot be empty."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response `401 Unauthorized`** / **`403 Forbidden`**: Retornado quando o token está ausente, expirado ou sem a role `admin`.
|
||||
|
||||
---
|
||||
|
||||
### `PATCH /api/v1/admin/properties/{id}/featured`
|
||||
|
||||
Habilita ou desabilita o flag de destaque de um imóvel específico.
|
||||
|
||||
**Autenticação**: Bearer token (JWT), role `admin` obrigatória.
|
||||
**Nota**: Endpoint referenciado para completude. Especificação completa na feature spec de gerenciamento de imóveis do Admin Panel.
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"is_featured": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response `200 OK`**:
|
||||
```json
|
||||
{
|
||||
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"is_featured": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Out of Scope
|
||||
|
||||
Os itens a seguir estão explicitamente excluídos desta especificação de feature e serão tratados em specs separadas:
|
||||
|
||||
- **Página de detalhe do imóvel**: Clicar em um card de imóvel navega para a URL de detalhe, mas o layout, conteúdo e API da página de detalhe são uma feature separada.
|
||||
- **Busca e filtros de imóveis**: A funcionalidade de busca (filtrar por faixa de preço, localização, tipo, quartos, etc.) é uma feature separada. O botão CTA do hero linka para a listagem; qualquer interface de busca na homepage está fora de escopo.
|
||||
- **Autenticação e UI do Painel Administrativo**: O fluxo de login do admin, layout do painel, navegação e gerenciamento de sessão são features separadas. Esta spec define apenas a view pública da homepage e o contrato de API que o painel consumirá.
|
||||
- **Página de Listagem de Imóveis**: A listagem completa em `/imoveis` com paginação, filtros e ordenação é uma feature separada.
|
||||
- **Backend de Formulário de Contato**: O processamento de submissão de formulário de contato, entrega de e-mail e armazenamento de leads são features separadas. O CTA da homepage pode linkar para uma página de contato ou fornecer e-mail/telefone estático.
|
||||
- **Suporte multi-idioma (i18n)**: Português (pt-BR) é o único idioma suportado. Internacionalização está fora de escopo.
|
||||
- **SEO / Meta Tags avançadas**: Open Graph tags, dados estruturados (JSON-LD para `RealEstateListing`) e geração de sitemap são concerns separados.
|
||||
- **Integração de Analytics**: Eventos de rastreamento (GA4, Hotjar, etc.) estão fora de escopo para esta feature.
|
||||
- **Conteúdo editável via admin das seções Sobre e Rodapé**: Na versão inicial, o conteúdo das seções Sobre, CTA e Rodapé é estático. Uma spec futura pode adicionar gerenciamento dessas seções pelo admin.
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Um visitante pode chegar à homepage e clicar para ver um imóvel em destaque em menos de 30 segundos sem nenhuma orientação — taxa de conclusão da tarefa primária ≥ 90% em testes de usabilidade.
|
||||
- **SC-002**: A homepage carrega e exibe o conteúdo acima da dobra (hero + navegação) em até 2,5 segundos em conexão banda larga padrão.
|
||||
- **SC-003**: Um admin pode atualizar o headline do hero e vê-lo refletido na homepage pública em até 60 segundos, sem intervenção de desenvolvedor.
|
||||
- **SC-004**: Um admin pode alterar quais imóveis estão em destaque e ver a grade da homepage ser atualizada em até 60 segundos, sem redeploy.
|
||||
- **SC-005**: A homepage renderiza sem regressões visuais em todos os 6 breakpoints definidos (320px, 375px, 768px, 1024px, 1280px, 1440px).
|
||||
- **SC-006**: Todos os requisitos de contraste de cor WCAG 2.1 AA são atendidos em toda a homepage — verificado por auditoria de acessibilidade automatizada com zero violações críticas.
|
||||
- **SC-007**: A seção de imóveis em destaque renderiza corretamente com 0, 1 ou 6 imóveis marcados como featured — sem erros de JavaScript ou layouts quebrados em nenhum desses estados.
|
||||
|
||||
---
|
||||
|
||||
## Assumptions
|
||||
|
||||
- A agência opera exclusivamente no Brasil; toda formatação de moeda usa o locale pt-BR (prefixo R$, ponto como separador de milhar, vírgula como separador decimal).
|
||||
- A homepage é renderizada como React SPA com client-side data fetching; esta spec não prescreve a estratégia de renderização (CSR/SSR/SSG).
|
||||
- O sistema de autenticação do painel admin (login, sessão, JWT) é implementado como feature separada e é um pré-requisito para a User Story 4 (Admin Configura Conteúdo).
|
||||
- Fotos de imóveis são armazenadas externamente (object storage / CDN) e referenciadas por URL na resposta da API. A homepage não realiza upload de arquivos.
|
||||
- O conteúdo das seções Sobre e CTA (descrição da agência, informações de contato) é considerado estático para a versão inicial e não requer configurabilidade via admin nesta spec.
|
||||
- O conteúdo do rodapé (links e informações de contato) é HTML estático para a versão inicial.
|
||||
- O número máximo de imóveis em destaque exibidos na homepage tem padrão 6, configurável pelo admin até no máximo 12.
|
||||
- Links de cards de imóveis apontam para `/imoveis/{slug}` mesmo que a página de detalhe ainda não esteja implementada. Links temporariamente quebrados são aceitáveis durante o desenvolvimento em fases.
|
||||
- A fonte Inter Variable é carregada via fonte auto-hospedada ou CDN com OpenType features `"cv01", "ss03"` habilitadas globalmente via `font-feature-settings`.
|
||||
- O painel admin está hospedado na mesma origem ou com CORS configurado adequadamente para os endpoints `/api/v1/admin/*`.
|
||||
449
.specify/features/001-homepage/tasks.md
Normal file
449
.specify/features/001-homepage/tasks.md
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue