--- 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 `i1j2k3l4m5n6` em `backend/migrations/versions/i1j2k3l4m5n6_add_job_applications.py` com `down_revision = "h1i2j3k4l5m6"` — implementar `upgrade()` criando a tabela `job_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_default `now()`) + 2 índices (`ix_job_applications_created_at` em `created_at`, `ix_job_applications_status` em `status`); `downgrade()` remove índices e tabela na ordem inversa - [ ] T002 Criar modelo SQLAlchemy `JobApplication` em `backend/app/models/job_application.py` — classe com `__tablename__ = "job_applications"`, 9 colunas mapeando o schema da tabela (status com `default="pending"`, created_at com `server_default=db.func.now()`), constante `ROLE_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)`: campos `name: str` (strip, não vazio), `email: EmailStr`, `phone: str | None = None`, `role_interest: str` (validado contra `ROLE_INTEREST_OPTIONS` via `@field_validator`), `message: str` (max_length=5000, não vazio), `file_name: str | None = None` - `JobApplicationOut(BaseModel)`: todos os campos de `JobApplicationIn` + `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.py` com dois blueprints: - `jobs_public_bp = Blueprint("jobs_public", __name__)`: endpoint `POST /jobs/apply` público — valida body via `JobApplicationIn` (retorna 422 com `{"error": "Dados inválidos", "details": ...}` em ValidationError), cria e salva `JobApplication` via `db.session`, retorna `{"message": "Candidatura recebida com sucesso"}` com status 201 - `jobs_admin_bp = Blueprint("jobs_admin", __name__)`: endpoint `GET /jobs` decorado com `@require_admin` — lê query params `page` (default 1, ≥ 1) e `per_page` (default 20, clamp 1–100), consulta `JobApplication.query.order_by(JobApplication.created_at.desc()).paginate(...)`, serializa via `PaginatedJobApplications` e 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_bp` com `url_prefix="/api/v1"` e `jobs_admin_bp` com `url_prefix="/api/v1/admin"` na função `create_app()` - [ ] T006 Aplicar migration no container e verificar schema: `docker-compose exec backend flask db upgrade` → confirmar tabela com `docker-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`: ```typescript 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.ts` com função `submitApplication(data: JobApplicationPayload): Promise` — chama `api.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.tsx` com formulário de candidatura: - Campos controlados com `useState`: `name`, `email`, `phone`, `role_interest` (select com `ROLE_INTEREST_OPTIONS`), `message` (textarea, contador de caracteres até 5000), `file_name` (input file decorativo — apenas registra `e.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: boolean` para 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) - [ ] T010 [US1] Adicionar rota `/trabalhe-conosco` em `frontend/src/App.tsx`: importar `JobsPage` e inserir `} />` 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 `Trabalhe Conosco` 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 `` ou botão secundário) com texto "Trabalhe Conosco" e `href`/`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` em `frontend/src/services/jobsService.ts` — chama `api.get("/api/v1/admin/jobs", { params: { page, per_page: perPage } })` com header Authorization via instância autenticada do Axios; adicionar tipo `PaginatedJobApplications` em `frontend/src/types/jobApplication.ts` espelhando 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 ou `bg-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.tsx` com 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`); estilo `bg-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: 1. `GET /api/v1/admin/jobs` sem token → 401 2. `POST /api/v1/jobs/apply` com body válido → 201, candidatura registrada 3. `POST /api/v1/jobs/apply` com e-mail inválido → 422 com `details` 4. `GET /api/v1/admin/jobs?page=1` com token admin → 200 com a candidatura enviada 5. Browser: `/trabalhe-conosco` renderiza hero + 3 cards + formulário 6. Browser: footer → link "Trabalhe Conosco" → navega para `/trabalhe-conosco` 7. 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).