1011 lines
25 KiB
Markdown
1011 lines
25 KiB
Markdown
# MediConnect - Sistema de Agendamento Médico
|
||
|
||
Sistema completo de gestão médica com agendamento inteligente, prontuários eletrônicos e gerenciamento de pacientes.
|
||
|
||
**Stack:** React + TypeScript + Vite + TailwindCSS + Supabase
|
||
**Deploy:** Cloudflare Pages
|
||
|
||
---
|
||
|
||
## 🚀 Acesso ao Sistema
|
||
|
||
- **URL Principal:** https://mediconnectbrasil.app/
|
||
- **URL Cloudflare:** https://mediconnect-5oz.pages.dev/
|
||
|
||
### Credenciais de Teste
|
||
|
||
**Médico:**
|
||
|
||
- Email: medico@teste.com
|
||
- Senha: senha123
|
||
|
||
**Paciente:**
|
||
|
||
- Email: paciente@teste.com
|
||
- Senha: senha123
|
||
|
||
**Secretária:**
|
||
|
||
- Email: secretaria@teste.com
|
||
- Senha: senha123
|
||
|
||
---
|
||
|
||
## 🏗️ Arquitetura
|
||
|
||
```
|
||
Frontend (React/Vite)
|
||
↓
|
||
Supabase Backend
|
||
├── Auth (JWT + Magic Link)
|
||
├── PostgreSQL (PostgREST)
|
||
├── Edge Functions (Slots, Criação de Usuários)
|
||
└── Storage (Avatares, Documentos)
|
||
↓
|
||
Cloudflare Pages (Deploy)
|
||
```
|
||
|
||
---
|
||
|
||
## <20> Instalação e Execução
|
||
|
||
### Pré-requisitos
|
||
|
||
- Node.js 18+
|
||
- pnpm (recomendado) ou npm
|
||
|
||
### Instalação
|
||
|
||
```bash
|
||
# Instalar dependências
|
||
pnpm install
|
||
|
||
# Iniciar desenvolvimento
|
||
pnpm dev
|
||
|
||
# Acessar em http://localhost:5173
|
||
```
|
||
|
||
### Build e Deploy
|
||
|
||
```bash
|
||
# Build de produção
|
||
pnpm build
|
||
|
||
# Deploy para Cloudflare Pages
|
||
pnpm wrangler pages deploy dist --project-name=mediconnect --branch=production
|
||
```
|
||
|
||
---
|
||
|
||
## ✨ Funcionalidades Principais
|
||
|
||
### 🏥 Para Médicos
|
||
|
||
- ✅ Agenda personalizada com disponibilidade configurável
|
||
- ✅ Gerenciamento de exceções (bloqueios e horários extras)
|
||
- ✅ Prontuário eletrônico completo
|
||
- ✅ Histórico de consultas do paciente
|
||
- ✅ Dashboard com métricas e estatísticas
|
||
- ✅ Teleconsulta e presencial
|
||
|
||
### 👥 Para Pacientes
|
||
|
||
- ✅ Agendamento inteligente com slots disponíveis em tempo real
|
||
- ✅ Histórico completo de consultas
|
||
- ✅ Visualização e download de relatórios médicos (PDF)
|
||
- ✅ Perfil com avatar e dados pessoais
|
||
- ✅ Filtros por médico, especialidade e data
|
||
|
||
### 🏢 Para Secretárias
|
||
|
||
- ✅ Gerenciamento completo de médicos, pacientes e consultas
|
||
- ✅ Cadastro com validação de CPF e CRM
|
||
- ✅ Configuração de agenda médica (horários e exceções)
|
||
- ✅ Busca e filtros avançados
|
||
- ✅ Confirmação profissional para exclusões
|
||
|
||
### 🔐 Sistema de Autenticação
|
||
|
||
- ✅ Login com email/senha
|
||
- ✅ Magic Link (login sem senha)
|
||
- ✅ Recuperação de senha
|
||
- ✅ Tokens JWT com refresh automático
|
||
- ✅ Controle de acesso por role (médico/paciente/secretária)
|
||
|
||
---
|
||
|
||
## 🔧 Tecnologias
|
||
|
||
### Frontend
|
||
|
||
- **React 18** - Interface moderna e reativa
|
||
- **TypeScript** - Tipagem estática
|
||
- **Vite** - Build ultra-rápido
|
||
- **TailwindCSS** - Estilização utilitária
|
||
- **React Router** - Navegação SPA
|
||
- **Axios** - Cliente HTTP
|
||
- **date-fns** - Manipulação de datas
|
||
- **jsPDF** - Geração de PDFs
|
||
- **Lucide Icons** - Ícones modernos
|
||
|
||
### Backend (Supabase)
|
||
|
||
- **PostgreSQL** - Banco de dados relacional
|
||
- **PostgREST** - API REST automática
|
||
- **Edge Functions** - Funções serverless (Deno)
|
||
- **Storage** - Armazenamento de arquivos
|
||
- **Auth** - Autenticação e autorização
|
||
|
||
### Deploy
|
||
|
||
- **Cloudflare Pages** - Hospedagem global com CDN
|
||
|
||
---
|
||
|
||
## 📁 Estrutura do Projeto
|
||
|
||
```
|
||
MEDICONNECT 2/
|
||
├── src/
|
||
│ ├── components/ # Componentes React
|
||
│ │ ├── auth/ # Login, cadastro, recuperação
|
||
│ │ ├── secretaria/ # Painéis da secretária
|
||
│ │ ├── agenda/ # Sistema de agendamento
|
||
│ │ ├── consultas/ # Gerenciamento de consultas
|
||
│ │ └── ui/ # Componentes reutilizáveis
|
||
│ ├── pages/ # Páginas da aplicação
|
||
│ │ ├── Home.tsx
|
||
│ │ ├── PainelMedico.tsx
|
||
│ │ ├── PainelSecretaria.tsx
|
||
│ │ └── AgendamentoPaciente.tsx
|
||
│ ├── services/ # Camada de API
|
||
│ │ ├── api/ # Cliente HTTP
|
||
│ │ ├── auth/ # Autenticação
|
||
│ │ ├── appointments/ # Agendamentos
|
||
│ │ ├── doctors/ # Médicos
|
||
│ │ ├── patients/ # Pacientes
|
||
│ │ ├── availability/ # Disponibilidade
|
||
│ │ └── avatars/ # Avatares
|
||
│ ├── context/ # Context API
|
||
│ ├── hooks/ # Custom hooks
|
||
│ ├── types/ # TypeScript types
|
||
│ └── utils/ # Funções utilitárias
|
||
├── public/ # Arquivos estáticos
|
||
├── scripts/ # Scripts de utilidade
|
||
└── dist/ # Build de produção
|
||
```
|
||
|
||
---
|
||
|
||
## 🔑 APIs e Serviços
|
||
|
||
### Principais Endpoints
|
||
|
||
#### Agendamentos
|
||
|
||
```typescript
|
||
// Buscar slots disponíveis (Edge Function)
|
||
POST /functions/v1/get-available-slots
|
||
{
|
||
"doctor_id": "uuid",
|
||
"date": "2025-10-30"
|
||
}
|
||
|
||
// Criar agendamento
|
||
POST /rest/v1/appointments
|
||
{
|
||
"doctor_id": "uuid",
|
||
"patient_id": "uuid",
|
||
"scheduled_at": "2025-10-30T09:00:00Z",
|
||
"duration_minutes": 30,
|
||
"appointment_type": "presencial"
|
||
}
|
||
```
|
||
|
||
#### Disponibilidade
|
||
|
||
```typescript
|
||
// Listar disponibilidade do médico
|
||
GET /rest/v1/doctor_availability?doctor_id=eq.{uuid}
|
||
|
||
// Criar horário de atendimento
|
||
POST /rest/v1/doctor_availability
|
||
|
||
// Atualizar disponibilidade
|
||
PATCH /rest/v1/doctor_availability?id=eq.{uuid}
|
||
```
|
||
|
||
#### Usuários
|
||
|
||
```typescript
|
||
// Criar médico (Edge Function com validações)
|
||
POST /functions/v1/create-doctor
|
||
|
||
// Criar paciente
|
||
POST /rest/v1/patients
|
||
|
||
// Listar médicos
|
||
GET /rest/v1/doctors?select=*
|
||
|
||
// Atualizar perfil
|
||
PATCH /rest/v1/doctors?id=eq.{uuid}
|
||
```
|
||
|
||
**Documentação completa:** Ver [AGENDAMENTO-SLOTS-API.md](./AGENDAMENTO-SLOTS-API.md)
|
||
|
||
---
|
||
|
||
## 🔒 Autenticação e Permissões
|
||
|
||
### Sistema de Autenticação
|
||
|
||
- **JWT Tokens** com refresh automático
|
||
- **Magic Link** - Login sem senha via email
|
||
- **Recuperação de senha** com email
|
||
- **Interceptors** adicionam token automaticamente
|
||
- **Renovação automática** quando token expira
|
||
|
||
### Roles e Permissões (RLS)
|
||
|
||
**Admin/Gestor:**
|
||
|
||
- Acesso completo a todos os recursos
|
||
- Criar/editar/deletar usuários
|
||
- Visualizar todos os dados
|
||
|
||
**Médicos:**
|
||
|
||
- Gerenciar agenda e disponibilidade
|
||
- Visualizar todos os pacientes
|
||
- Criar e editar prontuários
|
||
- Ver apenas próprios agendamentos
|
||
|
||
**Pacientes:**
|
||
|
||
- Agendar consultas
|
||
- Visualizar histórico próprio
|
||
- Editar perfil pessoal
|
||
- Download de relatórios médicos
|
||
|
||
**Secretárias:**
|
||
|
||
- Cadastrar médicos e pacientes
|
||
- Gerenciar agendamentos
|
||
- Configurar agendas médicas
|
||
- Busca e filtros avançados
|
||
|
||
---
|
||
|
||
## 🎨 Recursos de Acessibilidade
|
||
|
||
- ✅ Modo de alto contraste
|
||
- ✅ Ajuste de tamanho de fonte
|
||
- ✅ Navegação por teclado
|
||
- ✅ Leitores de tela compatíveis
|
||
- ✅ Menu de acessibilidade flutuante
|
||
|
||
---
|
||
|
||
## 📊 Dashboards e Relatórios
|
||
|
||
### Médico
|
||
|
||
- Total de pacientes atendidos
|
||
- Consultas do dia/semana/mês
|
||
- Próximas consultas
|
||
- Histórico de atendimentos
|
||
|
||
### Paciente
|
||
|
||
- Histórico de consultas
|
||
- Relatórios médicos com download PDF
|
||
- Próximos agendamentos
|
||
- Acompanhamento médico
|
||
|
||
### Secretária
|
||
|
||
- Visão geral de agendamentos
|
||
- Filtros por médico, data e status
|
||
- Busca de pacientes e médicos
|
||
- Estatísticas gerais
|
||
|
||
---
|
||
|
||
## 🚀 Melhorias Recentes (Outubro 2025)
|
||
|
||
### Sistema de Agendamento
|
||
|
||
- ✅ API de slots disponíveis (Edge Function)
|
||
- ✅ Cálculo automático de horários
|
||
- ✅ Validação de antecedência mínima
|
||
- ✅ Verificação de conflitos
|
||
- ✅ Interface otimizada
|
||
|
||
### Formatação de Dados
|
||
|
||
- ✅ Limpeza automática de telefone/CPF
|
||
- ✅ Formatação de nomes de médicos ("Dr.")
|
||
- ✅ Validação de campos obrigatórios
|
||
- ✅ Máscaras de entrada
|
||
|
||
### UX/UI
|
||
|
||
- ✅ Diálogos de confirmação profissionais
|
||
- ✅ Filtros de busca em todas as listas
|
||
- ✅ Feedback visual melhorado
|
||
- ✅ Loading states consistentes
|
||
- ✅ Mensagens de erro claras
|
||
|
||
### Performance
|
||
|
||
- ✅ Build otimizado (~424KB)
|
||
- ✅ Code splitting
|
||
- ✅ Lazy loading de rotas
|
||
- ✅ Cache de assets
|
||
|
||
---
|
||
|
||
## 📝 Convenções de Código
|
||
|
||
### TypeScript
|
||
|
||
- Interfaces para todas as entidades
|
||
- Tipos explícitos em funções
|
||
- Evitar `any` (usar `unknown` quando necessário)
|
||
|
||
### Componentes React
|
||
|
||
- Functional components com hooks
|
||
- Props tipadas com interfaces
|
||
- Estado local com useState/useContext
|
||
- Effects para side effects
|
||
|
||
### Serviços
|
||
|
||
- Um serviço por entidade (doctorService, patientService)
|
||
- Métodos assíncronos com try/catch
|
||
- Logs de debug no console
|
||
- Tratamento de erros consistente
|
||
|
||
### Nomenclatura
|
||
|
||
- **Componentes:** PascalCase (ex: `AgendamentoConsulta`)
|
||
- **Arquivos:** kebab-case ou PascalCase conforme tipo
|
||
- **Variáveis:** camelCase (ex: `selectedDate`)
|
||
- **Constantes:** UPPER_SNAKE_CASE (ex: `API_CONFIG`)
|
||
|
||
---
|
||
|
||
## 🐛 Troubleshooting
|
||
|
||
### Erro 401 (Unauthorized)
|
||
|
||
- Verificar se token está no localStorage
|
||
- Tentar logout e login novamente
|
||
- Verificar permissões RLS no Supabase
|
||
|
||
### Slots não aparecem
|
||
|
||
- Verificar se médico tem disponibilidade configurada
|
||
- Verificar se data é futura
|
||
- Verificar logs da Edge Function
|
||
|
||
### Upload de avatar falha
|
||
|
||
- Verificar tamanho do arquivo (max 2MB)
|
||
- Verificar formato (jpg, png)
|
||
- Verificar permissões do Storage no Supabase
|
||
|
||
### Build falha
|
||
|
||
- Limpar cache: `rm -rf node_modules dist`
|
||
- Reinstalar: `pnpm install`
|
||
- Verificar versão do Node (18+)
|
||
|
||
---
|
||
|
||
## 👥 Equipe
|
||
|
||
**RiseUp Squad 18**
|
||
|
||
- Desenvolvimento: GitHub Copilot + Equipe
|
||
- Data: Outubro 2025
|
||
|
||
---
|
||
|
||
## 📄 Licença
|
||
|
||
Este projeto é privado e desenvolvido para fins educacionais.
|
||
|
||
---
|
||
|
||
## <20> Links Úteis
|
||
|
||
- [Supabase Docs](https://supabase.com/docs)
|
||
- [React Docs](https://react.dev/)
|
||
- [Vite Docs](https://vitejs.dev/)
|
||
- [TailwindCSS Docs](https://tailwindcss.com/)
|
||
- [Cloudflare Pages](https://pages.cloudflare.com/)
|
||
|
||
---
|
||
|
||
**Última atualização:** 30 de Outubro de 2025
|
||
|
||
- ✅ Veem apenas **seus próprios dados**
|
||
- ✅ Veem apenas **seus próprios laudos** (filtro: `patient_id = paciente`)
|
||
- ✅ Veem apenas **seus próprios agendamentos**
|
||
- ✅ Podem agendar consultas
|
||
|
||
#### 👩💼 Secretárias:
|
||
|
||
- ✅ Veem **todos os pacientes**
|
||
- ✅ Veem **todos os agendamentos**
|
||
- ✅ Veem **todos os laudos**
|
||
- ✅ Podem criar/editar agendamentos
|
||
|
||
---
|
||
|
||
## 📡 API e Serviços
|
||
|
||
### Estrutura de Services
|
||
|
||
O projeto usa uma arquitetura de **services** que encapsulam toda comunicação com o Supabase:
|
||
|
||
```
|
||
src/services/
|
||
├── api/
|
||
│ ├── client.ts # Cliente HTTP (Axios configurado)
|
||
│ └── config.ts # Configurações da API
|
||
├── auth/
|
||
│ ├── authService.ts # Login, signup, recuperação de senha
|
||
│ └── types.ts
|
||
├── users/
|
||
│ └── userService.ts # getUserInfo, createUser, deleteUser
|
||
├── patients/
|
||
│ └── patientService.ts # CRUD de pacientes
|
||
├── doctors/
|
||
│ └── doctorService.ts # CRUD de médicos
|
||
├── appointments/
|
||
│ └── appointmentService.ts # Agendamentos
|
||
└── availability/
|
||
└── availabilityService.ts # Disponibilidade médica
|
||
```
|
||
|
||
### Principais Endpoints
|
||
|
||
#### 🔐 Autenticação (authService)
|
||
|
||
```typescript
|
||
// Login com Email e Senha
|
||
await authService.login({ email, password });
|
||
// Retorna: { access_token, refresh_token, user }
|
||
|
||
// Magic Link (Login sem senha)
|
||
await authService.sendMagicLink("email@example.com");
|
||
// Envia email com link de autenticação
|
||
// Usuário clica no link e é automaticamente autenticado
|
||
|
||
// Recuperação de senha
|
||
await authService.requestPasswordReset("email@example.com");
|
||
// Envia email com link de reset
|
||
|
||
// Atualizar senha
|
||
await authService.updatePassword(accessToken, newPassword);
|
||
// Usado na página /reset-password
|
||
|
||
// Refresh token
|
||
await authService.refreshToken(refreshToken);
|
||
```
|
||
|
||
**Fluxo Magic Link:**
|
||
|
||
1. Usuário solicita magic link na tela de login
|
||
2. `localStorage.setItem("magic_link_redirect", "/painel-medico")` salva contexto
|
||
3. Supabase envia email com link único
|
||
4. Usuário clica no link
|
||
5. `Home.tsx` detecta hash params e redireciona para `/auth/callback`
|
||
6. `AuthCallback.tsx` processa tokens, salva no localStorage
|
||
7. `window.location.href` redireciona para painel salvo
|
||
8. Página recarrega com `AuthContext` atualizado
|
||
9. Usuário autenticado no painel correto ✅
|
||
|
||
#### 👤 Usuários (userService)
|
||
|
||
```typescript
|
||
// Buscar informações do usuário autenticado (com roles)
|
||
const userInfo = await userService.getUserInfo();
|
||
// Retorna: { id, email, full_name, roles: ['medico', 'admin'] }
|
||
|
||
// Criar usuário com role
|
||
await userService.createUser({
|
||
email: "user@example.com",
|
||
full_name: "Nome Completo",
|
||
role: "medico", // ou 'admin', 'paciente', 'secretaria'
|
||
});
|
||
|
||
// Deletar usuário
|
||
await userService.deleteUser(userId);
|
||
```
|
||
|
||
#### 🏥 Pacientes (patientService)
|
||
|
||
```typescript
|
||
// Listar pacientes
|
||
const patients = await patientService.list();
|
||
|
||
// Buscar por ID
|
||
const patient = await patientService.getById(id);
|
||
|
||
// Criar paciente
|
||
await patientService.create({
|
||
email: "paciente@example.com",
|
||
full_name: "Nome Paciente",
|
||
cpf: "12345678900",
|
||
phone_mobile: "11999999999",
|
||
});
|
||
|
||
// Atualizar paciente
|
||
await patientService.update(id, { phone_mobile: "11888888888" });
|
||
|
||
// Deletar paciente
|
||
await patientService.delete(id);
|
||
```
|
||
|
||
#### 👨⚕️ Médicos (doctorService)
|
||
|
||
```typescript
|
||
// Listar médicos
|
||
const doctors = await doctorService.list();
|
||
|
||
// Buscar por ID
|
||
const doctor = await doctorService.getById(id);
|
||
|
||
// Buscar disponibilidade
|
||
const slots = await doctorService.getAvailableSlots(doctorId, date);
|
||
```
|
||
|
||
#### 📅 Agendamentos (appointmentService)
|
||
|
||
```typescript
|
||
// Listar agendamentos (filtrado por role automaticamente)
|
||
const appointments = await appointmentService.list();
|
||
|
||
// Criar agendamento
|
||
await appointmentService.create({
|
||
patient_id: "uuid-paciente",
|
||
doctor_id: "uuid-medico",
|
||
scheduled_at: "2025-10-25T10:00:00",
|
||
reason: "Consulta de rotina",
|
||
});
|
||
|
||
// Atualizar status
|
||
await appointmentService.updateStatus(id, "confirmed");
|
||
// Status: requested, confirmed, completed, cancelled
|
||
|
||
// Cancelar
|
||
await appointmentService.cancel(id, "Motivo do cancelamento");
|
||
```
|
||
|
||
#### 📸 Avatares (avatarService)
|
||
|
||
```typescript
|
||
// Upload de avatar (usa FormData com x-upsert: true)
|
||
const file = event.target.files[0]; // File do input
|
||
const result = await avatarService.upload({
|
||
userId: user.id,
|
||
file: file,
|
||
});
|
||
// Retorna: { Key: "url-publica-do-avatar" }
|
||
|
||
// Obter URL pública do avatar
|
||
const url = avatarService.getPublicUrl({
|
||
userId: user.id,
|
||
ext: "png", // ou 'jpg', 'webp'
|
||
});
|
||
// Retorna: https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/avatars/{userId}/avatar.png
|
||
|
||
// Auto-load de avatar (testa múltiplas extensões)
|
||
const extensions = ["png", "jpg", "webp"];
|
||
for (const ext of extensions) {
|
||
const url = avatarService.getPublicUrl({ userId: user.id, ext });
|
||
const response = await fetch(url, { method: "HEAD" });
|
||
if (response.ok) {
|
||
setAvatarUrl(url);
|
||
break;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Detalhes importantes:**
|
||
|
||
- Upload usa **multipart/form-data** via FormData
|
||
- Header `x-upsert: true` permite sobrescrever avatares existentes
|
||
- Suporta formatos: PNG, JPG, WEBP (máx 2MB)
|
||
- URLs públicas não requerem autenticação
|
||
- Avatar é carregado automaticamente nos painéis
|
||
|
||
---
|
||
|
||
## 🔧 Configuração da API
|
||
|
||
```typescript
|
||
// src/services/api/config.ts
|
||
export const API_CONFIG = {
|
||
SUPABASE_URL: "https://yuanqfswhberkoevtmfr.supabase.co",
|
||
SUPABASE_ANON_KEY: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||
AUTH_URL: `${SUPABASE_URL}/auth/v1`,
|
||
REST_URL: `${SUPABASE_URL}/rest/v1`,
|
||
APP_URL: "https://mediconnectbrasil.app",
|
||
TIMEOUT: 30000,
|
||
STORAGE_KEYS: {
|
||
ACCESS_TOKEN: "mediconnect_access_token",
|
||
REFRESH_TOKEN: "mediconnect_refresh_token",
|
||
USER: "mediconnect_user",
|
||
},
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 Deploy no Cloudflare Pages
|
||
|
||
### Via Wrangler CLI
|
||
|
||
```powershell
|
||
# Build
|
||
pnpm build
|
||
|
||
# Deploy para production
|
||
npx wrangler pages deploy dist --project-name=mediconnect --branch=production
|
||
|
||
# Deploy com mudanças não commitadas
|
||
npx wrangler pages deploy dist --project-name=mediconnect --commit-dirty=true
|
||
```
|
||
|
||
### Configuração do Projeto
|
||
|
||
- **Production Branch:** `production`
|
||
- **Build Command:** `pnpm build`
|
||
- **Build Output:** `dist`
|
||
- **Custom Domain:** `mediconnectbrasil.app`
|
||
|
||
### URLs
|
||
|
||
- **Production:** https://mediconnectbrasil.app/
|
||
- **Preview:** https://mediconnect-5oz.pages.dev/
|
||
- **Branch Preview:** https://[branch].mediconnect-5oz.pages.dev/
|
||
|
||
---
|
||
|
||
## 1. Variáveis de Ambiente (`.env` / `.env.local`)
|
||
|
||
| Variável | Obrigatória | Descrição |
|
||
| ------------------------ | ----------- | --------------------------------------------------------------- |
|
||
| `VITE_SUPABASE_URL` | Sim | URL base do projeto Supabase (`https://<ref>.supabase.co`) |
|
||
| `VITE_SUPABASE_ANON_KEY` | Sim | Chave pública (anon) usada para Auth password grant e PostgREST |
|
||
| `VITE_APP_ENV` | Não | Identifica ambiente (ex: `dev`, `staging`, `prod`) |
|
||
|
||
**Nota:** As variáveis já estão configuradas no código em `src/services/api/config.ts`. Não é necessário criar arquivo `.env` para desenvolvimento local.
|
||
|
||
Boas práticas:
|
||
|
||
- Nunca exponha Service Role Key no frontend.
|
||
- Não comitar `.env` – usar `.env.example` como referência (se houver).
|
||
|
||
---
|
||
|
||
## 2. Fluxo de Login e Validação de Roles
|
||
|
||
### Exemplo: Login de Médico
|
||
|
||
```typescript
|
||
// pages/LoginMedico.tsx
|
||
const handleLogin = async (e: FormEvent) => {
|
||
e.preventDefault();
|
||
|
||
try {
|
||
// 1. Autenticar com Supabase
|
||
const loginResponse = await authService.login({ email, password });
|
||
|
||
// 2. Salvar tokens
|
||
localStorage.setItem("access_token", loginResponse.access_token);
|
||
localStorage.setItem("refresh_token", loginResponse.refresh_token);
|
||
|
||
// 3. Buscar informações do usuário com roles
|
||
const userInfo = await userService.getUserInfo();
|
||
const roles = userInfo.roles || [];
|
||
|
||
// 4. Validar permissões
|
||
const isAdmin = roles.includes("admin");
|
||
const isGestor = roles.includes("gestor");
|
||
const isMedico = roles.includes("medico");
|
||
|
||
if (!isAdmin && !isGestor && !isMedico) {
|
||
toast.error("Você não tem permissão para acessar esta área");
|
||
await authService.logout();
|
||
return;
|
||
}
|
||
|
||
// 5. Redirecionar para o painel
|
||
navigate("/painel-medico");
|
||
} catch (error) {
|
||
toast.error("Email ou senha incorretos");
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Recuperação de Senha
|
||
|
||
### Fluxo Completo
|
||
|
||
1. **Usuário clica em "Esqueceu a senha?"**
|
||
2. **Sistema envia email com link de recuperação**
|
||
3. **Usuário clica no link → redireciona para `/reset-password`**
|
||
4. **Sistema extrai token do URL (#access_token=...)**
|
||
5. **Usuário define nova senha**
|
||
6. **Sistema atualiza senha e redireciona para login**
|
||
|
||
### Implementação
|
||
|
||
```typescript
|
||
// Solicitar recuperação (páginas de login)
|
||
const handlePasswordReset = async () => {
|
||
try {
|
||
await authService.requestPasswordReset(email);
|
||
toast.success("Link de recuperação enviado para seu email");
|
||
} catch (error) {
|
||
toast.error("Erro ao enviar email de recuperação");
|
||
}
|
||
};
|
||
|
||
// Página de reset (ResetPassword.tsx)
|
||
useEffect(() => {
|
||
// Extrair token do URL
|
||
const hash = window.location.hash;
|
||
const params = new URLSearchParams(hash.substring(1));
|
||
const token = params.get("access_token");
|
||
|
||
if (token) {
|
||
setAccessToken(token);
|
||
setIsLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
const handleSubmit = async (e: FormEvent) => {
|
||
try {
|
||
await authService.updatePassword(accessToken, newPassword);
|
||
toast.success("Senha atualizada com sucesso!");
|
||
navigate("/login-paciente");
|
||
} catch (error) {
|
||
toast.error("Erro ao atualizar senha");
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Estrutura do Banco de Dados
|
||
|
||
### Tabelas Principais
|
||
|
||
#### `profiles`
|
||
|
||
```sql
|
||
- id (uuid, PK)
|
||
- email (text, unique)
|
||
- full_name (text)
|
||
- phone (text)
|
||
- created_at (timestamp)
|
||
- updated_at (timestamp)
|
||
```
|
||
|
||
#### `patients`
|
||
|
||
```sql
|
||
- id (uuid, PK, FK -> profiles)
|
||
- email (text, unique)
|
||
- full_name (text)
|
||
- cpf (text, unique)
|
||
- phone_mobile (text)
|
||
- birth_date (date)
|
||
- address (text)
|
||
- created_at (timestamp)
|
||
```
|
||
|
||
#### `doctors`
|
||
|
||
```sql
|
||
- id (uuid, PK, FK -> profiles)
|
||
- email (text, unique)
|
||
- full_name (text)
|
||
- specialty (text)
|
||
- crm (text, unique)
|
||
- phone (text)
|
||
- created_at (timestamp)
|
||
```
|
||
|
||
#### `appointments`
|
||
|
||
```sql
|
||
- id (uuid, PK)
|
||
- patient_id (uuid, FK -> patients)
|
||
- doctor_id (uuid, FK -> doctors)
|
||
- scheduled_at (timestamp)
|
||
- status (enum: requested, confirmed, completed, cancelled)
|
||
- reason (text)
|
||
- notes (text)
|
||
- created_at (timestamp)
|
||
```
|
||
|
||
#### `user_roles`
|
||
|
||
```sql
|
||
- user_id (uuid, FK -> profiles)
|
||
- role (text: admin, gestor, medico, secretaria, paciente)
|
||
- created_at (timestamp)
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Armazenamento de Tokens
|
||
|
||
O sistema usa uma estratégia híbrida para máxima segurança:
|
||
|
||
| Tipo | Local | Expiração Natural |
|
||
| ------------- | ------------ | --------------------------------- |
|
||
| Access Token | localStorage | 1 hora (renovado automaticamente) |
|
||
| Refresh Token | localStorage | 30 dias (ou revogação backend) |
|
||
| User Snapshot | localStorage | Limpo em logout |
|
||
|
||
**Segurança:**
|
||
|
||
- Tokens são limpos automaticamente no logout
|
||
- Refresh automático quando access_token expira (401)
|
||
- Interceptors garantem tokens válidos em todas as requisições
|
||
|
||
Riscos remanescentes:
|
||
|
||
- XSS ainda pode ler refresh token dentro da mesma aba.
|
||
- Ataques supply-chain podem capturar tokens em runtime.
|
||
|
||
Mitigações planejadas:
|
||
|
||
1. CSP + bloqueio de inline script não autorizado.
|
||
2. Auditoria de dependências e lockfile imutável.
|
||
3. (Opcional) Migrar refresh para cookie httpOnly + rotacionamento curto (exige backend/proxy).
|
||
|
||
Fallback / Migração:
|
||
|
||
- Em primeira utilização o `tokenStore` migra chaves legacy (`authToken`, `refreshToken`, `authUser`) e remove do `localStorage`.
|
||
|
||
Operações:
|
||
|
||
- `tokenStore.setTokens(access, refresh?)` atualiza memória e session.
|
||
- `tokenStore.clear()` remove tudo (usado em logout e erro crítico de refresh).
|
||
|
||
Fluxo de Refresh:
|
||
|
||
1. Requisição falha com 401.
|
||
2. Wrapper (`http.ts`) obtém refresh do `tokenStore`.
|
||
|
||
---
|
||
|
||
## 7. Scripts Utilitários
|
||
|
||
### Gerenciamento de Usuários
|
||
|
||
```bash
|
||
# Listar todos os usuários
|
||
node scripts/manage-users.js list
|
||
|
||
# Criar usuário
|
||
node scripts/manage-users.js create email@example.com "Nome Completo"
|
||
|
||
# Deletar usuário
|
||
node scripts/manage-users.js delete user-id
|
||
|
||
# Limpar usuários de teste
|
||
node scripts/cleanup-users.js
|
||
```
|
||
|
||
### Testes de API
|
||
|
||
```bash
|
||
# Testar recuperação de senha
|
||
node test-password-recovery.js
|
||
|
||
# Criar usuário Fernando (exemplo)
|
||
node create-fernando.cjs
|
||
|
||
# Buscar usuário Fernando
|
||
node search-fernando.cjs
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Padrões de Código
|
||
|
||
### Nomenclatura
|
||
|
||
- **Componentes:** PascalCase (`LoginPaciente.tsx`)
|
||
- **Serviços:** camelCase (`authService.ts`)
|
||
- **Hooks:** camelCase com prefixo `use` (`useAuth.ts`)
|
||
- **Tipos:** PascalCase (`LoginInput`, `AuthUser`)
|
||
|
||
### Estrutura de Componentes
|
||
|
||
```typescript
|
||
// Imports
|
||
import React, { useState, useEffect } from "react";
|
||
import { useNavigate } from "react-router-dom";
|
||
import { serviceImport } from "../services";
|
||
|
||
// Types
|
||
interface Props {
|
||
// ...
|
||
}
|
||
|
||
// Component
|
||
const ComponentName: React.FC<Props> = ({ ...props }) => {
|
||
// Hooks
|
||
const navigate = useNavigate();
|
||
const [state, setState] = useState();
|
||
|
||
// Effects
|
||
useEffect(() => {
|
||
// ...
|
||
}, []);
|
||
|
||
// Handlers
|
||
const handleAction = async () => {
|
||
// ...
|
||
};
|
||
|
||
// Render
|
||
return <div>{/* JSX */}</div>;
|
||
};
|
||
|
||
export default ComponentName;
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Tecnologias Utilizadas
|
||
|
||
### Frontend
|
||
|
||
- **React** 18.3.1 - Biblioteca UI
|
||
- **TypeScript** 5.9.3 - Tipagem estática
|
||
- **Vite** 7.1.10 - Build tool
|
||
- **React Router** 6.30.1 - Roteamento
|
||
- **Tailwind CSS** 3.4.17 - Estilização
|
||
- **Axios** 1.12.2 - Cliente HTTP
|
||
- **React Hot Toast** 2.4.1 - Notificações
|
||
- **date-fns** 4.1.0 - Manipulação de datas
|
||
|
||
### Backend
|
||
|
||
- **Supabase** - Backend as a Service
|
||
- **PostgreSQL** - Banco de dados relacional
|
||
- **Supabase Auth** - Autenticação JWT
|
||
|
||
### Deploy
|
||
|
||
- **Cloudflare Pages** - Hospedagem frontend
|
||
- **Wrangler** 4.44.0 - CLI Cloudflare
|
||
|
||
---
|
||
|
||
## 10. Suporte e Contato
|
||
|
||
- **Equipe:** Squad 18 - Rise Up
|
||
- **Repositório:** https://git.popcode.com.br/RiseUP/riseup-squad18.git
|
||
- **Trello:** [Squad 18 - Idealização/Planejamento](https://trello.com/b/CCl3Azxk/squad-18-idealizacao-planejamento)
|
||
|
||
---
|
||
|
||
**Desenvolvido com ❤️ pela Squad 18**
|