sass-imobiliaria/specs/026-central-contatos/tasks.md
MatheusAlves96 cf5603243c
Some checks failed
CI/CD → Deploy via SSH / Build & Push Docker Images (push) Successful in 1m0s
CI/CD → Deploy via SSH / Deploy via SSH (push) Successful in 4m35s
CI/CD → Deploy via SSH / Validate HTTPS & Endpoints (push) Failing after 46s
feat: features 025-032 - favoritos, contatos, trabalhe-conosco, area-cliente, navbar, hero-light-dark, performance-homepage
- feat(025): favoritos locais com FavoritesContext, HeartButton, PublicFavoritesPage
- feat(026): central de contatos admin (leads/contatos unificados)
- feat(027): configuração da página de contato via admin
- feat(028): trabalhe conosco - candidaturas com upload e admin
- feat(029): UX área do cliente - visitas, comparação, perfil
- feat(030): navbar UX - menu mobile, ThemeToggle, useFavorites
- feat(031): hero light/dark - imagens separadas por tema, upload, preview, seed
- feat(032): performance homepage - Promise.all parallel fetches, sessionStorage cache,
  preload hero image, loading=lazy nos cards, useInView hook, will-change carrossel,
  keyframes em index.css, AgentsCarousel e HomeScrollScene via props
- fix: light mode HomeScrollScene - gradiente, cores de texto, scroll hint

migrations: g1h2i3j4k5l6 (source em leads), h1i2j3k4l5m6 (contact_config),
            i1j2k3l4m5n6 (job_applications), j2k3l4m5n6o7 (hero theme images)
2026-04-22 22:35:17 -03:00

206 lines
19 KiB
Markdown
Raw 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.

# Tasks: Central de Contatos com Rastreamento de Origem
**Input**: Design documents from `/specs/026-central-contatos/`
**Prerequisites**: spec.md ✅ · contexto técnico do autor ✅
**Branch**: `026-central-contatos`
## Format: `[ID] [P?] [Story?] Description`
- **[P]**: pode ser executada em paralelo (arquivos distintos, sem dependência de tarefa incompleta)
- **[Story]**: a qual user story pertence (`US1`, `US2`, `US3`, `US4`, `US5`)
- Caminhos exatos incluídos em cada tarefa
---
## Phase 1: Foundational — Modelo e Esquemas de Lead
**Purpose**: Adicionar os campos `source` e `source_detail` ao banco e às camadas Python. Este é o pré-requisito de todas as user stories — nenhuma pode ser implementada sem estes dados disponíveis.
**⚠️ BLOQUEANTE para todas as US**: nenhuma tarefa de US1US5 pode começar até T003 estar completo.
- [ ] T001 Criar migration Alembic para adicionar colunas `source VARCHAR(100) NULL` e `source_detail VARCHAR(255) NULL` à tabela `contact_leads` em `backend/migrations/versions/<timestamp>_add_source_to_contact_leads.py`
- **Detalhes**: usar `op.add_column('contact_leads', sa.Column('source', sa.String(100), nullable=True))` e `op.add_column('contact_leads', sa.Column('source_detail', sa.String(255), nullable=True))`; `downgrade` deve executar `op.drop_column` nas duas colunas; gerar com `alembic revision --autogenerate` e revisar o arquivo gerado.
- **Done**: `alembic upgrade head` executa sem erro; `\d contact_leads` mostra colunas `source` e `source_detail` como nullable; `alembic downgrade -1` remove as colunas sem erro.
- [ ] T002 Adicionar atributos `source` e `source_detail` ao modelo `ContactLead` em `backend/app/models/lead.py`
- **Detalhes**: adicionar `source = db.Column(db.String(100), nullable=True)` e `source_detail = db.Column(db.String(255), nullable=True)` após a coluna `message` existente; sem quebrar campos existentes (`id`, `property_id`, `name`, `email`, `phone`, `message`, `created_at`).
- **Done**: `ContactLead()` instancia sem erro; os dois campos são `None` por default; SQLAlchemy reflete corretamente no ORM após migration aplicada.
- [ ] T003 Atualizar schemas de lead em `backend/app/schemas/lead.py` para aceitar `source` e `source_detail` opcionais
- **Detalhes**: em `ContactLeadIn` (schema de entrada), adicionar `source: Optional[str] = None` e `source_detail: Optional[str] = None`; em `ContactLeadCreatedOut` (schema de saída), adicionar `source: Optional[str] = None` e `source_detail: Optional[str] = None`; importar `Optional` de `typing` se ainda não importado.
- **Done**: `ContactLeadIn(name="X", email="x@x.com", phone="99", message="msg")` valida sem erro (campos opcionais ausentes); `ContactLeadIn(..., source="imovel", source_detail="Apto Centro")` também valida; schema de saída serializa os dois campos.
**Checkpoint Phase 1**: `alembic upgrade head` OK; modelo ORM com os dois novos campos; schemas Pydantic validando opcionalmente. Base para todas as demais fases.
---
## Phase 2: Foundational — Endpoints Backend
**Purpose**: Expor o endpoint público de contato geral (`POST /contact`) e atualizar o roteamento do admin para filtrar por `source`. Estes dois endpoints são pré-requisitos de US1 e US4, respectivamente, e podem ser implementados em paralelo após Phase 1.
- [ ] T004 Criar endpoint `POST /api/v1/contact` (contato geral sem property) em `backend/app/routes/properties.py` ou em novo arquivo `backend/app/routes/contact.py`
- **Detalhes**: aceitar body `ContactLeadIn` via `request.get_json()`; validar com Pydantic; criar `ContactLead(property_id=None, source="contato", source_detail=None, **data.model_dump(exclude={"source","source_detail"}), name=data.name, email=data.email, phone=data.phone, message=data.message)`; persistir com `db.session.add` + `db.session.commit()`; retornar `ContactLeadCreatedOut.model_validate(lead).model_dump()` com status 201; registrar blueprint em `backend/app/__init__.py` se novo arquivo criado.
- **Done**: `POST /api/v1/contact` com payload válido retorna 201 e `id` do lead criado; lead salvo no banco com `source="contato"` e `property_id=NULL`; payload sem `name` retorna 422.
- [ ] T005 [P] Atualizar rota `GET /admin/leads` em `backend/app/routes/admin.py` para filtrar por `?source=`
- **Detalhes**: após o filtro existente `?property_id`, adicionar `source = request.args.get("source")`; se `source` não é None nem string vazia, aplicar `query = query.filter(ContactLead.source == source)`; manter retorno de todos os leads quando `source` não é passado; incluir os campos `source` e `source_detail` na serialização de saída (via schema ou `lead.__dict__`).
- **Done**: `GET /admin/leads` sem parâmetros retorna todos os leads; `GET /admin/leads?source=imovel` retorna somente leads com `source="imovel"`; `GET /admin/leads?source=contato` retorna somente leads com `source="contato"`; leads com `source=NULL` aparecem em "Todos" mas não nos filtros específicos.
**Checkpoint Phase 2**: curl `POST /api/v1/contact` retorna 201 ✓; curl `GET /admin/leads?source=contato` filtra corretamente ✓.
---
## Phase 3: US1 — Página de Contato Geral `/contato` (Priority: P1) 🎯 MVP
**Goal**: Disponibilizar página pública `/contato` com formulário funcional que cria leads com `source = "contato"`.
**Independent Test**: Acessar `/contato` sem autenticação, preencher nome, e-mail, telefone, assunto e mensagem e submeter. Verificar mensagem de confirmação na tela e lead salvo no banco com `source = "contato"`.
**Dependências**: T004 (endpoint `POST /api/v1/contact`) deve estar completo.
- [ ] T006 [US1] Criar função `contactGeneral(data)` em `frontend/src/services/properties.ts`
- **Detalhes**: exportar `async function contactGeneral(data: { name: string; email: string; phone: string; subject: string; message: string }): Promise<void>`; fazer `POST /api/v1/contact` com axios passando `{ ...data, source: "contato" }`; lançar erro em caso de resposta não-2xx.
- **Done**: função exportada e tipada; chama o endpoint correto com `source: "contato"` no payload.
- [ ] T007 [US1] Criar `frontend/src/pages/ContactPage.tsx`
- **Detalhes**: campos controlados: `name` (text, obrigatório), `email` (email, obrigatório, validar formato com regex), `phone` (tel, obrigatório), `subject` (select com opções: "Informações", "Anúncio", "Parceria", "Outro", obrigatório), `message` (textarea, obrigatório); estado `loading: boolean`, `success: boolean`, `errors: Record<string, string>`; validação client-side antes do submit (todos obrigatórios + formato de e-mail); ao submeter, chamar `contactGeneral(data)`; em sucesso, exibir mensagem de confirmação inline e desabilitar/resetar o formulário; em erro do servidor, exibir alerta de erro genérico; estilizar com Tailwind CSS consistente com o restante do projeto.
- **Done**: formulário renderiza; validação destaca campos obrigatórios ausentes sem submeter; e-mail inválido exibe erro inline; submissão bem-sucedida mostra confirmação e bloqueia re-envio; submissão com erro de servidor mostra feedback de erro.
- [ ] T008 [US1] Registrar rota `/contato` em `frontend/src/App.tsx`
- **Detalhes**: importar `ContactPage` de `./pages/ContactPage`; adicionar `<Route path="/contato" element={<ContactPage />} />` dentro do bloco de rotas públicas existente (dentro de `<Routes>`); não alterar nenhuma outra rota.
- **Done**: navegar para `/contato` renderiza `ContactPage`; rotas existentes não quebram.
**Checkpoint US1**: Acessar `/contato` → página renderiza ✓. Submeter com todos os campos → confirmação aparece ✓. Submeter sem nome → erro inline ✓. Lead salvo com `source="contato"` ✓.
---
## Phase 4: US2 — Página de Cadastro de Residência `/cadastro-residencia` (Priority: P1)
**Goal**: Disponibilizar página pública `/cadastro-residencia` para proprietários interessados em anunciar imóvel, criando lead com `source = "cadastro_residencia"`.
**Independent Test**: Acessar `/cadastro-residencia`, preencher todos os campos obrigatórios (nome, e-mail, telefone, endereço, tipo, finalidade) e submeter. Verificar lead criado com `source = "cadastro_residencia"` e `source_detail` contendo identificação do imóvel.
**Dependências**: T004 (endpoint `POST /api/v1/contact`) deve estar completo.
- [ ] T009 [US2] Criar `frontend/src/pages/CadastroResidenciaPage.tsx`
- **Detalhes**: campos controlados: `name` (text, obrigatório), `email` (email, obrigatório, validar formato), `phone` (tel, obrigatório), `address` (text, obrigatório), `propertyType` (select: "Casa", "Apartamento", "Comercial", obrigatório), `area` (number opcional, validar que é positivo quando preenchido), `purpose` (select: "Venda", "Aluguel", obrigatório), `notes` (textarea, opcional); estado `loading`, `success`, `errors`; ao submeter, construir `source_detail` como `"${propertyType} • ${purpose} • ${address}"` e enviar para `POST /api/v1/contact` com `{ name, email, phone, message: notes || "(sem observações)", source: "cadastro_residencia", source_detail }`; exibir confirmação em sucesso; estilizar com Tailwind CSS.
- **Done**: formulário renderiza todos os campos; campos obrigatórios ausentes são destacados; área não-numérica ou negativa exibe erro inline; submissão cria lead com `source="cadastro_residencia"` e `source_detail` identificável; confirmação aparece após sucesso.
- [ ] T010 [US2] Registrar rota `/cadastro-residencia` em `frontend/src/App.tsx`
- **Detalhes**: importar `CadastroResidenciaPage` de `./pages/CadastroResidenciaPage`; adicionar `<Route path="/cadastro-residencia" element={<CadastroResidenciaPage />} />` nas rotas públicas; não alterar outras rotas.
- **Done**: navegar para `/cadastro-residencia` renderiza `CadastroResidenciaPage`; rotas existentes não quebram.
**Checkpoint US2**: Acessar `/cadastro-residencia` → página renderiza ✓. Submeter com todos os campos → lead criado com `source="cadastro_residencia"` ✓. Área negativa → erro inline ✓.
---
## Phase 5: US4 — Central de Leads no Painel Admin (Priority: P1)
**Goal**: Página `/admin/leads` com listagem de todos os leads ordenada por data decrescente e filtro por origem (badge colorido por source).
**Independent Test**: Autenticar como admin, acessar `/admin/leads`, verificar leads das três origens listados. Aplicar filtro "Cadastro de Residência" e confirmar que apenas `source = "cadastro_residencia"` aparece.
**Dependências**: T005 (`GET /admin/leads?source=`) deve estar completo.
- [ ] T011 [US4] Criar `frontend/src/pages/admin/AdminLeadsPage.tsx`
- **Detalhes**: ao montar, buscar `GET /api/v1/admin/leads` (com token de autenticação via axios interceptor existente); estado `leads`, `loading`, `error`, `sourceFilter: string` (default `""`); quando `sourceFilter` não vazio, refazer fetch com `?source=${sourceFilter}`; renderizar barra de filtros com botões/tabs: "Todos", "Contato" (`source=contato`), "Imóvel" (`source=imovel`), "Cadastro de Residência" (`source=cadastro_residencia`); tabela com colunas: Origem (badge colorido por source — contato: azul, imovel: verde, cadastro_residencia: laranja, null/desconhecido: cinza), Nome, E-mail, Telefone, Prévia da mensagem (truncada em 60 chars), Data (formatada `dd/MM/yyyy`); badge "Imóvel" exibe `source_detail` como subtitle ou tooltip; ordenar por `created_at DESC`; exibir estado vazio ("Nenhum lead encontrado") quando lista vazia; suportar paginação básica (se a listagem retornar campo de total, exibir link "carregar mais" ou navegação por páginas).
- **Done**: página renderiza leads de todas as origens; filtro por source recarrega a lista; badge colorido por origem; `source_detail` visível para leads de imóvel; data formatada; estado de loading e erro tratados.
- [ ] T012 [US4] Adicionar item "Leads" ao menu lateral em `frontend/src/layouts/AdminLayout.tsx`
- **Detalhes**: localizar o array/lista de `navLinks` ou itens de menu do `AdminLayout`; adicionar entrada `{ label: "Leads", path: "/admin/leads" }` (ou equivalente JSX `<NavLink to="/admin/leads">Leads</NavLink>`) após o item existente que melhor se encaixa na ordem do menu (ex.: após "Imóveis" ou "Configurações"); não remover nem reordenar itens existentes.
- **Done**: menu lateral exibe item "Leads"; clicar navega para `/admin/leads`; link ativo é destacado pelo mecanismo existente.
- [ ] T013 [US4] Registrar rota `/admin/leads` em `frontend/src/App.tsx`
- **Detalhes**: importar `AdminLeadsPage` de `./pages/admin/AdminLeadsPage`; adicionar `<Route path="/admin/leads" element={<AdminLeadsPage />} />` dentro do bloco de rotas protegidas de admin (dentro do layout de admin existente); não alterar outras rotas.
- **Done**: navegar para `/admin/leads` como admin autenticado renderiza `AdminLeadsPage` dentro do `AdminLayout`; não autenticado redireciona para login.
**Checkpoint US4**: Acessar `/admin/leads` autenticado → listagem completa ✓. Filtrar por "Imóvel" → somente `source="imovel"` ✓. Badge colorido por origem ✓.
---
## Phase 6: US3 — Rastreamento de Origem no Formulário de Imóvel (Priority: P2)
**Goal**: O formulário de contato existente na página de detalhes do imóvel passa automaticamente `source = "imovel"` e `source_detail = property.title` sem alterar a experiência visual do usuário.
**Independent Test**: Acessar página de detalhes de um imóvel, enviar formulário de contato. Verificar no banco que o lead criado tem `source = "imovel"` e `source_detail` com o título do imóvel.
**Dependências**: T003 (schemas atualizados), T001/T002 (migration e modelo), mais T004 não é necessário — a rota de imóvel já existe. As tasks de frontend dependem de T003.
- [ ] T014 [US3] Atualizar rota `POST /properties/<slug>/contact` em `backend/app/routes/properties.py` para salvar `source` e `source_detail`
- **Detalhes**: após criar o objeto `data = ContactLeadIn(**request.get_json())`, ao instanciar `ContactLead`, definir `source = data.source or "imovel"` e `source_detail = data.source_detail`; o campo `property_id` já é definido pela slug da rota; o resto do handler permanece idêntico.
- **Done**: `POST /properties/<slug>/contact` salva `source="imovel"` quando o frontend não passa `source`; quando o frontend passa `source`, usa o valor recebido; `property_id` continua sendo preenchido pela slug.
- [ ] T015 [P] [US3] Atualizar a função `contactProperty` em `frontend/src/services/properties.ts` para aceitar e enviar `source` e `source_detail`
- **Detalhes**: adicionar `source?: string` e `source_detail?: string` ao tipo do parâmetro `data` (ou ao tipo `ContactData` se existir); incluir esses campos no payload da chamada axios existente: `{ ...data, source, source_detail }`.
- **Done**: `contactProperty(slug, { name, email, phone, message, source: "imovel", source_detail: "Título" })` envia o payload completo; chamadas sem `source`/`source_detail` continuam funcionando (campos opcionais).
- [ ] T016 [US3] Atualizar `frontend/src/pages/PropertyDetailPage.tsx` para passar `source` e `source_detail` ao chamar `contactProperty`
- **Detalhes**: localizar a chamada existente a `contactProperty(slug, data)` no handler de submit do formulário; adicionar `source: "imovel"` e `source_detail: property?.title ?? ""` ao objeto `data` passado; nenhuma mudança visual ou nos campos do formulário.
- **Done**: ao submeter o formulário de contato em `/imoveis/<slug>`, o lead criado tem `source="imovel"` e `source_detail` com o título do imóvel; a aparência do formulário é idêntica.
**Checkpoint US3**: Enviar formulário de contato em `/imoveis/<slug>` → lead com `source="imovel"` e `source_detail=título` no banco ✓. Formulário sem mudanças visuais ✓.
---
## Phase 7: US5 — Correção dos Links da Navbar (Priority: P3)
**Goal**: Os links "Contato" e "Sobre" da navbar navegam para rotas internas `/contato` e `/sobre` em vez de âncoras na homepage.
**Independent Test**: Em qualquer página, clicar "Contato" → URL muda para `/contato`. Clicar "Sobre" → URL muda para `/sobre`. Sem reload da página inteira.
**Dependências**: nenhuma — tarefa completamente independente; pode ser executada a qualquer momento após a branch ser criada.
- [ ] T017 [P] [US5] Corrigir links "Sobre" e "Contato" em `frontend/src/components/Navbar.tsx`
- **Detalhes**: localizar o array `navLinks` (ou equivalente); alterar a entrada com label/text "Sobre" de `href="/#sobre"` (ou `to="/#sobre"`) para `to="/sobre"`; alterar a entrada "Contato" de `href="/#contato"` (ou `to="/#contato"`) para `to="/contato"`; se os links usam tag `<a>`, converter para `<Link>` ou `<NavLink>` do react-router-dom para navegação client-side.
- **Done**: clicar "Contato" na navbar navega para `/contato` sem reload; clicar "Sobre" navega para `/sobre` sem reload; o link ativo é destacado pelo mecanismo existente do NavLink.
**Checkpoint US5**: Navbar em qualquer página: "Contato" → `/contato` ✓. "Sobre" → `/sobre` ✓. Navegação client-side (sem reload) ✓.
---
## Polish & Cross-Cutting
**Purpose**: Ajustes finais de UX e consistência que dependem de todas as user stories anteriores estarem completas.
- [ ] T018 Verificar responsividade dos formulários de `/contato` e `/cadastro-residencia` em viewport mobile (< 768px) ajustar classes Tailwind se necessário em `frontend/src/pages/ContactPage.tsx` e `frontend/src/pages/CadastroResidenciaPage.tsx`
- **Done**: formulários renderizam sem overflow horizontal em 375px de largura; inputs e botões têm tamanho mínimo de toque adequado.
- [ ] T019 [P] Garantir que leads com `source = NULL` (criados antes da feature) aparecem na listagem admin sem errors em `frontend/src/pages/admin/AdminLeadsPage.tsx`
- **Detalhes**: o badge de origem deve exibir "Desconhecida" (cinza) quando `source` é `null` ou `undefined`; nenhum crash ou `undefined` visível na UI.
- **Done**: leads sem `source` exibem badge cinza "Desconhecida"; filtros "Contato", "Imóvel" e "Cadastro de Residência" não incluem esses leads; filtro "Todos" os inclui.
---
## Dependency Graph
```
T001 → T002 → T003 ──┬──→ T004 ──→ T006 → T007 → T008 [US1]
│ └──→ T009 → T010 [US2]
├──→ T005 [US4 backend]
├──→ T014 [US3 backend]
└──→ T015 → T016 [US3 frontend]
T005 ──→ T011 → T012 → T013 [US4 frontend]
T017 [US5 — independente]
T018, T019 ── aguardam todas as US [Polish]
```
## Parallel Execution
**Após T003 completo**, as seguintes trilhas podem avançar em paralelo:
| Trilha A (US1) | Trilha B (US2) | Trilha C (US4 backend) | Trilha D (US3) | Trilha E (US5) |
|---|---|---|---|---|
| T004 T006 T007 T008 | T004 T009 T010 | T005 | T014 + T015 T016 | T017 |
> T004 é compartilhado entre US1 e US2 — completar antes de iniciar as duas trilhas.
## Implementation Strategy
**MVP mínimo** (US1 + US4): Phase 1 Phase 2 Phase 3 Phase 5. Resultado: formulário `/contato` funcional + admin pode visualizar e filtrar leads por origem.
**Incremento 2** (+ US2): adicionar Phase 4. Proprietários podem cadastrar imóveis para anúncio.
**Incremento 3** (+ US3): adicionar Phase 6. Rastreamento completo também para contatos originados de imóveis específicos.
**Incremento 4** (+ US5 + Polish): Phase 7 + Polish. Navbar corrigida e ajustes de responsividade.