26 KiB
UX/UI Audit — Página /imoveis
Análise realizada em 18/04/2026. Baseada no código-fonte atual da
PropertiesPage,FilterSidebar,PropertyRowCard,ContactModale componentes relacionados.
Índice
- Diagnóstico Geral
- Arquitetura de Informação
- Filtros e Busca
- Cards de Imóvel
- Layout e Visualização
- Paginação
- Estado Vazio e Erros
- Micro-interações e Animações
- Acessibilidade (A11y)
- Mobile Experience
- Performance Percebida
- Fluxo de Conversão
- Roadmap Priorizado
1. Diagnóstico Geral
Pontos positivos atuais
- Filtros sincronizados com a URL — links são compartilháveis e o histórico do browser funciona corretamente.
- Skeleton loading implementado — evita CLS (Cumulative Layout Shift).
- Carrossel de fotos com lazy load por slide individual.
- Sidebar sticky no desktop, drawer no mobile.
- Favoritos e comparação com contexto global persistente.
- Badge de contagem de filtros ativos no botão mobile.
Problemas críticos identificados
| Severidade | Quantidade | Descrição resumida |
|---|---|---|
| 🔴 Alta | 5 | Impactam diretamente conversão ou usabilidade fundamental |
| 🟡 Média | 9 | Degradam a experiência sem bloquear o uso |
| 🟢 Baixa | 8 | Refinamentos e polish |
2. Arquitetura de Informação
2.1 Ausência de campo de busca textual 🔴
Problema: Não existe um input de busca livre (por endereço, bairro, título ou código do imóvel). O usuário só consegue filtrar via dropdowns/chips. Para alguém que já sabe o endereço ou código do imóvel, o fluxo é completamente ineficiente.
Referências: Todos os grandes portais imobiliários (Zap, VivaReal, OLX) colocam a busca textual como primeiro ponto de entrada.
Solução recomendada:
┌─────────────────────────────────────────────────────┐
│ 🔍 Buscar por endereço, bairro ou código... │
└─────────────────────────────────────────────────────┘
- Barra de busca proeminente no topo da área de resultados (não na sidebar).
- Debounce de 400ms para evitar requests excessivos.
- Parâmetro
qna URL:/imoveis?q=Barra+Funda. - Busca no backend via
ILIKEemtitle,address,code,neighborhood.name.
2.2 Ausência de ordenação 🔴
Problema: Não há opção para ordenar os resultados. O usuário não controla se quer ver por menor preço, maior área, mais recente ou destaque.
Solução recomendada:
// Dropdown de ordenação ao lado do contador de resultados
<select name="sort">
<option value="relevance">Relevância</option>
<option value="price_asc">Menor preço</option>
<option value="price_desc">Maior preço</option>
<option value="area_desc">Maior área</option>
<option value="newest">Mais recente</option>
</select>
- Parâmetro
sortna URL. - Persistir a preferência de ordenação na URL (já funciona com o sistema atual de
filtersToParams).
2.3 Falta de chips/tags de filtros ativos 🟡
Problema: Quando o usuário aplica filtros no desktop, não há feedback visual na área de resultados mostrando quais filtros estão ativos. O contador "87 imóveis encontrados" não revela por quê esse número é aquele.
Solução recomendada:
┌─ Filtros ativos ──────────────────────────────────────┐
│ [Aluguel ×] [São Paulo ×] [2+ quartos ×] [Limpar tudo] │
└───────────────────────────────────────────────────────┘
- Chips removíveis logo abaixo do header, acima do primeiro card.
- Cada chip tem
×para remover individualmente. - Botão "Limpar tudo" só aparece quando há ≥ 2 chips.
2.4 Sem breadcrumbs 🟢
Problema: Não há indicação de onde o usuário está na hierarquia do site. Especialmente útil quando vem de uma busca filtrada.
Solução: Breadcrumb minimalista no header: Início > Imóveis > Apartamentos em São Paulo.
3. Filtros e Busca
3.1 Sidebar muito estreita para inputs de range 🟡
Problema: A sidebar tem w-56 (224px). Os RangeInputs de preço e área ficam comprimidos, forçando o usuário a digitar em campos muito pequenos sem feedback visual do range selecionado.
Solução recomendada:
- Aumentar para
w-64(256px) ouw-72(288px) no desktop. - Adicionar slider visual (range input duplo) acima dos inputs numéricos para preço — visualmente mais intuitivo e mais rápido que digitar valores.
// Price range com slider + inputs
<div>
<RangeSlider min={0} max={5_000_000} value={[priceMin, priceMax]} />
<RangeInputs ... />
</div>
3.2 Drawer mobile abre à direita 🟡
Problema: O drawer de filtros mobile abre pela direita (absolute right-0). O padrão de mercado (Google, Apple HIG) é que filtros e navegação secundária abram pela esquerda. Abrir pela direita quebra a expectativa de usuários que associam o lado direito a ações e notificações.
Solução: Mudar para left-0 com largura de 85vw máx 360px. O botão de filtro pode ficar onde está.
3.3 Nenhum feedback de "aplicando filtros" 🟡
Problema: Ao mudar qualquer filtro no sidebar desktop, a lista já recarrega (debounced pelo useEffect). Porém o usuário não tem feedback imediato de que algo está acontecendo — o skeleton só aparece depois do delay de rede, criando uma janela de latência percebida.
Solução:
- Mostrar um indicador sutil (spinner ou barra de progresso no topo da lista) imediatamente ao alterar filtros, antes do resultado da API.
- Adicionar transição
opacity: 0.5na lista enquantoloading === true, mantendo os cards anteriores visíveis em vez de sumindo e mostrando skeletons.
// Ao invés de mostrar skeleton completo, apenas escurecer
<div className={`flex flex-col gap-3 transition-opacity ${loading ? 'opacity-50 pointer-events-none' : 'opacity-100'}`}>
{result?.items.map(p => <PropertyRowCard ... />)}
</div>
3.4 Checkbox "incluir condomínio" ambíguo 🟢
Problema: O checkbox "incluir condomínio" no filtro de preço não é óbvio. Um usuário leigo pode não entender se isso soma o valor do condomínio ao preço de aluguel para filtrar, ou se está filtrando imóveis que tenham condomínio.
Solução:
- Tooltip (?) com explicação: "Quando ativo, o filtro de preço inclui a taxa de condomínio no total considerado."
- Ou reescrever o label para:
Preço total (com condomínio).
3.5 Comodidades sempre colapsadas 🟢
Problema: Os grupos de comodidades (Lazer, Segurança, etc.) têm defaultOpen = false. Em um sidebar já scrollável, isso exige que o usuário descubra que essas seções existem e clique para expandir.
Solução: Manter colapsado por padrão, mas mostrar os chips das comodidades selecionadas mesmo colapsado, para que o usuário veja que há filtros ativos ali.
4. Cards de Imóvel
4.1 Altura fixa quebra o layout em telas menores 🔴
Problema: h-[220px] fixo no card e w-[340px] fixo na imagem. Em viewports entre 768px e 1024px (tablets), o card fica comprimido mas mantém a altura rígida, fazendo o texto ser truncado prematuramente.
Impacto: Usuários em tablets iPad (768px) ficam no breakpoint errado — recebem o layout desktop comprimido, não o mobile drawer.
Solução recomendada:
// Card com altura adaptável
<article className="group bg-panel border border-borderSubtle rounded-2xl overflow-hidden
hover:border-borderStandard transition-all duration-200
flex flex-col sm:flex-row sm:h-[220px]">
{/* Imagem: full-width em mobile, fixed em desktop */}
<div className="relative flex-shrink-0 w-full h-48 sm:w-[280px] sm:h-full lg:w-[340px]">
4.2 Botões dentro de um elemento <Link> (problema de semântica) 🔴
Problema: O elemento <Link> envolve toda a seção de informações do card, incluindo os botões "Comparar" e "Entre em contato". Isso cria elementos interativos aninhados (<button> dentro de <a>), o que é inválido no HTML5 e pode causar comportamento inconsistente em diferentes browsers e leitores de tela.
Solução: Reestruturar o card para que o <Link> seja apenas um elemento de fundo clicável (via position: absolute) e os botões fiquem fora do DOM do Link:
<article className="relative group ...">
{/* Imagem */}
<div className="...carousel..."></div>
{/* Info section — sem ser um Link diretamente */}
<div className="flex flex-col flex-1 p-5 gap-2">
<Link to={...} className="absolute inset-0" aria-label="Ver detalhes" />
{/* Todo o conteúdo */}
<h3>...</h3>
{/* Botões ficam acima do Link absoluto com z-index */}
<div className="mt-auto flex items-center gap-2 relative z-10">
<button onClick={handleCompareClick}>Comparar</button>
<button onClick={handleContactClick}>Entre em contato</button>
</div>
</div>
</article>
4.3 "Ver detalhes →" como <span> não interativo 🟡
Problema: O texto Ver detalhes → é um <span> dentro de um <Link>. Visualmente parece um link separado, mas não tem estados de hover/focus próprios. Usuários podem tentar clicar especificamente nele achando que é um CTA distinto.
Solução: Transformar em um botão visual real com seta animada no hover:
<span className="ml-auto text-xs font-medium text-accent-violet group-hover:translate-x-0.5 transition-transform inline-flex items-center gap-1">
Ver detalhes <ArrowRightIcon />
</span>
4.4 Tipo de imóvel ausente no card 🟡
Problema: O card não exibe o tipo/subtipo do imóvel (Apartamento, Casa, Sala Comercial, etc.). O usuário precisa entrar na página de detalhes para descobrir o tipo, mesmo que já esteja filtrando por tipo.
Solução: Adicionar o subtipo como um chip pequeno abaixo do título:
{property.subtype && (
<span className="text-[10px] text-textTertiary bg-surface rounded px-1.5 py-0.5 w-fit">
{property.subtype.name}
</span>
)}
4.5 Carrossel inacessível em mobile (hover-only) 🔴
Problema: Os botões prev/next do carrossel têm opacity-0 group-hover:opacity-100. Em dispositivos touch, não existe o estado :hover, tornando os botões de navegação completamente invisíveis e inacessíveis em mobile.
Solução:
// Tornar visível em mobile, apenas com hover no desktop
className="... opacity-100 sm:opacity-0 sm:group-hover:opacity-100 ..."
Ou usar swipe gesture nativo em mobile (touchstart/touchend).
4.6 Ausência de badge "Novo" e "Destaque" 🟢
Problema: Não há diferenciação visual entre imóveis recém adicionados, imóveis em destaque ou imóveis com preço reduzido. Isso reduz a urgência percebida e o valor editorial da listagem.
Solução:
{property.is_featured && (
<span className="bg-amber-500/90 text-white ...">⭐ Destaque</span>
)}
{property.is_new && ( // criado nos últimos 7 dias
<span className="bg-emerald-500/90 text-white ...">Novo</span>
)}
4.7 Abreviações confusas nas stats 🟢
Problema: qts (quartos) e ban (banheiros) são abreviações pouco intuitivas, especialmente para usuários menos familiarizados. m² não tem o label "Área".
Solução:
3 quartos · 2 banheiros · 85 m² · 1 vaga
Em espaços comprimidos, usar tooltips:
<span title="Quartos"><BedIcon /> 3</span>
5. Layout e Visualização
5.1 Único modo de visualização (lista) 🟡
Problema: A página só oferece visualização em lista horizontal. Muitos usuários preferem visualização em grade (especialmente para imóveis com fotos bonitas) pois permite comparar mais imóveis visualmente de uma vez.
Solução: Toggle de visualização no header:
[≡ Lista] [⊞ Grade]
- Lista: layout atual
PropertyRowCard(horizontal, 1 coluna). - Grade: cards verticais
PropertyGridCard(2-3 colunas, foto em cima), com menos detalhes mas foto maior. - Persistir preferência em
localStorage.
// Grade: 2-3 colunas responsivas
<div className={view === 'grid'
? 'grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4'
: 'flex flex-col gap-3'}>
5.2 Per page fixo em 16 🟢
Problema: O per_page: 16 é hardcoded em dois lugares (handleFiltersChange e handleClear). O usuário não pode escolher ver mais ou menos resultados por página.
Solução: Seletor discreto no header: Exibir: [16] [32] [48].
6. Paginação
6.1 Sem indicador de posição 🟡
Problema: A paginação mostra apenas os números de página mas não informa quantos resultados estão sendo exibidos em relação ao total. "Página 3 de 12" não comunica que estamos vendo "imóveis 33-48 de 185".
Solução:
// Acima da paginação ou integrado ao header
<p className="text-xs text-textTertiary text-center mt-6">
Exibindo {(page - 1) * perPage + 1}–{Math.min(page * perPage, total)} de {total} imóveis
</p>
6.2 Scroll to top abrupto 🟡
Problema: window.scrollTo({ top: 0, behavior: 'smooth' }) faz o scroll mas não há indicação visual de que os resultados mudaram. O usuário pode não perceber que está vendo novos resultados.
Solução:
- Adicionar uma transição sutil de
opacitynos cards ao trocar de página. - Ou scroll para o topo do container de resultados, não da janela inteira (para não esconder o sidebar).
// Scroll para o topo da área de resultados
gridRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
6.3 Paginação duplicada 🟢
Melhoria: Adicionar paginação também no topo da lista de resultados (acima do primeiro card). Útil para quando o usuário quer mudar de página sem scrollar até o fim.
7. Estado Vazio e Erros
7.1 Estado vazio sem sugestões 🟡
Problema atual:
Nenhum imóvel encontrado com esses filtros.
[Limpar filtros]
Esse estado desperdiça uma oportunidade de reter o usuário e ajudá-lo a encontrar algo relevante.
Solução — Empty State rico:
😕 Nenhum imóvel encontrado
Tente estas sugestões:
• [Remover filtro de bairro] → 12 imóveis disponíveis
• [Ampliar faixa de preço] → 8 imóveis disponíveis
• [Mudar para 1+ quartos] → 5 imóveis disponíveis
[Ou limpar todos os filtros →]
Para calcular as sugestões, fazer requests paralelos com filtros relaxados.
7.2 Sem tratamento de erro de rede 🔴
Problema: O fetchProperties usa try/finally mas não guarda o erro. Se a API falhar, a lista simplesmente permanece em estado anterior ou vazia, sem nenhuma mensagem ao usuário.
Solução:
const [error, setError] = useState<string | null>(null)
// no fetchProperties:
} catch (err) {
setError('Não foi possível carregar os imóveis. Tente novamente.')
} finally {
setLoading(false)
}
// no render:
{error && (
<div className="...">
<p>{error}</p>
<button onClick={() => fetchProperties(filters)}>Tentar novamente</button>
</div>
)}
8. Micro-interações e Animações
8.1 Sem animação de entrada nos cards 🟢
Problema: Os cards aparecem abruptamente após o loading. Uma animação sutil de entrada melhora a percepção de qualidade.
Solução com Tailwind + delay staggered:
{result.items.map((property, i) => (
<div
key={property.id}
className="animate-fade-in-up"
style={{ animationDelay: `${i * 40}ms` }}
>
<PropertyRowCard property={property} />
</div>
))}
/* Em index.css */
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in-up {
animation: fade-in-up 0.25s ease both;
}
8.2 Sem botão "Voltar ao topo" 🟢
Problema: Em listas longas (16 cards), o usuário precisa scrollar muito para voltar ao topo e ajustar filtros no sidebar desktop ou clicar no botão de filtros mobile.
Solução: Botão flutuante que aparece após scrollar 400px:
{scrollY > 400 && (
<button
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
className="fixed bottom-24 right-6 z-40 w-10 h-10 rounded-full bg-panel border border-borderStandard shadow-lg ..."
>
↑
</button>
)}
8.3 Barra de comparação conflita com ComparisonBar 🟢
Problema: A ComparisonBar fica fixa no bottom-0. Quando ativa, ela sobrepõe o rodapé e pode encobrir o botão "Voltar ao topo". Sem margem dinâmica no conteúdo principal, cards podem ficar atrás da barra.
Solução: Quando ComparisonBar está visível, adicionar pb-20 no container principal.
9. Acessibilidade (A11y)
9.1 Elementos interativos aninhados 🔴
(Ver seção 4.2 — <button> dentro de <a> é HTML inválido)
9.2 Carrossel sem suporte a teclado 🟡
Problema: Os botões prev/next do carrossel não têm tabIndex e estão ocultos visualmente (opacity-0). Um usuário que navega por teclado não consegue navegar pelas fotos.
Solução:
<button
onClick={prev}
onFocus={() => /* mostrar botões */}
tabIndex={0}
aria-label="Foto anterior"
// Remover opacity-0 do tabIndex focus state
className="... focus:opacity-100"
>
9.3 Dots do carrossel com área clicável pequena 🟡
Problema: Os dots têm w-1.5 h-1.5 (6px). A área mínima recomendada pela WCAG é 24×24px.
Solução:
// Área de toque maior com padding
<button
className="p-2 -m-2" // área de toque 22px+padding
>
<span className="block w-1.5 h-1.5 rounded-full ..." />
</button>
9.4 <main> sem id ou aria-label 🟢
Problema: O <main> existe, mas não tem aria-label ou id="main-content" para skip links.
Solução:
<main id="main-content" aria-label="Listagem de imóveis" className="pt-14">
9.5 Filtros sidebar sem role="search" ou aria-label 🟢
Problema: O sidebar de filtros não tem semântica ARIA adequada.
Solução:
<aside aria-label="Filtros de busca">
<FilterSidebar ... />
</aside>
10. Mobile Experience
10.1 Layout horizontal comprimido em tablet 🔴
(Ver seção 4.1 — altura fixa do card)
O breakpoint lg:hidden para o sidebar significa que em tablets (768px–1023px), o usuário recebe o layout desktop mas com espaço insuficiente.
Solução: Mudar o breakpoint do sidebar para md:block ou xl:block e ajustar o layout do card para ser vertical até xl.
10.2 Sem infinite scroll como alternativa 🟢
Problema: A paginação tradicional obriga o usuário a clicar e esperar. Em mobile, infinite scroll ou "Carregar mais" é mais natural.
Solução opcional: Botão "Ver mais imóveis" no final da lista (append, não replace) como alternativa à paginação:
{result.page < result.pages && (
<button
onClick={() => loadMore()}
className="w-full mt-6 py-3 rounded-xl border border-borderStandard ..."
>
Carregar mais imóveis ({remaining} restantes)
</button>
)}
10.3 Tipografia pequena demais em mobile 🟡
Problema: text-xs (12px) em múltiplos lugares (stats do card, labels de filtro). A WCAG recomenda mínimo 16px para corpo de texto em mobile.
Elementos afetados:
- Stats do card (
text-xs text-textSecondary) - Label do botão "Entre em contato" (
text-xs font-semibold) - Paginação (
text-xs) - Seção titles da sidebar (
text-xs font-medium uppercase)
Solução: Escalar para text-sm (14px) em mobile, mantendo text-xs apenas em elementos secundários decorativos.
11. Performance Percebida
11.1 5 requests paralelos antes de renderizar qualquer coisa 🟡
Problema: O Promise.all na montagem carrega tipos, comodidades, cidades, bairros e imobiliárias antes de mostrar o sidebar. Enquanto isso, a sidebar fica vazia.
Solução:
- Mostrar skeleton da sidebar enquanto os dados de catálogo carregam.
- Priorizar o request de
getProperties(o mais importante) e deixar o catálogo carregar em segundo plano.
// Separar o loading do catálogo do loading dos imóveis
const [catalogLoading, setCatalogLoading] = useState(true)
// Catalog carrega em background, não bloqueia os imóveis
useEffect(() => {
Promise.all([...]).then(([...]) => {
// set states
setCatalogLoading(false)
})
}, [])
11.2 Todas as fotos do carrossel são renderizadas no DOM 🟡
Problema: O PhotoCarousel renderiza todos os slides no DOM desde o início, apenas mudando a opacity. Um imóvel com 10 fotos renderiza 10 <img> tags, mesmo que o usuário veja apenas 1.
Solução: Renderizar apenas o slide atual e os adjacentes (virtualização simples):
// Renderizar apenas slide atual ± 1
{slides.map((photo, i) => (
Math.abs(i - current) <= 1 && (
<SlideImage key={i} src={photo.url} alt={...} />
)
))}
12. Fluxo de Conversão
12.1 "Entre em contato" visualmente fraco 🟡
Problema: O botão verde bg-emerald-500 é a única ação de conversão no card, mas divide espaço igualmente com "Comparar" (border simples). O olho do usuário não é guiado para o CTA principal.
Hierarquia visual atual: [Comparar] ≈ [Entre em contato] Ver detalhes →
Solução — hierarquia clara:
[Ver detalhes] → ação primária (fundo brand)
[Entre em contato] → ação secundária (outline brand)
[Comparar] → ação terciária (ghost/minimal)
Ou: tornar "Entre em contato" o único botão com cor de fundo, ampliado:
<button className="rounded-lg px-4 py-2 text-xs font-semibold bg-brand text-white
hover:bg-accentHover transition-colors shadow-sm">
Falar com corretor
</button>
12.2 Comparação sem limite visual claro 🟢
Problema: Quando o usuário tenta adicionar um 4º imóvel à comparação, o botão simplesmente não funciona (por lógica no contexto), sem feedback visual do motivo.
Solução:
// Quando comparação está cheia e imóvel não está na lista
{!inComparison && comparisonFull && (
<Tooltip content="Máximo de 3 imóveis para comparar. Remova um para adicionar este.">
<button disabled className="opacity-40 cursor-not-allowed ...">Comparar</button>
</Tooltip>
)}
12.3 WhatsApp como CTA não aparece no card 🟢
Problema: O número de WhatsApp existe no sistema e o ContactModal o utiliza, mas o card não oferece acesso direto. Muitos usuários preferem o WhatsApp a preencher um formulário.
Solução (opcional): Ícone de WhatsApp como botão terciário no card:
<a
href={`https://wa.me/${whatsapp}?text=...`}
target="_blank"
rel="noopener noreferrer"
className="..."
onClick={e => e.stopPropagation()}
>
<WhatsAppIcon />
</a>
13. Roadmap Priorizado
Sprint 1 — Crítico (impacto imediato na usabilidade)
| # | Item | Esforço | Impacto |
|---|---|---|---|
| 1 | Corrigir botões dentro de <Link> (semântica HTML) |
P | 🔴 |
| 2 | Carrossel visível em mobile (remover opacity-0 em touch) | P | 🔴 |
| 3 | Tratamento de erro de rede no fetch de imóveis | P | 🔴 |
| 4 | Layout do card responsivo (sem altura fixa) | M | 🔴 |
| 5 | Campo de busca textual | G | 🔴 |
P = Pequeno (< 2h) · M = Médio (2–6h) · G = Grande (> 6h)
Sprint 2 — Alto valor (conversão e descoberta)
| # | Item | Esforço | Impacto |
|---|---|---|---|
| 6 | Ordenação de resultados (preço, área, data) | M | 🟡 |
| 7 | Chips de filtros ativos com remoção individual | M | 🟡 |
| 8 | Toggle lista/grade com PropertyGridCard |
G | 🟡 |
| 9 | Estado vazio com sugestões de filtros relaxados | M | 🟡 |
| 10 | Hierarquia de CTAs no card (primário/secundário/terciário) | P | 🟡 |
Sprint 3 — Refinamentos (qualidade percebida)
| # | Item | Esforço | Impacto |
|---|---|---|---|
| 11 | Animação de entrada dos cards (stagger) | P | 🟢 |
| 12 | Indicador de posição na paginação ("X–Y de Z imóveis") | P | 🟢 |
| 13 | Botão "Voltar ao topo" flutuante | P | 🟢 |
| 14 | Badge "Novo" e "Destaque" no card | P | 🟢 |
| 15 | Suporte a teclado no carrossel | M | 🟢 |
| 16 | Tipo de imóvel no card (subtipo) | P | 🟢 |
| 17 | Virtualização de slides do carrossel | M | 🟢 |
| 18 | Skeleton do sidebar enquanto catálogo carrega | P | 🟢 |
| 19 | Slider visual para range de preço | G | 🟢 |
| 20 | Paginação no topo da lista | P | 🟢 |
Referências e Benchmarks
| Portal | Funcionalidade de referência |
|---|---|
| VivaReal | Chips de filtros ativos, toggle lista/mapa/grade, busca textual no topo |
| Zap Imóveis | Ordenação proeminente, cards com subtipo e badges, infinite scroll mobile |
| Airbnb | Slider de preço com histograma de distribuição, filtros modais ricos |
| Booking.com | Estado vazio com sugestões de relaxamento de filtros |
| Rightmove (UK) | Paginação com "X–Y de Z", salvar busca, alertas por email |
Documento gerado para uso interno do projeto saas_imobiliaria. Revisão recomendada após implementação de cada sprint.