sass-imobiliaria/specs/028-trabalhe-conosco/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

217 lines
14 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.

---
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 1100), 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<void>` — 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 `<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" 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<PaginatedJobApplications>` 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 | — | T001T006, 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).