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

19 KiB
Raw Blame History

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 ✓.


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 já 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.