feat: add full project - backend, frontend, docker, specs and configs

This commit is contained in:
MatheusAlves96 2026-04-20 23:59:45 -03:00
parent b77c7d5a01
commit e6cb06255b
24489 changed files with 61341 additions and 36 deletions

View file

@ -0,0 +1,150 @@
# 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 |