sass-imobiliaria/.specify/features/004-property-detail-page/research.md

150 lines
7.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

# Research: Property Detail Page (004)
**Feature**: `004-property-detail-page`
**Date**: 2026-04-13
**Status**: Complete — todos os NEEDS CLARIFICATION resolvidos
---
## R-01 — Campos `code` e `description` ausentes no modelo `Property`
**Pergunta**: Os campos `code` e `description` devem ser adicionados nesta feature ou numa feature separada?
**Evidência**: O contrato da API em `spec.md` inclui explicitamente `"code": "AP-00042"` e `"description": "..."` na resposta de `GET /api/v1/properties/<slug>`. A Constituição (Princípio III) define que a spec é a fonte de verdade.
**Decisão**: Adicionar ambos os campos ao modelo `Property` nesta feature. A migration que cria `contact_leads` também incluirá as novas colunas.
**Campos a adicionar**:
- `code`: `VARCHAR(30)`, `UNIQUE`, `nullable=True` (nullable para não quebrar registros existentes sem migration data)
- `description`: `TEXT`, `nullable=True`
**Rationale**: Agrupar numa única migration evita fragmentação de DDL. Os campos são necessários para o contrato da API desta feature.
**Alternativa rejeitada**: Feature separada só para `code`/`description` — overhead desnecessário para dois campos simples.
---
## R-02 — Campo `address` ausente em `PropertyOut`
**Pergunta**: O campo `address` existe no modelo mas não está em `PropertyOut`. Como tratar?
**Evidência**: `Property.address = db.Column(db.String(300), nullable=True)` existe no modelo. `PropertyOut` não inclui `address`. O contrato da spec exige `address` na resposta de detalhe.
**Decisão**: Adicionar `address: str | None` ao `PropertyOut` existente. É um campo geral do imóvel (não exclusivo da tela de detalhe) e sua ausência no schema era uma omissão.
**Impacto nos consumers existentes**: O endpoint `GET /api/v1/properties` (list) passará a incluir `address` na resposta. Isso é retrocompatível — campos adicionais em JSON não quebram consumers que não os leem.
**Alternativa rejeitada**: `PropertyDetailOut` separado apenas para `address` — over-engineering para um campo que logicamente pertence ao schema base.
---
## R-03 — `type` vs `listing_type` no contrato da API
**Pergunta**: O contrato da spec documenta `listing_type` mas o schema e o modelo usam `type`. O que usar na implementação?
**Evidência**:
- Model: `type = db.Column(db.Enum("venda", "aluguel", name="property_type"))`
- `PropertyOut`: `type: Literal["venda", "aluguel"]`
- Spec API contract: `"listing_type": "venda"`
**Decisão**: Manter `type` no schema e na serialização JSON. O contrato da spec usa `listing_type` como nome descritivo na documentação, mas o campo JSON emitido pelo backend será `type` (consistente com o endpoint de listagem já em produção). A spec documenta o _significado_ do campo, não exige renaming.
**Rationale**: Renomear para `listing_type` quebraria o endpoint de listagem que já retorna `type`. Backward compatibility supera a preferência de nomenclatura da spec, especialmente porque o frontend já consome `type`.
**Alternativa rejeitada**: Alias Pydantic `listing_type` via `Field(alias="type")` — introduziria inconsistência entre list e detail sem benefício real no MVP.
---
## R-04 — Carousel: biblioteca externa vs handlers nativos
**Pergunta**: Usar Embla Carousel, Swiper.js ou implementar com React state + handlers nativos?
**Análise**:
| Opção | Tamanho bundle | Complexidade | Justificativa |
|-------|---------------|--------------|---------------|
| Embla Carousel | ~7 KB gzip | baixa (API simples) | overkill para carousel básico |
| Swiper.js | ~35 KB gzip | média-alta | excesso de features desnecessárias |
| React state nativo | 0 KB extra | baixa-média | suficiente para os requisitos da spec |
**Requisitos da spec**: navegação por teclado (←/→), swipe touch, thumbnail strip com estado ativo. Tudo implementável com:
- `useState` para índice ativo
- `onKeyDown` no container (tabIndex=0)
- `onTouchStart`/`onTouchEnd` para detectar swipe horizontal
- CSS `transition` para animação suave
**Decisão**: Implementar com React state + handlers nativos. Zero nova dependência npm (alinhamento com Princípio VI).
**Rationale**: Os requisitos são exatos e limitados. Uma lib traz overhead de API para aprender, bundle weight extra e potencial conflito com o design system customizado.
---
## R-05 — Mapa (US3 P3): Google Maps Embed
**Pergunta**: Qual serviço de mapa usar para US3? Chave de API necessária?
**Análise**:
- US3 é P3 (prioridade mais baixa) — não bloqueia o MVP funcional
- Google Maps Embed API: iframe simples, sem SDK JS, sem package npm
- URL: `https://www.google.com/maps/embed/v1/place?key=API_KEY&q=ENDEREÇO_CODIFICADO`
- Requer chave de API com "Maps Embed API" habilitada
- OpenStreetMap via `iframe` Nominatim: gratuito, sem chave, mas qualidade variável
**Decisão**: Google Maps Embed via `<iframe>` simples quando `VITE_GOOGLE_MAPS_API_KEY` estiver definido. Se a variável não existir ou o endereço for nulo, a seção de mapa é silenciosamente omitida `(null render)`.
**Configuração necessária**:
- Env var frontend: `VITE_GOOGLE_MAPS_API_KEY` (opcional)
- Sem nova dependência npm
**Alternativa rejeitada**: Leaflet + react-leaflet — adiciona ~40 KB ao bundle para uma feature P3 que pode ser omitida no MVP.
---
## R-06 — Schema de detalhe: `PropertyDetailOut` vs extensão de `PropertyOut`
**Pergunta**: Criar `PropertyDetailOut(PropertyOut)` ou adicionar campos diretamente a `PropertyOut`?
**Evidência e raciocínio**:
- `code` e `description` são campos de detalhe narrativo — não fazem sentido no card da listagem (espaço limitado)
- `PropertyOut` é usado por dois endpoints: list (`GET /api/v1/properties`) e futuramente featured
- Adicionar `code`/`description` a `PropertyOut` polui a resposta da listagem
**Decisão**: Criar `PropertyDetailOut(PropertyOut)` com os campos adicionais:
```python
class PropertyDetailOut(PropertyOut):
address: str | None
code: str | None
description: str | None
```
`address` vai em `PropertyDetailOut` — em `PropertyOut` base o campo não é adicionado para não expor informação de endereço completo na listagem paginada.
**Wait — revisão**: O R-02 decidiu adicionar `address` a `PropertyOut`. Reconsiderando com R-06: manter `address` apenas em `PropertyDetailOut` é mais conservador e evita expor endereços na listagem. **Decisão final**: `address`, `code`, `description` em `PropertyDetailOut` somente.
---
## R-07 — Validação de `ContactLeadIn`: comprimentos de campo
**Pergunta**: Quais validações Pydantic usar em `ContactLeadIn`?
**Spec definiu**:
- `name`: obrigatório, 2150 chars
- `email`: obrigatório, EmailStr
- `phone`: opcional, max 20 chars
- `message`: obrigatório, 102000 chars
**Decisão**: Usar `pydantic.EmailStr` (requer `email-validator` no pyproject.toml — já presente como dependência do projeto). Usar `Annotated[str, Field(min_length=..., max_length=...)]` para os demais.
**Verificar**: `email-validator` já está em `pyproject.toml` antes de implementar. Se não estiver, adicionar com `uv add email-validator`.
---
## Resumo das Decisões
| ID | Decisão |
|----|---------|
| R-01 | `code` e `description` adicionados em `Property` + na migration desta feature |
| R-02 | `address` vai em `PropertyDetailOut` (não em `PropertyOut` base) |
| R-03 | Manter `type` no JSON (não renomear para `listing_type`) |
| R-04 | Carousel: React state + handlers nativos, zero nova lib |
| R-05 | Google Maps Embed via iframe, env var opcional, omissão silenciosa se ausente |
| R-06 | `PropertyDetailOut(PropertyOut)` com `address`, `code`, `description` |
| R-07 | Pydantic `EmailStr` + `Field(min_length, max_length)` para ContactLeadIn |