# Data Model — Melhorias UX/UI (023) > Nenhuma migration de banco necessária. Todos os campos utilizados já existem no modelo `Property`. --- ## Entidades Existentes (campos utilizados nesta feature) ### `Property` (`backend/app/models/property.py`) | Campo | Tipo SQLAlchemy | Tipo Python | Sprint | Utilização | |---|---|---|---|---| | `title` | `VARCHAR(200)` | `str` | 1 | Busca textual `q` (ILIKE) | | `address` | `VARCHAR(300)` | `str \| None` | 1 | Busca textual `q` (ILIKE) | | `code` | `VARCHAR(30)` | `str \| None` | 1 | Busca textual `q` (ILIKE) | | `neighborhood_id` | `INTEGER FK → neighborhoods.id` | `int \| None` | 1 | Join para busca `q` em `Neighborhood.name` | | `price` | `NUMERIC(12,2)` | `Decimal` | 2 | Ordenação `price_asc` / `price_desc` | | `area_m2` | `INTEGER` | `int` | 2 | Ordenação `area_desc` | | `created_at` | `DATETIME` | `datetime` | 2/3 | Ordenação `newest`; badge "Novo" (frontend) | | `is_featured` | `BOOLEAN` | `bool` | 3 | Badge "Destaque" no card | ### `Neighborhood` (`backend/app/models/location.py`) | Campo | Tipo SQLAlchemy | Utilização | |---|---|---| | `id` | `INTEGER PK` | Join com `Property.neighborhood_id` | | `name` | `VARCHAR` | Busca textual `q` (ILIKE) | --- ## Tipos Frontend Adicionados (`frontend/src/services/properties.ts`) ### `PropertyFilters` — campos novos ```ts // Adição aos campos existentes: q?: string // busca textual livre sort?: SortOption ``` ### `SortOption` (novo tipo) ```ts type SortOption = | 'relevance' // default — equivale a created_at DESC no backend | 'price_asc' | 'price_desc' | 'area_desc' | 'newest' ``` ### `ViewMode` (novo tipo local — apenas frontend) ```ts type ViewMode = 'list' | 'grid' // Persiste em localStorage com key 'imoveis_view_mode' ``` --- ## Entidades de UI (apenas frontend — sem persistência no banco) ### `ActiveFilterChip` Tipo derivado calculado a partir de `PropertyFilters` + dados do catálogo: ```ts interface ActiveFilterChip { key: string // identificador único (ex: 'city_id', 'q', 'bedrooms_min') label: string // texto exibido no chip (ex: 'São Paulo', 'Busca: "Jardins"') onRemove: () => void // callback que remove este filtro específico } ``` ### `EmptyStateSuggestion` ```ts interface EmptyStateSuggestion { label: string // ex: 'Remover filtro de bairro' relaxedFilters: PropertyFilters count: number // total de imóveis com o filtro relaxado } ``` --- ## Validação de Entrada — Backend O parâmetro `q` não é validado via Pydantic (é query param de GET, sem body). Sanitização aplicada diretamente na rota: ```python q = args.get("q", "").strip() # Comprimento máximo razoável (evitar payloads abusivos) if len(q) > 200: q = q[:200] ``` O parâmetro `sort` é validado via whitelist implícita no `sort_map.get(sort, default)`. --- ## Diagrama de Relacionamentos (campos relevantes) ``` Property ├── title (VARCHAR 200) ─── ILIKE com q ├── address (VARCHAR 300) ─── ILIKE com q ├── code (VARCHAR 30) ─── ILIKE com q ├── neighborhood_id (FK) ─┐ │ ├── JOIN → Neighborhood.name ─── ILIKE com q ├── price (NUMERIC 12,2) ─── ORDER BY price_asc/desc ├── area_m2 (INTEGER) ─── ORDER BY area_desc ├── created_at (DATETIME) ─── ORDER BY newest; badge "Novo" no frontend └── is_featured (BOOLEAN) ─── badge "Destaque" no frontend ```