247 lines
17 KiB
Markdown
247 lines
17 KiB
Markdown
# Feature Specification: Área do Cliente
|
|
|
|
**Feature Branch**: `006-client-area`
|
|
**Created**: 2026-04-13
|
|
**Status**: Draft
|
|
**Depends On**: Feature 005 (Autenticação de Clientes — ClientUser model e JWT middleware)
|
|
|
|
---
|
|
|
|
## User Scenarios & Testing *(mandatory)*
|
|
|
|
### User Story 1 — Favoritar e Desfavoritar Imóvel (Priority: P1)
|
|
|
|
Um cliente autenticado pode favoritar um imóvel a partir do card ou da página de detalhes. O estado do botão de coração é persistido no backend e permanece entre sessões.
|
|
|
|
**Why this priority**: Favoritos é o recurso mais imediato de retenção do usuário na plataforma. Incentiva o retorno e aumenta o tempo de sessão.
|
|
|
|
**Independent Test**: Pode ser testado de forma isolada ao verificar que um cliente autenticado consegue adicionar e remover um imóvel de favoritos, e que ao recarregar a página o estado é mantido.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** o cliente está autenticado e navega pelo catálogo, **When** clica no botão de coração em um PropertyCard, **Then** o imóvel é adicionado aos favoritos, o coração fica preenchido e a ação é persistida na API.
|
|
2. **Given** o imóvel já está favoritado, **When** o cliente clica no coração novamente, **Then** o imóvel é removido dos favoritos, o coração fica vazio e o backend reflete a remoção.
|
|
3. **Given** o cliente não está autenticado, **When** clica no coração, **Then** é redirecionado para a página de login e, após autenticar, retorna ao imóvel.
|
|
4. **Given** o cliente já favoritou o mesmo imóvel, **When** uma segunda requisição de adição é enviada, **Then** o sistema retorna 409 sem duplicar o registro.
|
|
|
|
---
|
|
|
|
### User Story 2 — Página de Favoritos (Priority: P2)
|
|
|
|
Um cliente autenticado acessa `/area-do-cliente/favoritos` e visualiza todos os imóveis que marcou como favoritos, podendo desfavoritar diretamente da lista.
|
|
|
|
**Why this priority**: Sem a página de favoritos o botão de coração não tem destino, tornando o recurso incompleto do ponto de vista do usuário.
|
|
|
|
**Independent Test**: Pode ser testado verificando que a página exibe corretamente todos os imóveis favoritados e permite removê-los da lista.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** o cliente possui imóveis favoritados, **When** acessa `/area-do-cliente/favoritos`, **Then** vê uma grade de PropertyCards com botão de desfavoritar em cada um.
|
|
2. **Given** o cliente não possui nenhum favorito, **When** acessa a página, **Then** vê o estado vazio: "Nenhum favorito ainda".
|
|
3. **Given** o cliente clica para desfavoritar na página de favoritos, **When** a remoção é confirmada, **Then** o card desaparece da lista sem recarregar a página inteira.
|
|
4. **Given** o cliente não está autenticado, **When** acessa a rota diretamente, **Then** é redirecionado para a página de login.
|
|
|
|
---
|
|
|
|
### User Story 3 — Painel Principal (Dashboard) (Priority: P3)
|
|
|
|
Um cliente autenticado acessa `/area-do-cliente` e vê um painel de resumo com contadores e atalhos para as seções da área do cliente.
|
|
|
|
**Why this priority**: É o ponto de entrada da área do cliente; sem ele o usuário não tem orientação após o login.
|
|
|
|
**Independent Test**: Pode ser testado verificando que os contadores exibidos refletem dados reais do cliente (favoritos, visitas pendentes, boletos ativos).
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** o cliente está autenticado, **When** acessa `/area-do-cliente`, **Then** vê cards de resumo mostrando: total de favoritos, visitas pendentes e boletos ativos.
|
|
2. **Given** o cliente clica em um card de resumo (ex.: "Favoritos"), **Then** é navegado para a subseção correspondente.
|
|
3. **Given** todos os contadores estão zerados, **When** o cliente acessa o painel, **Then** os cards exibem "0" sem mensagens de erro.
|
|
|
|
---
|
|
|
|
### User Story 4 — Comparar Imóveis (Priority: P4)
|
|
|
|
Um cliente pode adicionar até 3 imóveis a uma lista de comparação e visualizar uma tabela lado a lado em `/area-do-cliente/comparar`. A seleção é mantida localmente durante a sessão.
|
|
|
|
**Why this priority**: Recurso diferencial que ajuda na decisão de compra; não requer autenticação de dados no backend, sendo implementável de forma autônoma.
|
|
|
|
**Independent Test**: Pode ser testado verificando a barra flutuante de comparação, adição/remoção de imóveis e a renderização correta da tabela comparativa.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** o cliente visualiza um imóvel no catálogo, **When** clica em "Comparar", **Then** o imóvel é adicionado à barra flutuante de comparação no rodapé da tela.
|
|
2. **Given** o cliente já tem 3 imóveis na comparação, **When** tenta adicionar um quarto, **Then** recebe uma mensagem informando que o limite de 3 imóveis foi atingido e não ocorre adição.
|
|
3. **Given** o cliente tem ao menos 1 imóvel na barra, **When** clica em "Ver Comparação" ou acessa `/area-do-cliente/comparar`, **Then** vê uma tabela com colunas por imóvel e linhas para: preço, área, quartos, banheiros, vagas, condomínio, tipo, bairro e comodidades.
|
|
4. **Given** o cliente clica em "Remover" em uma coluna da tabela, **Then** o imóvel é removido e a tabela é atualizada.
|
|
5. **Given** o cliente recarrega a página, **Then** os imóveis selecionados para comparação são restaurados do armazenamento local.
|
|
6. **Given** a lista de comparação está vazia e o cliente acessa `/area-do-cliente/comparar`, **Then** vê estado vazio com sugestão de selecionar imóveis do catálogo.
|
|
|
|
---
|
|
|
|
### User Story 5 — Histórico de Visitas (Priority: P5)
|
|
|
|
Um cliente autenticado acessa `/area-do-cliente/visitas` e visualiza o histórico de solicitações de visita com status atual.
|
|
|
|
**Why this priority**: Permite ao cliente acompanhar suas solicitações, reduzindo contato direto e dúvidas recorrentes para a equipe de vendas.
|
|
|
|
**Independent Test**: Pode ser testado verificando que as visitas criadas ao submeter o formulário de contato (como usuário logado) aparecem listadas com os status corretos.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** o cliente possui visitas cadastradas, **When** acessa `/area-do-cliente/visitas`, **Then** vê uma lista cronológica com: imóvel vinculado, mensagem enviada, status atual (badge colorido) e data agendada (quando confirmada).
|
|
2. **Given** o status de uma visita é alterado pelo admin, **When** o cliente recarrega a página, **Then** o novo status é refletido.
|
|
3. **Given** o cliente não tem nenhuma visita, **When** acessa a página, **Then** vê "Nenhuma visita agendada".
|
|
4. **Given** o cliente está autenticado e submete o formulário de contato na página de um imóvel, **When** a solicitação é enviada, **Then** uma VisitRequest é criada com status "pending" e aparece no histórico.
|
|
|
|
---
|
|
|
|
### User Story 6 — Boletos (Priority: P6)
|
|
|
|
Um cliente autenticado acessa `/area-do-cliente/boletos` e visualiza os boletos criados pelo admin, podendo acessar o link de pagamento ou baixar o PDF.
|
|
|
|
**Why this priority**: Acesso a boletos é funcionalidade financeira crítica para clientes em processo de locação ou compra.
|
|
|
|
**Independent Test**: Pode ser testado com boletos criados diretamente via API de admin, verificando listagem e acesso ao link.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** o cliente possui boletos vinculados, **When** acessa `/area-do-cliente/boletos`, **Then** vê uma tabela com: imóvel (quando vinculado), descrição, valor, vencimento, badge de status e botão para acessar o boleto.
|
|
2. **Given** o cliente clica no botão de acesso ao boleto, **Then** é aberto o link/URL do boleto em nova aba.
|
|
3. **Given** o boleto está com status "paid", **Then** o badge exibe "Pago" em cor distinta dos demais status.
|
|
4. **Given** o cliente não possui boletos, **When** acessa a página, **Then** vê "Nenhum boleto disponível".
|
|
|
|
---
|
|
|
|
### User Story 7 — Admin cria Boleto via API (Priority: P7)
|
|
|
|
Um administrador autentica na API e cria um boleto para um cliente, opcionalmente vinculado a um imóvel.
|
|
|
|
**Why this priority**: Backend necessário para suportar a P6; não há UI de admin no MVP.
|
|
|
|
**Independent Test**: Pode ser testado diretamente via chamada à API POST /api/v1/admin/boletos com token de admin.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** o admin envia POST `/api/v1/admin/boletos` com os campos obrigatórios, **Then** o boleto é criado com status "pending" e retorna 201 com os dados do boleto.
|
|
2. **Given** o campo `user_id` não corresponde a um ClientUser existente, **Then** a API retorna 404.
|
|
3. **Given** campos obrigatórios estão ausentes (user_id, description, amount, due_date), **Then** a API retorna 422.
|
|
|
|
---
|
|
|
|
### User Story 8 — Admin atualiza status de Visita via API (Priority: P8)
|
|
|
|
Um administrador atualiza o status de uma solicitação de visita e opcionalmente define a data/hora agendada.
|
|
|
|
**Why this priority**: Necessário para o ciclo completo de visitas; sem esta operação o cliente nunca vê status diferente de "pending".
|
|
|
|
**Independent Test**: Pode ser testado via PUT `/api/v1/admin/visits/<id>/status` e verificando a mudança no retorno de GET /api/v1/me/visits.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** o admin envia PUT `/api/v1/admin/visits/<id>/status` com `{"status": "confirmed", "scheduled_at": "2026-05-01T10:00:00"}`, **Then** a VisitRequest é atualizada e retorna 200.
|
|
2. **Given** o id não existe, **Then** retorna 404.
|
|
3. **Given** o valor de `status` não é um dos permitidos (pending/confirmed/cancelled/completed), **Then** retorna 422.
|
|
|
|
---
|
|
|
|
### Edge Cases
|
|
|
|
- O que acontece quando o cliente tenta favoritar um imóvel que foi removido/desativado do catálogo? O registro SavedProperty permanece; o imóvel é exibido com indicação "Imóvel indisponível" ou omitido da listagem de favoritos.
|
|
- O que acontece com a comparação se um dos imóveis armazenados no localStorage deixar de existir? A aplicação ignora silenciosamente o id inválido ao carregar e exibe apenas os imóveis válidos.
|
|
- O que acontece quando a VisitRequest é criada e o cliente posteriormente envia o contato como anônimo (para o mesmo imóvel)? O ContactLead é criado normalmente (usuário anônimo) sem afetar a VisitRequest existente.
|
|
- O que acontece com boletos cujo `url` é nulo? O botão de acesso é desabilitado ou ocultado; o boleto ainda aparece na listagem.
|
|
- O que acontece se o token JWT expirar durante a navegação na área do cliente? O Axios interceptor (feature 005) redireciona para o login; a rota protegida bloqueia o acesso.
|
|
|
|
---
|
|
|
|
## Requirements *(mandatory)*
|
|
|
|
### Functional Requirements
|
|
|
|
**Favoritos**
|
|
|
|
- **FR-001**: O sistema DEVE permitir que clientes autenticados adicionem imóveis à lista de favoritos; tentativas de adicionar um imóvel já favoritado DEVEM retornar 409.
|
|
- **FR-002**: O sistema DEVE permitir que clientes autenticados removam imóveis da lista de favoritos; tentativas de remover um imóvel não favoritado DEVEM retornar 404.
|
|
- **FR-003**: A lista de favoritos de um cliente DEVE ser persistida no backend e recuperada entre sessões.
|
|
- **FR-004**: O botão de coração DEVE refletir o estado de favorito do imóvel para o cliente autenticado corrente.
|
|
|
|
**Comparação**
|
|
|
|
- **FR-005**: A aplicação DEVE permitir que o usuário adicione até 3 imóveis à lista de comparação; a adição de um quarto imóvel DEVE ser bloqueada com mensagem de feedback.
|
|
- **FR-006**: A lista de comparação DEVE ser persistida no armazenamento local do navegador e restaurada ao recarregar a página.
|
|
- **FR-007**: A página de comparação DEVE exibir uma tabela lado a lado com as seguintes características: preço, área, quartos, banheiros, vagas, condomínio, tipo, bairro e comodidades.
|
|
- **FR-008**: Uma barra flutuante de comparação DEVE ser exibida no rodapé sempre que houver ao menos 1 imóvel na lista.
|
|
|
|
**Painel do Cliente**
|
|
|
|
- **FR-009**: A rota `/area-do-cliente` DEVE exibir cards de resumo com o total de favoritos, o número de visitas com status "pending" e o número de boletos com status "pending".
|
|
- **FR-010**: Todas as rotas sob `/area-do-cliente` DEVEM ser protegidas; clientes não autenticados DEVEM ser redirecionados para o login.
|
|
|
|
**Visitas**
|
|
|
|
- **FR-011**: A rota `/area-do-cliente/visitas` DEVE exibir todas as VisitRequests do cliente autenticado, ordenadas por data de criação decrescente.
|
|
- **FR-012**: Ao submeter o formulário de contato na página de detalhes de um imóvel, se o usuário estiver autenticado, o sistema DEVE criar uma VisitRequest vinculada ao cliente além do ContactLead.
|
|
- **FR-013**: O admin DEVE poder atualizar o status de uma VisitRequest via API, incluindo opcionalmente a data/hora agendada.
|
|
|
|
**Boletos**
|
|
|
|
- **FR-014**: A rota `/area-do-cliente/boletos` DEVE exibir todos os boletos do cliente autenticado, ordenados por data de vencimento decrescente.
|
|
- **FR-015**: O admin DEVE poder criar boletos para qualquer cliente via API, com ou sem vínculo a um imóvel.
|
|
- **FR-016**: Boletos com `url` preenchida DEVEM exibir um botão de acesso; boletos sem `url` DEVEM exibir o botão desabilitado.
|
|
|
|
**API**
|
|
|
|
- **FR-017**: Todas as rotas sob `/api/v1/me/` DEVEM exigir token JWT válido de ClientUser; ausência ou token inválido DEVE retornar 401.
|
|
- **FR-018**: Todas as rotas sob `/api/v1/admin/` DEVEM exigir autenticação de admin; acesso com token de ClientUser DEVE retornar 403.
|
|
|
|
---
|
|
|
|
### Key Entities
|
|
|
|
- **SavedProperty**: Associação entre um cliente e um imóvel favoritado. Atributos: identificador único, referência ao cliente, referência ao imóvel, data de criação. Unicidade garantida por par (cliente, imóvel).
|
|
- **VisitRequest**: Solicitação de visita feita por um cliente para um imóvel. Atributos: identificador único, referência ao cliente, referência ao imóvel, mensagem livre, status do fluxo (pendente/confirmado/cancelado/concluído), data/hora agendada (opcional), data de criação.
|
|
- **Boleto**: Documento de cobrança criado pelo admin para um cliente, opcionalmente vinculado a um imóvel. Atributos: identificador único, referência ao cliente, referência ao imóvel (opcional), descrição, valor monetário, data de vencimento, status (pendente/pago/vencido), URL de acesso (opcional), data de criação.
|
|
- **ComparisonList** (frontend): Lista temporária de até 3 imóveis selecionados para comparação. Armazenada localmente no navegador; não persiste no backend.
|
|
|
|
---
|
|
|
|
## API Contract
|
|
|
|
> Todas as rotas requerem `Authorization: Bearer <token>` (client JWT, exceto rotas `/admin/` que requerem token de admin).
|
|
|
|
| Método | Rota | Corpo / Parâmetros | Respostas |
|
|
|--------|------|--------------------|-----------|
|
|
| GET | `/api/v1/me/favorites` | — | 200: `[{property completo}]` · 401 |
|
|
| POST | `/api/v1/me/favorites` | `{property_id}` | 201 · 409 se já favoritado · 401 · 404 se imóvel não existe |
|
|
| DELETE | `/api/v1/me/favorites/<property_id>` | — | 204 · 404 · 401 |
|
|
| GET | `/api/v1/me/visits` | — | 200: `[{id, property:{id,title,slug}, message, status, scheduled_at, created_at}]` · 401 |
|
|
| GET | `/api/v1/me/boletos` | — | 200: `[{id, description, amount, due_date, status, url}]` · 401 |
|
|
| POST | `/api/v1/admin/boletos` | `{user_id, property_id?, description, amount, due_date, url?}` | 201: boleto criado · 404 cliente não existe · 422 campos inválidos · 401/403 |
|
|
| PUT | `/api/v1/admin/visits/<id>/status` | `{status, scheduled_at?}` | 200: visita atualizada · 404 · 422 status inválido · 401/403 |
|
|
|
|
---
|
|
|
|
## Success Criteria *(mandatory)*
|
|
|
|
### Measurable Outcomes
|
|
|
|
- **SC-001**: Clientes autenticados conseguem favoritar e desfavoritar imóveis em menos de 2 segundos por ação, com estado persistido entre sessões.
|
|
- **SC-002**: A tabela de comparação é renderizada em menos de 1 segundo para até 3 imóveis; a seleção é restaurada corretamente ao recarregar a página.
|
|
- **SC-003**: 100% das rotas da área do cliente bloqueiam acesso não autenticado, redirecionando para o login sem expor dados.
|
|
- **SC-004**: O painel exibe contadores precisos — favoritos, visitas pendentes e boletos ativos — sem discrepâncias em relação ao banco de dados.
|
|
- **SC-005**: Clientes conseguem localizar e acessar um boleto em até 3 cliques a partir do painel principal.
|
|
- **SC-006**: Todas as mudanças de status de visita feitas pelo admin refletem no painel do cliente na próxima atualização de página.
|
|
- **SC-007**: O formulário de contato na página de detalhes, quando submetido por cliente autenticado, cria a VisitRequest e ela aparece imediatamente no histórico de visitas.
|
|
|
|
---
|
|
|
|
## Assumptions
|
|
|
|
- O modelo `ClientUser` e o middleware de autenticação JWT (Feature 005) são pré-requisitos e estarão disponíveis antes da implementação desta feature.
|
|
- O admin não terá uma interface gráfica no MVP; operações de admin (criação de boletos e atualização de status de visita) são executadas diretamente via API.
|
|
- Boletos são gerados externamente; o sistema apenas armazena e exibe o link/URL de acesso. Nenhuma integração com gateway de pagamento é necessária no MVP.
|
|
- A comparação de imóveis não será persistida no backend; usar armazenamento local do navegador é suficiente para os requisitos do MVP.
|
|
- Uma VisitRequest é criada somente quando o cliente está autenticado. Usuários anônimos continuam gerando ContactLeads sem criar VisitRequests.
|
|
- O status de boletos pode ser atualizado manualmente pelo admin via chamada à API (implícito no PUT /api/v1/admin/boletos/<id>) o que fica como extensão futura; no MVP o status só é definido na criação.
|
|
- O design de todos os componentes segue o tema Linear dark definido em `DESIGN.md` (fundo `#08090a`, acento `#5e6ad2`/`#7170ff`).
|
|
- A lista de favoritos retornada pela API inclui todos os detalhes do imóvel necessários para renderização do PropertyCard, sem chamadas adicionais.
|