- 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)
14 KiB
| description |
|---|
| Task list para a feature 028 - Trabalhe Conosco |
Tasks: Trabalhe Conosco (028)
Input: Design documents de specs/028-trabalhe-conosco/
Prerequisites: plan.md ✅ · spec.md ✅ · data-model.md ✅ · contracts/jobs-api.md ✅
Format: [ID] [P?] [Story?] Description — arquivo
- [P]: Pode executar em paralelo (arquivo diferente, sem bloqueadores incompletos)
- [Story]: User story correspondente (US1, US2, US3, US4)
- Arquivo exato indicado em cada task
Phase 1: Foundational — Backend (Bloqueador de tudo)
Purpose: Migration, model, schemas e rotas Flask precisam existir antes que qualquer integração frontend possa ser testada contra o servidor real.
⚠️ CRÍTICO: Nenhuma fase de user story pode começar até esta fase estar completa.
-
T001 Criar migration Alembic
i1j2k3l4m5n6embackend/migrations/versions/i1j2k3l4m5n6_add_job_applications.pycomdown_revision = "h1i2j3k4l5m6"— implementarupgrade()criando a tabelajob_applications(9 colunas conforme data-model.md: id SERIAL PK, name VARCHAR(150) NOT NULL, email VARCHAR(254) NOT NULL, phone VARCHAR(30) NULL, role_interest VARCHAR(100) NOT NULL, message TEXT NOT NULL, file_name VARCHAR(255) NULL, status VARCHAR(50) NOT NULL server_default'pending', created_at TIMESTAMP NOT NULL server_defaultnow()) + 2 índices (ix_job_applications_created_atemcreated_at,ix_job_applications_statusemstatus);downgrade()remove índices e tabela na ordem inversa -
T002 Criar modelo SQLAlchemy
JobApplicationembackend/app/models/job_application.py— classe com__tablename__ = "job_applications", 9 colunas mapeando o schema da tabela (status comdefault="pending", created_at comserver_default=db.func.now()), constanteROLE_INTEREST_OPTIONS = ["Corretor(a)", "Assistente Administrativo", "Estagiário(a)", "Outro"]e__repr__com id + email -
T003 [P] Criar schemas Pydantic em
backend/app/schemas/job.py— definir 3 classes:JobApplicationIn(BaseModel): camposname: str(strip, não vazio),email: EmailStr,phone: str | None = None,role_interest: str(validado contraROLE_INTEREST_OPTIONSvia@field_validator),message: str(max_length=5000, não vazio),file_name: str | None = NoneJobApplicationOut(BaseModel): todos os campos deJobApplicationIn+id: int,status: str,created_at: datetime;model_config = ConfigDict(from_attributes=True)PaginatedJobApplications(BaseModel):items: list[JobApplicationOut],total: int,page: int,per_page: int,pages: int
-
T004 Criar rotas em
backend/app/routes/jobs.pycom dois blueprints:jobs_public_bp = Blueprint("jobs_public", __name__): endpointPOST /jobs/applypúblico — valida body viaJobApplicationIn(retorna 422 com{"error": "Dados inválidos", "details": ...}em ValidationError), cria e salvaJobApplicationviadb.session, retorna{"message": "Candidatura recebida com sucesso"}com status 201jobs_admin_bp = Blueprint("jobs_admin", __name__): endpointGET /jobsdecorado com@require_admin— lê query paramspage(default 1, ≥ 1) eper_page(default 20, clamp 1–100), consultaJobApplication.query.order_by(JobApplication.created_at.desc()).paginate(...), serializa viaPaginatedJobApplicationse retorna JSON 200
-
T005 Registrar model e blueprints em
backend/app/__init__.py:- Na seção de imports de models, adicionar
from app.models import job_application as _job_application_models - Registrar
jobs_public_bpcomurl_prefix="/api/v1"ejobs_admin_bpcomurl_prefix="/api/v1/admin"na funçãocreate_app()
- Na seção de imports de models, adicionar
-
T006 Aplicar migration no container e verificar schema:
docker-compose exec backend flask db upgrade→ confirmar tabela comdocker-compose exec db psql -U postgres -d saas_imobiliaria -c "\d job_applications"
Checkpoint: curl -X POST http://localhost:5000/api/v1/jobs/apply com body válido retorna 201. GET /api/v1/admin/jobs sem token retorna 401.
Phase 2: User Story 1 — Candidato Envia Formulário (Priority: P1) 🎯 MVP
Goal: Página /trabalhe-conosco com formulário funcional que submete via POST /api/v1/jobs/apply, exibe confirmação de sucesso e mantém dados em caso de erro de rede.
Independent Test: Acessar /trabalhe-conosco sem autenticação, preencher todos os campos obrigatórios (nome, e-mail, cargo de interesse, mensagem) e clicar em "Enviar Candidatura". Verificar que uma mensagem de confirmação é exibida e que GET /api/v1/admin/jobs (com token admin) lista a candidatura recebida.
-
T007 [P] [US1] Criar interface TypeScript em
frontend/src/types/jobApplication.ts:export interface JobApplicationPayload { name: string; email: string; phone?: string; role_interest: string; message: string; file_name?: string; } export const ROLE_INTEREST_OPTIONS = [ "Corretor(a)", "Assistente Administrativo", "Estagiário(a)", "Outro", ] as const; -
T008 [P] [US1] Criar
frontend/src/services/jobsService.tscom funçãosubmitApplication(data: JobApplicationPayload): Promise<void>— chamaapi.post("/api/v1/jobs/apply", data)via instância Axios do projeto e relança o erro para tratamento no componente -
T009 [US1] Criar
frontend/src/pages/JobsPage.tsxcom formulário de candidatura:- Campos controlados com
useState:name,email,phone,role_interest(select comROLE_INTEREST_OPTIONS),message(textarea, contador de caracteres até 5000),file_name(input file decorativo — apenas registrae.target.files?.[0]?.name) - Validação frontend antes do submit: e-mail formato válido, campos obrigatórios não vazios, message ≤ 5000 chars, arquivo (se presente) deve ser PDF e ≤ 2 MB
- Estado
submitting: booleanpara desabilitar o botão durante o envio - Submit: chama
submitApplication(), em sucesso exibe mensagem de confirmação e limpa o formulário; em erro de rede exibe mensagem genérica sem apagar os dados preenchidos - Estilo: dark theme do projeto (
bg-panel,border-borderSubtle, accent#5e6ad2, tipografia Inter, Tailwind CSS)
- Campos controlados com
-
T010 [US1] Adicionar rota
/trabalhe-conoscoemfrontend/src/App.tsx: importarJobsPagee inserir<Route path="/trabalhe-conosco" element={<JobsPage />} />entre as rotas públicas
Checkpoint: Formulário em /trabalhe-conosco envia candidatura, recebe 201 e exibe confirmação. Erro de rede exibe mensagem sem apagar campos.
Phase 3: User Story 2 — Visitante Descobre a Oportunidade (Priority: P1)
Goal: Links "Trabalhe Conosco" no footer (coluna "A Imobiliária") e na página /corretores tornam a página de candidatura descobrível organicamente.
Independent Test: Acessar qualquer página e verificar que o footer contém o link "Trabalhe Conosco" na coluna "A Imobiliária". Acessar /corretores e verificar que existe elemento com texto "Trabalhe Conosco" que navega para /trabalhe-conosco.
-
T011 [P] [US2] Adicionar link "Trabalhe Conosco" em
frontend/src/components/Footer.tsx— localizar a coluna "A Imobiliária" e inserir<Link to="/trabalhe-conosco">Trabalhe Conosco</Link>seguindo o mesmo padrão visual dos demais links da coluna -
T012 [P] [US2] Adicionar link/botão "Trabalhe Conosco" em
frontend/src/pages/AgentsPage.tsx— inserir elemento (link<Link>ou botão secundário) com texto "Trabalhe Conosco" ehref/to="/trabalhe-conosco"em posição visível na página (ex.: ao final da seção de equipe ou como chamada à ação após o grid de corretores)
Checkpoint: Footer exibe link em todas as páginas. /corretores exibe elemento que navega para /trabalhe-conosco.
Phase 4: User Story 3 — Administrador Visualiza Candidaturas (Priority: P2)
Goal: Endpoint GET /api/v1/admin/jobs (implementado na Phase 1) retorna listagem paginada e corretamente serializada. Serviço Axios disponível para consumo futuro no painel admin.
Independent Test: Autenticar como admin e consultar GET /api/v1/admin/jobs?page=1&per_page=20. Verificar: resposta 200 com campos items, total, page, per_page, pages; cada item contém id, name, email, phone, role_interest, message, file_name, status, created_at. Sem token: 401. Token não-admin: 403.
- T013 [US3] Adicionar função
listApplications(page?: number, perPage?: number): Promise<PaginatedJobApplications>emfrontend/src/services/jobsService.ts— chamaapi.get("/api/v1/admin/jobs", { params: { page, per_page: perPage } })com header Authorization via instância autenticada do Axios; adicionar tipoPaginatedJobApplicationsemfrontend/src/types/jobApplication.tsespelhando o schema do contrato (items: JobApplicationItem[],total,page,per_page,pages)
Checkpoint: listApplications() pode ser chamado do console do browser (após login admin) e retorna dados paginados com a estrutura correta.
Phase 5: User Story 4 — Conteúdo Institucional (Priority: P3)
Goal: Página /trabalhe-conosco enriquecida com hero section e seção "Por que trabalhar conosco?" com 3 cards de benefícios, posicionados acima do formulário.
Independent Test: Acessar /trabalhe-conosco e verificar: hero section com título principal e subtítulo no topo; seção "Por que trabalhar conosco?" com exatamente 3 cards de benefícios antes do formulário; layout responsivo sem sobreposição em mobile.
-
T014 [US4] Adicionar hero section no topo de
frontend/src/pages/JobsPage.tsx— bloco com título principal (ex.: "Faça parte da nossa equipe") e subtítulo descritivo; seguir design tokens dark (text-primary,text-secondary, fundo com gradiente sutil oubg-surface); posicionar acima dos cards de benefícios e do formulário -
T015 [US4] Adicionar seção "Por que trabalhar conosco?" em
frontend/src/pages/JobsPage.tsxcom 3 cards de benefícios estáticos — cada card tem ícone SVG, título e descrição; layout em grid responsivo (grid-cols-1 md:grid-cols-3); estilobg-panel border border-borderSubtle rounded-xl; posicionar entre o hero e o formulário de candidatura. Sugestão de conteúdo dos cards: "Crescimento Profissional" / "Ambiente Colaborativo" / "Comissões Competitivas"
Checkpoint: /trabalhe-conosco exibe hero → 3 benefit cards → formulário nessa ordem. Em mobile (375 px) os cards empilham verticalmente sem overflow horizontal.
Phase 6: Polish & Verificação Final
- T016 Executar verificação end-to-end manualmente:
GET /api/v1/admin/jobssem token → 401POST /api/v1/jobs/applycom body válido → 201, candidatura registradaPOST /api/v1/jobs/applycom e-mail inválido → 422 comdetailsGET /api/v1/admin/jobs?page=1com token admin → 200 com a candidatura enviada- Browser:
/trabalhe-conoscorenderiza hero + 3 cards + formulário - Browser: footer → link "Trabalhe Conosco" → navega para
/trabalhe-conosco - Browser:
/corretores→ link "Trabalhe Conosco" → navega para/trabalhe-conosco
Dependencies & Execution Order
Dependências entre fases
Phase 1 (Foundational Backend)
│
├──→ Phase 2 (US1 — Formulário) ──→ Phase 4 (US3 — Admin service)
│ │
│ └──→ Phase 3 (US2 — Links de entrada)
│
└──→ Phase 5 (US4 — Conteúdo institucional, extensão da Phase 2)
│
└──→ Phase 6 (Polish)
- Phase 1: Sem dependências — começa imediatamente
- Phase 2: T007 e T008 podem começar em paralelo com Phase 1 (sem necessidade do backend para criar os arquivos TS); T009 depende de T007 + T008; T010 depende de T009
- Phase 3: T011 e T012 são paralelos entre si e independentes do backend; dependem apenas de T010 (rota já existir no App.tsx)
- Phase 4: T013 depende de T007/T008 (padrão do serviço) e do endpoint já implementado em T004
- Phase 5: T014 e T015 são modificações em JobsPage.tsx criado em T009 — devem ser feitas sequencialmente em relação a T009
- Phase 6: Depende de todas as fases anteriores
Dependências por task
| Task | Depende de | Pode ir em paralelo com |
|---|---|---|
| T001 | — | T003 |
| T002 | T001 | T003 |
| T003 | — | T001, T002 |
| T004 | T002, T003 | — |
| T005 | T002, T004 | — |
| T006 | T005 | — |
| T007 | — | T001–T006, T008, T011, T012 |
| T008 | T007 | T011, T012 |
| T009 | T007, T008 | T011, T012 |
| T010 | T009 | T011, T012 |
| T011 | T010 | T012 |
| T012 | T010 | T011 |
| T013 | T007, T008 | T011, T012 |
| T014 | T009 | T013 |
| T015 | T014 | T013 |
| T016 | T006, T015 | — |
Parallel Execution Examples
Fluxo MVP (US1 apenas — Phase 1 + Phase 2)
Stream A (Backend): T001 → T002 → T004 → T005 → T006
Stream B (Schemas): T003 (paralelo a T001-T002)
Stream C (Frontend): T007 → T008 → T009 → T010
Fluxo completo
Stream A (Backend): T001 → T002 → T004 → T005 → T006
Stream B (Schemas): T003
Stream C (Frontend): T007 → T008 → T009 → T010 → T014 → T015
Stream D (Links): T011 (paralelo após T010)
Stream E (Links): T012 (paralelo após T010)
Stream F (Admin svc): T013 (paralelo após T008)
Implementation Strategy
MVP Scope (Phase 1 + Phase 2): Formulário público funcional com persistência — entrega o núcleo da feature (US1 P1).
Incremento 2 (Phase 3): Links de descoberta — sem novos arquivos backend, apenas modificações pontuais em Footer e AgentsPage (US2 P1).
Incremento 3 (Phase 4): Serviço admin no frontend — prepara consumo da listagem (US3 P2); página admin React adiada para iteração futura.
Incremento 4 (Phase 5): Conteúdo institucional (hero + cards) sobre a base já existente de JobsPage (US4 P3).