251 lines
24 KiB
Markdown
251 lines
24 KiB
Markdown
# Tasks: Sistema de Autenticação de Clientes
|
||
|
||
**Feature**: `005-authentication`
|
||
**Branch**: `005-authentication`
|
||
**Input**: `spec.md`, `plan.md`, `data-model.md`, `contracts/auth-api.md`, `quickstart.md`
|
||
**Generated**: 2026-04-13
|
||
**Status**: Ready for implementation
|
||
|
||
---
|
||
|
||
## Format
|
||
|
||
```
|
||
- [ ] T[NNN] [P?] [USN?] Description — path/to/file.ext
|
||
```
|
||
|
||
- **[P]** — Paralelizável (arquivo diferente, sem dependência de tarefa incompleta)
|
||
- **[USN]** — User Story associada (US1–US4)
|
||
- IDs sequenciais na ordem de execução recomendada
|
||
|
||
---
|
||
|
||
## Phase 1: Setup — Infraestrutura e Dependências
|
||
|
||
**Objetivo**: Instalar as bibliotecas necessárias e configurar a variável de ambiente do segredo JWT. Sem estas tarefas nenhum código de autenticação pode ser executado.
|
||
|
||
**⚠️ CRÍTICO**: Todas as fases seguintes dependem de T001 e T002 estarem concluídas.
|
||
|
||
| ID | Complexidade | Deps | spec_ref |
|
||
|----|-------------|------|----------|
|
||
| T001 | S | — | plan.md §Primary Dependencies, quickstart.md §2 |
|
||
| T002 | S | — | plan.md §Constraints, spec.md §FR-010, SC-007 |
|
||
|
||
- [X] T001 Adicionar `"PyJWT>=2.9"`, `"bcrypt>=4.2"` e `"pydantic[email]"` às dependências em `[project].dependencies` e executar `uv add "PyJWT>=2.9" "bcrypt>=4.2" "pydantic[email]"` para atualizar `uv.lock` — `backend/pyproject.toml`
|
||
- **Done when**: `import jwt`, `import bcrypt` e `from pydantic import EmailStr` executam sem erro dentro do container; `uv.lock` reflete as três novas dependências; `uv run python -c "import jwt, bcrypt; from pydantic import EmailStr; print('ok')"` imprime `ok`.
|
||
|
||
- [X] T002 Adicionar variável `JWT_SECRET_KEY=<valor-gerado>` ao arquivo `backend/.env` (para desenvolvimento local) e como variável de ambiente no serviço `backend` do `docker-compose.yml`; o valor NUNCA deve ser string vazia ou placeholder óbvio como `"secret"` — `backend/.env` e `docker-compose.yml`
|
||
- **Done when**: `docker-compose config` mostra `JWT_SECRET_KEY` definida para o serviço `backend`; `grep -r "JWT_SECRET_KEY" backend/app/` não retorna nenhuma ocorrência com valor embutido (apenas leituras via `os.environ` ou `current_app.config`); `backend/.env` contém chave com pelo menos 32 caracteres hexadecimais.
|
||
|
||
**Checkpoint Phase 1**: `uv run python -c "import jwt, bcrypt"` executa sem erro no container; `JWT_SECRET_KEY` disponível como variável de ambiente.
|
||
|
||
---
|
||
|
||
## Phase 2: Foundational — Modelo, Migration e Schemas Base
|
||
|
||
**Objetivo**: Criar a tabela `client_users` no banco de dados e os schemas Pydantic compartilhados que todas as user stories utilizam. Estas tarefas bloqueiam a implementação de qualquer endpoint.
|
||
|
||
**⚠️ CRÍTICO**: T005 (rotas de auth) e T009 (frontend types) não podem avançar sem T003 e T004 concluídos.
|
||
|
||
| ID | Complexidade | Deps | spec_ref |
|
||
|----|-------------|------|----------|
|
||
| T003 | S | T001 | data-model.md §ClientUser, spec.md §Key Entities |
|
||
| T004 | M | T003 | data-model.md §DDL, quickstart.md §3 |
|
||
| T005 | S | T001 | data-model.md §Schemas Pydantic |
|
||
|
||
- [X] T003 Criar modelo SQLAlchemy `ClientUser` com colunas `id` (UUID PK, `default=uuid.uuid4`), `name` (String 150, NOT NULL), `email` (String 254, NOT NULL, unique=True, index=True), `password_hash` (String 100, NOT NULL), `role` (String 20, NOT NULL, `default='client'`), `created_at` (DateTime, NOT NULL, `server_default=func.now()`); importar o modelo em `backend/app/models/__init__.py` — `backend/app/models/user.py` e `backend/app/models/__init__.py`
|
||
- **Done when**: `from app.models.user import ClientUser` importa sem erro; `ClientUser.__tablename__ == "client_users"`; `ClientUser.email` tem `unique=True` e `index=True`; `ClientUser.role` tem `default='client'`; `from app.models import ClientUser` importa sem erro (via `__init__.py`); Flask-Migrate detecta a tabela ao rodar `flask db migrate`.
|
||
|
||
- [X] T004 Gerar e aplicar migration Alembic criando a tabela `client_users` com todas as colunas e índice `ix_client_users_email` — `backend/migrations/versions/<hash>_add_client_users.py`
|
||
- **Done when**: `uv run flask db migrate -m "add client_users"` cria arquivo de migration; revisão manual confirma `op.create_table("client_users", ...)` com colunas `id`, `name`, `email`, `password_hash`, `role`, `created_at` e `op.create_index("ix_client_users_email", "client_users", ["email"], unique=True)`; `flask db upgrade` executa sem erro; `flask db downgrade -1` reverte sem erro; `flask db upgrade` re-aplica sem erro.
|
||
|
||
- [X] T005 [P] Criar schemas Pydantic `RegisterIn` (name: str min=1/max=150, email: EmailStr normalizado lowercase, password: str min=8), `LoginIn` (email: EmailStr normalizado lowercase, password: str), `UserOut` (id: UUID, name: str, email: str, role: str, created_at: datetime; `model_config = ConfigDict(from_attributes=True)`), `AuthTokenOut` (access_token: str, user: UserOut) — `backend/app/schemas/auth.py`
|
||
- **Done when**: `from app.schemas.auth import RegisterIn, LoginIn, UserOut, AuthTokenOut` importa sem erro; `RegisterIn(name="A", email="TESTE@EX.COM", password="12345678").email == "teste@ex.com"` (normalização lowercase); `RegisterIn(name="A", email="ok@ok.com", password="1234567")` levanta `ValidationError` (senha < 8 chars); `UserOut.model_validate(client_user_instance)` serializa sem `password_hash`; `AuthTokenOut(access_token="tok", user=user_out_instance)` serializa corretamente.
|
||
|
||
**Checkpoint Phase 2**: Tabela `client_users` existe no banco; `from app.schemas.auth import RegisterIn` funciona; `flask db upgrade` passa sem erro.
|
||
|
||
---
|
||
|
||
## Phase 3: US1 — Cadastro Público de Novo Cliente (Priority: P1) 🎯 MVP
|
||
|
||
**Goal**: Qualquer visitante pode criar conta (POST /register); o sistema valida os dados, armazena senha com bcrypt e retorna token JWT + dados do usuário. O formulário de cadastro no frontend permite o fluxo completo.
|
||
|
||
**Independent Test**: `POST /api/v1/auth/register` com dados válidos retorna 201 + token; e-mail duplicado retorna 409; senha < 8 chars retorna 422; formulário no frontend completa o cadastro e redireciona para `/area-do-cliente`.
|
||
|
||
| ID | Complexidade | Deps | spec_ref |
|
||
|----|-------------|------|----------|
|
||
| T006 | S | T005 | plan.md §require_auth decorator, spec.md §FR-008, FR-010 |
|
||
| T007 | M | T003, T005, T006 | spec.md §API Contract, contracts/auth-api.md §register |
|
||
| T008 | S | T007 | plan.md §backend/__init__.py |
|
||
| T009 | S | — | plan.md §frontend types, spec.md §FR-011 |
|
||
| T010 | S | T009 | plan.md §api.ts interceptor, spec.md §FR-015 |
|
||
| T011 | S | T009 | plan.md §auth service, contracts/auth-api.md |
|
||
| T012 | M | T009, T011 | plan.md §AuthContext, spec.md §FR-011, FR-013 |
|
||
| T015 | M | T009, T011, T012 | spec.md §US1, FR-013, SC-001 |
|
||
|
||
- [X] T006 Criar decorator `require_auth` em `backend/app/utils/auth.py`: extrai Bearer token do header `Authorization`; decodifica via `jwt.decode(token, current_app.config["JWT_SECRET_KEY"], algorithms=["HS256"])`; em caso de token ausente, expirado ou inválido retorna `jsonify({"error": "Não autorizado."})` com status 401; em caso de sucesso, injeta `g.current_user_id = payload["sub"]` e chama o endpoint original — `backend/app/utils/auth.py`
|
||
- **Done when**: `from app.utils.auth import require_auth` importa sem erro; rota decorada com `@require_auth` retorna 401 quando `Authorization` está ausente; retorna 401 com token manipulado; retorna 401 com token expirado (TTL forçado para 0s em teste); `g.current_user_id` contém o UUID como string após autenticação bem-sucedida.
|
||
|
||
- [X] T007 Criar blueprint `auth_bp` com prefixo `/api/v1/auth` contendo três endpoints: (1) `POST /register` — valida `RegisterIn`, verifica e-mail duplicado (retorna 409 se existir), faz hash da senha com `bcrypt.hashpw`, persiste `ClientUser`, emite JWT com `sub=str(user.id)` e `exp=utcnow+7dias`, retorna `AuthTokenOut` com status 201; (2) `POST /login` — valida `LoginIn`, busca usuário por email, verifica senha com `bcrypt.checkpw` (retorna 401 genérico se inválido), emite JWT, retorna `AuthTokenOut` com status 200; (3) `GET /me` protegida por `@require_auth` — busca usuário por `g.current_user_id`, retorna `UserOut` com status 200 (retorna 401 se usuário não encontrado) — `backend/app/routes/auth.py`
|
||
- **Done when**: `POST /api/v1/auth/register` com dados válidos retorna 201 com `access_token` e `user` (sem `password_hash`); segundo POST com mesmo e-mail retorna 409; POST com senha de 5 chars retorna 422; `POST /api/v1/auth/login` com credenciais corretas retorna 200 + token; login com e-mail errado retorna 401; login com senha errada retorna 401 (mesma mensagem genérica); `GET /api/v1/auth/me` com token válido retorna 200 com `id`, `name`, `email`, `role`, `created_at`; `GET /api/v1/auth/me` sem token retorna 401.
|
||
|
||
- [X] T008 Registrar `auth_bp` na factory `create_app()` em `backend/app/__init__.py`: adicionar `from app.routes.auth import auth_bp` e `app.register_blueprint(auth_bp)`; garantir import de `ClientUser` antes do `db.create_all()` ou Alembic para que o modelo seja detectado; adicionar `app.config["JWT_SECRET_KEY"] = os.environ["JWT_SECRET_KEY"]` (raise `KeyError` se ausente) — `backend/app/__init__.py`
|
||
- **Done when**: Servidor Flask inicia sem erros após a alteração; `GET /api/v1/auth/register` retorna 405 (rota existe, método não permitido) — confirmando registro do blueprint; `app.config["JWT_SECRET_KEY"]` está definido em runtime; ausência da variável de ambiente no startup causa `KeyError` explícito (não silencioso).
|
||
|
||
- [X] T009 [P] Criar interfaces TypeScript `User` (id: string, name: string, email: string, role: string, created_at: string), `AuthTokenResponse` (access_token: string, user: User), `LoginCredentials` (email: string, password: string), `RegisterCredentials` (name: string, email: string, password: string, confirmPassword: string) e `AuthState` (user: User | null, isAuthenticated: boolean, isLoading: boolean) — `frontend/src/types/auth.ts`
|
||
- **Done when**: `import { User, AuthState, AuthTokenResponse, LoginCredentials, RegisterCredentials } from '@/types/auth'` compila sem erro TypeScript; `RegisterCredentials` inclui `confirmPassword` (validação apenas no frontend); `AuthState.user` é `null` quando não autenticado.
|
||
|
||
- [X] T010 [P] Atualizar o arquivo de serviços para exportar instância Axios com `baseURL` do backend; adicionar interceptor de request que injeta `Authorization: Bearer <token>` quando `localStorage.getItem("auth_token")` não for null; adicionar interceptor de response que limpa `localStorage` e redireciona para `/login` em resposta 401 — `frontend/src/services/api.ts`
|
||
- **Done when**: `import api from '@/services/api'` compila sem erro; requisição com token no `localStorage` inclui header `Authorization: Bearer <token>` (verificável no DevTools Network); requisição sem token não inclui o header; resposta 401 aciona limpeza do `localStorage` e navegação para `/login` sem loop (verificar que `/login` em si não dispara o interceptor em loop).
|
||
|
||
- [X] T011 [P] Criar serviço de autenticação com funções `registerUser(data: RegisterCredentials): Promise<AuthTokenResponse>`, `loginUser(data: LoginCredentials): Promise<AuthTokenResponse>` e `getMe(): Promise<User>`, todas chamando os endpoints do blueprint `auth_bp` via instância `api`; nenhuma URL hardcoded — `frontend/src/services/auth.ts`
|
||
- **Done when**: `import { registerUser, loginUser, getMe } from '@/services/auth'` compila sem erro TypeScript; `registerUser` chama `POST /api/v1/auth/register`; `loginUser` chama `POST /api/v1/auth/login`; `getMe` chama `GET /api/v1/auth/me`; erros 4xx/5xx são propagados para o caller sem silenciamento; nenhuma URL hardcoded (usa instância `api` de `api.ts`).
|
||
|
||
- [X] T012 Criar `AuthContext` com `AuthProvider` exportado que: inicializa com `isLoading: true`; em `useEffect` inicial tenta `getMe()` (se `localStorage` tem `auth_token`), define `user` e `isAuthenticated: true` em sucesso, limpa token e define `isAuthenticated: false` em falha; expõe funções `login(credentials): Promise<void>` (chama `loginUser`, salva token, define user) e `register(credentials): Promise<void>` (chama `registerUser`, salva token, define user) e `logout(): void` (remove token, limpa user, navega para `/login`); exportar hook `useAuth()` que consome o contexto — `frontend/src/contexts/AuthContext.tsx`
|
||
- **Done when**: `import { AuthProvider, useAuth } from '@/contexts/AuthContext'` compila sem erro; `useAuth()` fora do `AuthProvider` lança erro descritivo; após `login()` bem-sucedido `isAuthenticated === true` e `user` contém dados do servidor; `logout()` limpa estado e navega para `/login`; recarregar página com token válido no `localStorage` restaura sessão (`isAuthenticated: true`); recarregar com token expirado resulta em `isAuthenticated: false`.
|
||
|
||
- [X] T015 [US1] Criar `RegisterPage` com formulário contendo campos `name` (obrigatório), `email` (obrigatório, formato), `password` (obrigatório, mín. 8 chars), `confirmPassword` (obrigatório, deve igualar `password`); validação `confirmPassword !== password` impede envio e exibe mensagem no campo; ao submeter chama `register()` do `useAuth()`; em sucesso redireciona para `/area-do-cliente`; em erro de rede exibe mensagem amigável; exibe spinner no botão durante requisição; link para `/login` para usuários já cadastrados; segue design system DESIGN.md (fundo `#08090a`, painel `#0f1011`, accent `#5e6ad2`/`#7170ff`, tipografia Inter Variable) — `frontend/src/pages/RegisterPage.tsx`
|
||
- **Done when**: Senha < 8 chars exibe erro de validação sem enviar requisição; `confirmPassword` diferente exibe "As senhas não coincidem" sem enviar; POST inválido (e-mail duplicado) exibe mensagem de erro amigável; POST válido redireciona para `/area-do-cliente`; botão desabilitado durante `isSubmitting`; `npm run build` não apresenta erros TypeScript; estilos correspondem ao design system vigente.
|
||
|
||
**Checkpoint Phase 3**: `POST /api/v1/auth/register` funciona end-to-end; formulário de cadastro no frontend completa o fluxo; usuário pode ver `/area-do-cliente` após cadastro.
|
||
|
||
---
|
||
|
||
## Phase 4: US2 — Login de Cliente Existente (Priority: P2)
|
||
|
||
**Goal**: Um cliente cadastrado pode autenticar-se com e-mail e senha, receber token JWT e ser redirecionado para `/area-do-cliente`. Erros de credencial retornam mensagem genérica.
|
||
|
||
**Independent Test**: Login com credenciais corretas redireciona para `/area-do-cliente`; e-mail errado retorna 401 com mensagem genérica; senha errada retorna 401 com mesma mensagem genérica; erro de rede exibe mensagem amigável.
|
||
|
||
| ID | Complexidade | Deps | spec_ref |
|
||
|----|-------------|------|----------|
|
||
| T014 | M | T009, T011, T012 | spec.md §US2, FR-013, SC-002 |
|
||
|
||
> **Nota**: O endpoint `POST /api/v1/auth/login` foi implementado em T007 (Phase 3). Esta fase foca na interface do cliente.
|
||
|
||
- [X] T014 [US2] Criar `LoginPage` com formulário contendo campos `email` (obrigatório, formato) e `password` (obrigatório); ao submeter chama `login()` do `useAuth()`; em sucesso redireciona para `/area-do-cliente`; em erro 401 exibe "E-mail ou senha incorretos." sem indicar qual campo falhou; em erro de rede exibe "Erro de conexão. Tente novamente."; exibe spinner no botão durante requisição; link para `/cadastro` para novos usuários; segue design system DESIGN.md — `frontend/src/pages/LoginPage.tsx`
|
||
- **Done when**: E-mail inválido (formato) exibe erro de validação sem enviar requisição; credenciais erradas exibem "E-mail ou senha incorretos." (mesmo texto para e-mail ou senha incorretos); credenciais corretas redirecionam para `/area-do-cliente`; botão desabilitado durante `isSubmitting`; `npm run build` não apresenta erros TypeScript; estilos correspondem ao design system vigente.
|
||
|
||
**Checkpoint Phase 4**: Login end-to-end funcional — cliente existente autentica-se e é redirecionado; 401 exibe mensagem genérica; erro de rede tratado.
|
||
|
||
---
|
||
|
||
## Phase 5: US3 — Acesso a Rotas Protegidas com Token (Priority: P3)
|
||
|
||
**Goal**: Rotas protegidas no frontend redirecionam para `/login` usuários não autenticados. Rotas protegidas no backend rejeitam requisições sem token válido com 401.
|
||
|
||
**Independent Test**: Acessar `/area-do-cliente` sem token redireciona para `/login`; com token válido renderiza conteúdo; token adulterado/expirado retorna 401 do backend e redireciona para `/login` no frontend.
|
||
|
||
| ID | Complexidade | Deps | spec_ref |
|
||
|----|-------------|------|----------|
|
||
| T013 | S | T009, T012 | spec.md §US3, FR-012 |
|
||
| T016 | M | T012, T013, T014, T015 | spec.md §FR-011, FR-012, FR-013, FR-015 |
|
||
|
||
> **Nota**: O decorator `@require_auth` foi implementado em T006 (Phase 3). Esta fase foca no roteamento protegido do frontend e na integração final do App.tsx.
|
||
|
||
- [X] T013 [P] [US3] Criar componente `ProtectedRoute` que consome `useAuth()`; enquanto `isLoading === true` renderiza spinner ou `null` (evita redirect prematuro); se `!isAuthenticated` retorna `<Navigate to="/login" replace />`; caso contrário renderiza `<Outlet />` — `frontend/src/components/ProtectedRoute.tsx`
|
||
- **Done when**: Sem token no `localStorage`, acessar rota protegida redireciona para `/login`; com token válido, a rota protegida renderiza normalmente; durante `isLoading` (verificação inicial) não há redirect prematuro; `npm run build` não apresenta erros TypeScript.
|
||
|
||
- [X] T016 [US3] Atualizar `App.tsx` para: envolver toda a árvore de rotas com `<AuthProvider>`; adicionar rota `<Route path="/login" element={<LoginPage />} />`; adicionar rota `<Route path="/cadastro" element={<RegisterPage />} />`; criar grupo de rotas protegidas com `<Route element={<ProtectedRoute />}>` contendo `<Route path="/area-do-cliente" element={<ClientAreaPage />} />` (pode ser página placeholder); importar todos os novos componentes e páginas — `frontend/src/App.tsx`
|
||
- **Done when**: `/login` renderiza `LoginPage`; `/cadastro` renderiza `RegisterPage`; `/area-do-cliente` sem autenticação redireciona para `/login`; `/area-do-cliente` com autenticação renderiza conteúdo (pode ser placeholder); `npm run build` sem erros TypeScript; `<AuthProvider>` envolve todas as rotas.
|
||
|
||
**Checkpoint Phase 5**: Fluxo completo de proteção funcional — usuário não autenticado é redirecionado; token inválido é tratado; `ProtectedRoute` garante isolamento.
|
||
|
||
---
|
||
|
||
## Phase 6: US4 — Visualização do Perfil do Cliente Autenticado (Priority: P4)
|
||
|
||
**Goal**: Cliente autenticado pode consultar seus dados de perfil via `GET /api/v1/auth/me`. A Navbar exibe nome do usuário + opção de logout quando autenticado.
|
||
|
||
**Independent Test**: `GET /api/v1/auth/me` com token válido retorna `id, name, email, role, created_at`; sem token retorna 401; Navbar exibe "Entrar" para visitantes e nome + "Sair" para autenticados.
|
||
|
||
| ID | Complexidade | Deps | spec_ref |
|
||
|----|-------------|------|----------|
|
||
| T017 | S | T012 | spec.md §US4, FR-014, SC-005 |
|
||
|
||
> **Nota**: O endpoint `GET /api/v1/auth/me` foi implementado em T007 (Phase 3). Esta fase foca na Navbar.
|
||
|
||
- [X] T017 [US4] Atualizar `Navbar` para consumir `useAuth()`; quando `!isAuthenticated` exibe botão/link "Entrar" apontando para `/login`; quando `isAuthenticated` exibe nome do usuário (`user.name`) truncado se necessário + botão "Sair" que chama `logout()`; durante `isLoading` exibe placeholder neutro (evita flash de estado incorreto); garantir que o estado mude reativamente sem reload — `frontend/src/components/Navbar.tsx`
|
||
- **Done when**: Navbar exibe "Entrar" para visitante; após login exibe nome do usuário e "Sair"; clicar em "Sair" chama `logout()`, limpa sessão e exibe "Entrar" novamente; durante `isLoading` não há flash de "Entrar"/"Sair"; `npm run build` não apresenta erros TypeScript.
|
||
|
||
**Checkpoint Phase 6**: Perfil consultável via API; Navbar reflete estado de autenticação reativamente.
|
||
|
||
---
|
||
|
||
## Phase 7: Polish & Cross-Cutting Concerns
|
||
|
||
**Objetivo**: Validações finais de segurança, conformidade com requisitos não-funcionais e verificação do quickstart.
|
||
|
||
| ID | Complexidade | Deps | spec_ref |
|
||
|----|-------------|------|----------|
|
||
| T018 | S | T007 | spec.md §SC-006, SC-007, FR-007 |
|
||
| T019 | S | T001–T017 | quickstart.md §4, spec.md §SC-001–SC-003 |
|
||
|
||
- [X] T018 [P] Auditar `backend/app/routes/auth.py` e `backend/app/utils/auth.py` para garantir: (a) nenhuma resposta contém `password_hash`; (b) mensagens de erro de login são idênticas para e-mail inexistente e senha incorreta (SC-006); (c) `JWT_SECRET_KEY` nunca aparece em log ou resposta; (d) `bcrypt.checkpw` é chamado mesmo quando o usuário não existe (para evitar timing attack de enumeração de e-mail) — `backend/app/routes/auth.py` e `backend/app/utils/auth.py`
|
||
- **Done when**: `grep -r "password_hash" backend/app/routes/ backend/app/schemas/` retorna apenas declarações de model, nunca em serialização de resposta; curl de login com e-mail inexistente e com senha errada retornam exatamente a mesma resposta JSON; `grep -r "JWT_SECRET_KEY" backend/app/` mostra apenas leituras via `current_app.config`, nunca o valor em si; revisão manual confirma `bcrypt.checkpw` executado mesmo para usuário não encontrado (dummy hash).
|
||
|
||
- [X] T019 Executar o roteiro completo do `quickstart.md`: instalar dependências, gerar/aplicar migration, testar os três endpoints via `Invoke-WebRequest` (register, login, me), verificar que o frontend completa o fluxo cadastro → área do cliente e login → área do cliente — `quickstart.md` (verificação, sem edição de código)
|
||
- **Done when**: Todos os comandos do quickstart executam sem erro; `POST /register` retorna 201; `POST /login` retorna 200; `GET /me` com token retorna 200; frontend em `http://localhost:5173/cadastro` completa cadastro e redireciona; `http://localhost:5173/login` completa login e redireciona; `http://localhost:5173/area-do-cliente` sem sessão redireciona para `/login`.
|
||
|
||
---
|
||
|
||
## Dependency Graph
|
||
|
||
```
|
||
T001 ──┬── T003 ──── T004
|
||
│ └── T007 ──────┐
|
||
└── T005 ──── T007 │
|
||
│
|
||
T002 ──────────── T008 ◄────────┘
|
||
|
||
T006 ──────────── T007
|
||
|
||
T009 ──┬── T010
|
||
├── T011 ──┬── T012 ──┬── T013 ──┐
|
||
│ └── T015 ├── T014 │
|
||
│ └── T015 │
|
||
└── (via T012) │
|
||
│
|
||
T013 ──────────── T016 ◄────────────────┘
|
||
T014 ──────────── T016
|
||
T015 ──────────── T016
|
||
|
||
T012 ──────────── T017
|
||
|
||
T007, T016, T017 ── T018, T019
|
||
```
|
||
|
||
## Parallel Execution Examples
|
||
|
||
### Backend e Frontend em paralelo
|
||
|
||
```
|
||
# Terminal 1 — Backend
|
||
T001 → T002 → T003 → T004 → T005 (paralelo com T003) →
|
||
T006 → T007 → T008 → auditar T018
|
||
|
||
# Terminal 2 — Frontend (pode iniciar após T001)
|
||
T009 → T010 (paralelo), T011 (paralelo) →
|
||
T012 → T013 (paralelo), T014 (paralelo), T015 (paralelo) →
|
||
T016 → T017
|
||
```
|
||
|
||
### Componentes frontend em paralelo (após T012)
|
||
|
||
```
|
||
# T013, T014, T015 podem ser implementados em paralelo
|
||
# pois estão em arquivos distintos e dependem apenas de T012
|
||
```
|
||
|
||
---
|
||
|
||
## Implementation Strategy (MVP Scope)
|
||
|
||
| Prioridade | Fase | User Story | Tarefas | Entregável |
|
||
|---|---|---|---|---|
|
||
| **MVP** | Setup + Foundational + Phase 3 | US1 (Cadastro) | T001–T008, T009–T012, T015 | Usuário pode se cadastrar e acessar área do cliente |
|
||
| **Incremental** | Phase 4 | US2 (Login) | T014 | Usuário existente pode fazer login |
|
||
| **Incremental** | Phase 5 | US3 (Proteção) | T013, T016 | Rotas protegidas e redirecionamento |
|
||
| **Completude** | Phase 6 | US4 (Perfil) | T017 | Navbar com estado de autenticação |
|
||
|
||
**MVP mínimo**: T001 → T002 → T003 → T004 → T005 → T006 → T007 → T008 → T009 → T010 → T011 → T012 → T015 → T016 (básico)
|