sass-imobiliaria/specs/024-filtro-busca-avancada/spec.md

165 lines
15 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.

# Feature Specification: Filtro de Busca Avançada — FilterSidebar
**Feature Branch**: `024-filtro-busca-avancada`
**Created**: 2026-04-20
**Status**: Draft
**Fonte**: Solicitação de melhoria UX no sidebar de filtros da página `/imoveis`
---
## Contexto
O `FilterSidebar` da página `/imoveis` já existe e é funcional, mas apresenta três lacunas de usabilidade que impactam a descoberta de imóveis:
1. **Ausência de busca cross-categoria dentro do sidebar**: o usuário precisa abrir seção por seção para encontrar um tipo de imóvel ou bairro específico.
2. **Todas as seções abertas por padrão**: ao carregar a página, o sidebar está completamente expandido, gerando sobrecarga visual e exigindo scroll antes mesmo de ver os resultados.
3. **Listas longas sem hierarquia de popularidade**: bairros e tipos são listados em ordem arbitrária; itens raramente usados ocupam o mesmo espaço visual que os mais procurados, dificultando a seleção rápida.
---
## User Scenarios & Testing
### User Story 1 — Campo de Busca Cross-Categoria no Sidebar (Priority: P1)
Um visitante que sabe exatamente o que procura (ex.: "Copacabana", "Cobertura") digita o termo no campo de busca do sidebar e vê instantaneamente em qual categoria aquela opção se enquadra, podendo selecioná-la com um clique sem precisar abrir seções manualmente.
**Why this priority**: A busca cross-categoria resolve o maior obstáculo de navegação do sidebar: o usuário com intenção definida não sabe em qual seção procurar. É o ganho de UX mais alto com menor complexidade de implementação — nenhum dado novo de backend é necessário, pois os dados já chegam via `catalog.ts`.
**Independent Test**: Digitar "Copa" no campo de busca do sidebar e verificar que aparece uma lista de sugestões agrupadas mostrando "Copacabana" sob o grupo "Bairro"; clicar em "Copacabana" e confirmar que o filtro de bairro é aplicado.
**Acceptance Scenarios**:
1. **Given** a página `/imoveis` com o sidebar visível, **When** o usuário vê o topo do sidebar, **Then** um campo de busca com placeholder "Buscar filtro…" está disponível acima das seções accordion.
2. **Given** o campo de busca do sidebar com o valor "apar", **When** o usuário para de digitar por 200ms, **Then** uma lista de sugestões aparece inline (não em dropdown popup) mostrando entradas agrupadas, por exemplo: grupo "Tipo de imóvel" → "Apartamento".
3. **Given** sugestões de busca visíveis com múltiplas categorias correspondentes, **When** o usuário vê a lista, **Then** cada grupo tem um cabeçalho de categoria (ex.: "Tipo de imóvel", "Bairro", "Cidade") e os itens correspondentes abaixo.
4. **Given** uma sugestão visível, **When** o usuário clica nela, **Then** o filtro correspondente é aplicado (equivalente a selecionar o item na seção accordion), o campo de busca é limpo e a seção relevante é expandida para mostrar o item selecionado.
5. **Given** o campo de busca preenchido sem nenhuma correspondência, **When** a busca é executada, **Then** uma mensagem "Nenhum filtro encontrado para "[termo]"" é exibida no lugar das sugestões.
6. **Given** o campo de busca preenchido, **When** o usuário pressiona Escape ou limpa o campo, **Then** as sugestões são ocultadas e o estado das seções retorna ao normal.
7. **Given** a navegação por teclado com sugestões visíveis, **When** o usuário pressiona as teclas de seta (↓↑) e Enter, **Then** ele pode selecionar uma sugestão sem usar o mouse.
---
### User Story 2 — Seção de Preço Aberta por Padrão, Demais Colapsadas (Priority: P1)
Um visitante que acaba de chegar na página `/imoveis` encontra o sidebar com uma experiência limpa: apenas a seção de preço está expandida, tornando o filtro mais importante imediatamente visível, enquanto as demais seções ficam colapsadas e acessíveis sob demanda.
**Why this priority**: Juntamente com a busca cross-categoria, esta mudança tem impacto imediato na percepção de organização do sidebar sem exigir dados adicionais do backend. A seção de preço é o filtro de maior influência na decisão do usuário, justificando seu destaque inicial.
**Independent Test**: Carregar `/imoveis` pela primeira vez (ou sem parâmetros de URL) e verificar que somente a seção "Preço" está expandida; todas as demais seções (Tipo, Quartos, Bairros, etc.) estão colapsadas.
**Acceptance Scenarios**:
1. **Given** a página `/imoveis` carregada sem filtros ativos, **When** o sidebar renderiza, **Then** apenas a seção "Preço" está expandida (`defaultOpen = true`); todas as demais seções têm `defaultOpen = false`.
2. **Given** filtros ativos presentes na URL (ex.: `?city=1&bedrooms=2`), **When** o sidebar renderiza, **Then** as seções que contêm filtros ativos ficam automaticamente expandidas, além da seção de Preço.
3. **Given** o usuário colapsou manualmente a seção de Preço e navega para outra página e retorna, **When** o sidebar renderiza, **Then** o estado de colapso/expansão das seções volta ao padrão (Preço aberto, demais fechados), pois esse estado não é persistido.
---
### User Story 3 — Listas com "Ver mais" e Ordenação por Popularidade (Priority: P2)
Um visitante que navega pelos filtros de bairro ou tipo de imóvel vê imediatamente os N itens mais relevantes (com mais imóveis disponíveis), pode expandir para ver todos com "Ver mais", e identifica visualmente os itens mais populares por meio de badges ou destaque.
**Why this priority**: Listas longas sem hierarquia sobrecarregam a interface e enterram as opções mais úteis. Exibir os mais populares primeiro e truncar com "Ver mais" é o padrão de portais imobiliários líderes. Requer dados de contagem do backend, tornando-o de implementação mais complexa que as stories P1.
**Independent Test**: Abrir a seção "Bairros" no sidebar e verificar que apenas os 5 bairros com mais imóveis são exibidos inicialmente, com badge "Popular" no primeiro; clicar em "Ver mais" e confirmar que todos os bairros são exibidos.
**Acceptance Scenarios**:
1. **Given** uma seção de filtro com mais de 5 itens (ex.: Bairros), **When** o accordion é expandido, **Then** apenas os 5 primeiros itens são exibidos, ordenados do mais popular (mais imóveis associados) para o menos popular.
2. **Given** uma seção com mais de 5 itens exibindo os primeiros 5, **When** o usuário clica no botão "Ver mais (N)", **Then** todos os itens restantes são exibidos inline (sem modal ou navegação), e o botão muda para "Ver menos".
3. **Given** uma seção expandida com todos os itens visíveis, **When** o usuário clica em "Ver menos", **Then** a lista retorna a exibir apenas os 5 primeiros e o scroll retorna ao início da seção.
4. **Given** os itens de uma seção ordenados por popularidade, **When** o usuário vê a lista, **Then** os 3 itens com mais imóveis associados exibem um badge "Popular" ao lado do nome.
5. **Given** um filtro já selecionado que não está entre os 5 primeiros da lista, **When** o usuário reabre a seção com o filtro ativo, **Then** o item selecionado é exibido mesmo que esteja além dos 5 iniciais (o truncamento não oculta itens selecionados).
6. **Given** uma seção com 5 itens ou menos, **When** o accordion é expandido, **Then** o botão "Ver mais" não é exibido.
---
### Edge Cases
- O que acontece quando a busca cross-categoria retorna o mesmo item em múltiplas categorias (ex.: cidade e bairro com o mesmo nome)?
- Como o sistema lida com termos de busca contendo caracteres especiais (acentos, cedilha, hifens)?
- O que acontece quando um filtro selecionado não aparece mais na lista porque o backend não o retornou (ex.: cidade removida do catálogo)?
- Como o "Ver mais" se comporta quando um item selecionado está entre os ocultos — ele precisa estar visível mesmo sem clicar em "Ver mais"?
- O que acontece se os dados de popularidade (contagem de imóveis por bairro/tipo) ainda estão carregando quando o accordion é expandido?
- Como a seção de preço se comporta quando o parâmetro `listing_type` muda entre "venda" e "aluguel" (os presets já mudam — o estado de expansão da seção é preservado)?
- Qual é o comportamento do campo de busca do sidebar quando o catálogo ainda está carregando (`catalogLoading = true`)?
---
## Requirements
### Functional Requirements
#### Busca Cross-Categoria no Sidebar
- **FR-001**: O `FilterSidebar` DEVE exibir um campo de busca textual no topo, acima de todas as seções accordion, com placeholder "Buscar filtro…".
- **FR-002**: O campo de busca DEVE pesquisar simultaneamente em todas as categorias de filtro disponíveis: tipos de imóvel, cidades, bairros e comodidades.
- **FR-003**: A busca DEVE aplicar debounce de 200ms antes de exibir resultados, para não travar a interface durante digitação rápida.
- **FR-004**: Os resultados DEVEM ser apresentados agrupados por categoria, com o cabeçalho de cada grupo claramente identificado (ex.: "Tipo de imóvel", "Bairro", "Cidade", "Comodidade").
- **FR-005**: A busca DEVE ser case-insensitive e ignorar acentuação para maximizar correspondências (ex.: "copacabana" deve encontrar "Copacabana", "apto" deve encontrar "Apartamento").
- **FR-006**: Ao clicar em uma sugestão, o filtro correspondente DEVE ser aplicado imediatamente, o campo de busca DEVE ser limpo e a seção do accordion correspondente DEVE ser expandida.
- **FR-007**: Quando `catalogLoading = true`, o campo de busca DEVE estar desabilitado com estado visual de loading (cursor não permitido, opacidade reduzida).
- **FR-008**: A navegação por teclado (↑↓ para mover entre sugestões, Enter para selecionar, Escape para fechar) DEVE ser suportada para acessibilidade.
#### Estado Inicial das Seções
- **FR-009**: Ao carregar o `FilterSidebar` sem filtros pré-ativos na URL, apenas a seção "Preço" DEVE ter `defaultOpen = true`; todas as demais seções DEVEM ter `defaultOpen = false`.
- **FR-010**: Quando filtros pré-ativos existem na URL, as seções que contêm esses filtros ativos DEVEM ser inicialmente expandidas além da seção de Preço.
- **FR-011**: O estado de expansão das seções NÃO DEVE ser persistido entre sessões — cada carregamento de página retorna ao estado padrão.
#### Listas com Truncamento e Popularidade
- **FR-012**: Seções com mais de 5 itens DEVEM exibir apenas os 5 primeiros inicialmente, com um botão "Ver mais (N)" indicando quantos itens adicionais existem.
- **FR-013**: Os itens de cada seção DEVEM ser ordenados por popularidade, definida como a contagem de imóveis ativos associados àquele item (ex.: número de imóveis em cada bairro, número de imóveis de cada tipo).
- **FR-014**: Os 3 itens mais populares de cada seção DEVEM exibir um badge "Popular" ao lado do label.
- **FR-015**: Itens com filtro ativo (selecionados) DEVEM sempre ser visíveis, independentemente de estarem entre os 5 primeiros ou não.
- **FR-016**: O backend DEVE fornecer dados de contagem de imóveis por categoria (tipo de imóvel, cidade, bairro) para permitir ordenação por popularidade na camada de catálogo.
- **FR-017**: O botão "Ver mais" DEVE ter comportamento toggle: ao ser clicado novamente, exibe "Ver menos" e retorna a lista ao truncamento inicial.
### Requisitos Não-Funcionais
- **NFR-001**: A busca cross-categoria DEVE executar inteiramente no cliente (sem chamadas adicionais à API), utilizando os dados de catálogo já carregados.
- **NFR-002**: A animação de expansão/colapso das sugestões de busca DEVE seguir a mesma curva de animação das seções accordion existentes (CSS grid trick, `duration-200 ease-out`).
- **NFR-003**: O visual de todos os elementos novos (campo de busca, badges, botão "Ver mais") DEVE seguir os design tokens do projeto: `textTertiary`, `textSecondary`, `borderSubtle`, `borderStandard`, `surface`, `brand`.
- **NFR-004**: Nenhuma alteração visual ou de comportamento DEVE impactar o funcionamento dos filtros existentes (os filtros aplicados via accordion continuam funcionando normalmente).
- **NFR-005**: A contagem de imóveis por categoria retornada pelo backend DEVE ser calculada apenas sobre imóveis ativos e disponíveis, sem expor dados de imóveis inativos.
### Key Entities
- **CatalogItem com Contagem**: Extensão das entidades de catálogo existentes (tipo de imóvel, cidade, bairro) com o campo `property_count` indicando quantos imóveis ativos estão associados.
- **SugestãoDeFiltro**: Resultado da busca cross-categoria, composto por `category` (grupo), `label` (texto exibido), `value` (identificador) e `filterKey` (chave do filtro a ser aplicado em `PropertyFilters`).
- **EstadoDeExpansãoDaSeção**: Mapeamento interno de `sectionKey → boolean` controlando quais seções do accordion estão abertas; inicializado a partir dos filtros ativos da URL.
---
## Success Criteria
### Measurable Outcomes
- **SC-001**: Usuários com intenção definida (sabem o bairro ou tipo que buscam) conseguem aplicar o filtro desejado em menos de 3 interações a partir do campo de busca do sidebar.
- **SC-002**: A seção de preço é a primeira coisa interativa que o usuário vê ao abrir o sidebar em 100% dos carregamentos sem filtros pré-ativos.
- **SC-003**: Em seções com mais de 5 itens, os 5 exibidos inicialmente cobrem pelo menos 70% do volume de imóveis disponíveis (validação pelo ordenamento por popularidade).
- **SC-004**: O tempo de resposta do campo de busca cross-categoria (desde o fim do debounce até a exibição das sugestões) é imperceptível para o usuário (abaixo de 50ms, pois é processamento local).
- **SC-005**: Nenhum filtro previamente funcional quebra após a implementação — todos os cenários de aceitação da spec `023-ux-melhorias-imoveis` continuam passando.
---
## Assumptions
- Os dados de catálogo (tipos, cidades, bairros, comodidades) já estão disponíveis via `catalog.ts` no momento em que o `FilterSidebar` renderiza; não há nova chamada de API necessária para a busca cross-categoria.
- A ordenação por popularidade requer uma única adição ao endpoint de catálogo existente (`property_count` por item), não um endpoint separado.
- O número de itens por seção raramente ultrapassa 2030 em produção para este SaaS (portfólio de uma imobiliária), tornando o processamento de busca local viável sem paginação.
- O estado de expansão das seções não será persistido em `localStorage` nesta versão; a persistência pode ser adicionada em iteração futura se houver demanda.
- O badge "Popular" é puramente visual e não afeta a lógica de filtragem.
- A busca cross-categoria opera sobre dados do catálogo (tipos, cidades, bairros, comodidades) e não sobre dados de imóveis individuais (endereços, códigos) — a busca por endereço/código já está coberta pelo campo de busca global da feature `023`.
---
## Fora do Escopo
- Salvar buscas favoritas ou histórico de filtros por usuário.
- Busca cross-categoria que inclua imóveis individuais (por código, endereço ou título) — isso é responsabilidade do campo de busca global já especificado em `023-ux-melhorias-imoveis`.
- Ordenação de resultados de imóveis por popularidade de bairro (apenas os filtros do sidebar são ordenados por popularidade, não os cards de imóveis).
- Criação de um sistema completo de analytics de filtros — `property_count` é suficiente como proxy de popularidade nesta versão.
- Filtros de busca cross-categoria em tela mobile (sheet/modal) — este spec cobre apenas o sidebar em desktop; a experiência mobile é tratada em feature separada.
- Internacionalização dos labels de categoria ou dos badges.