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

5.6 KiB
Raw Blame History

Data Model: Property Detail Page (004)

Feature: 004-property-detail-page Date: 2026-04-13


Entidades Modificadas

Property (existente — colunas adicionadas)

Campo Tipo SQL Tipo Python Nullable Notas
id UUID PK UUID não existente
title VARCHAR(200) str não existente
slug VARCHAR(220) UNIQUE str não existente
code VARCHAR(30) UNIQUE str | None sim novo — ex: "AP-00042"
description TEXT str | None sim novo — descrição narrativa
address VARCHAR(300) str | None sim existente
price NUMERIC(12,2) Decimal não existente
condo_fee NUMERIC(10,2) Decimal | None sim existente
type ENUM(venda,aluguel) Literal["venda","aluguel"] não existente
subtype_id INT FK → property_types int | None sim existente
bedrooms INT int não existente
bathrooms INT int não existente
parking_spots INT int não existente
area_m2 INT int não existente
city_id INT FK → cities int | None sim existente
neighborhood_id INT FK → neighborhoods int | None sim existente
is_featured BOOLEAN bool não existente
is_active BOOLEAN bool não existente
created_at TIMESTAMP datetime não existente

Relacionamentos existentes (sem mudança):

  • photos: 1:M → PropertyPhoto (cascade delete-orphan, order by display_order)
  • subtype: M:1 → PropertyType (joined)
  • city: M:1 → City (joined)
  • neighborhood: M:1 → Neighborhood (joined)
  • amenities: M:M via property_amenity

Entidades Novas

ContactLead (novo)

Registra cada solicitação de contato feita por um visitante em relação a um imóvel.

Campo Tipo SQL Tipo Python Nullable Restrições
id SERIAL PK int não autoincrement
property_id UUID FK → properties UUID | None sim ON DELETE SET NULL
name VARCHAR(150) str não
email VARCHAR(254) str não
phone VARCHAR(20) str | None sim
message TEXT str não
created_at TIMESTAMP WITH TIME ZONE datetime não server_default=NOW()

Índices:

  • ix_contact_leads_property_id em property_id (consultas futuras de admin)
  • ix_contact_leads_created_at em created_at (ordenação)

Relacionamento:

  • ContactLead.propertyProperty (lazy="select", nullable via FK SET NULL)

Sem cascade delete: leads são preservados mesmo se o imóvel for deletado (histórico de negócio).


Schemas Pydantic (backend)

PropertyDetailOut (novo — herda de PropertyOut)

class PropertyDetailOut(PropertyOut):
    model_config = ConfigDict(from_attributes=True)
    address: str | None
    code: str | None
    description: str | None

Usado exclusivamente pelo endpoint GET /api/v1/properties/<slug>.

ContactLeadIn (novo — input de validação)

class ContactLeadIn(BaseModel):
    name: Annotated[str, Field(min_length=2, max_length=150)]
    email: EmailStr
    phone: Annotated[str | None, Field(max_length=20)] = None
    message: Annotated[str, Field(min_length=10, max_length=2000)]

ContactLeadCreatedOut (novo — resposta 201)

class ContactLeadCreatedOut(BaseModel):
    id: int
    message: str

Types TypeScript (frontend)

PropertyDetail (novo — herda de Property)

export interface PropertyDetail extends Property {
    address: string | null
    code: string | null
    description: string | null
}

ContactFormData (novo)

export interface ContactFormData {
    name: string
    email: string
    phone: string
    message: string
}

Estado do React — PropertyDetailPage

PropertyDetailPage
├── property: PropertyDetail | null   (fetch por slug)
├── notFound: boolean                 (true se 404)
├── loading: boolean
└── ContactSection
    ├── formData: ContactFormData
    ├── submitting: boolean
    ├── submitStatus: 'idle' | 'success' | 'error'
    └── fieldErrors: Partial<Record<keyof ContactFormData, string>>

Validações de Estado

Regra Onde aplicada
is_active = false → 404 Backend (GET /properties/<slug>)
slug inexistente → 404 Backend
name obrigatório, 2150 chars Backend (Pydantic) + Frontend (HTML validation)
email formato válido Backend (EmailStr) + Frontend (type="email")
phone max 20 chars, opcional Backend (Pydantic)
message obrigatório, 102000 chars Backend (Pydantic) + Frontend
property_id via slug (nunca do cliente) Backend

Transições de Estado do Formulário de Contato

idle → [usuário clica Enviar]
  ↓ validação frontend falha → exibe erros de campo → idle
  ↓ validação ok → submitting (botão desabilitado)
    ↓ 201 Created → success (mensagem de confirmação, formulário limpo)
    ↓ 4xx/5xx → error (mensagem de erro, dados preservados)
    error → [usuário edita] → idle

Agrupamento de Amenidades

Valor group no BD Label exibido
caracteristica Características
lazer Lazer
condominio Condomínio
seguranca Segurança

Grupos sem amenidade associada ao imóvel não são renderizados (FR-F07).