717 lines
17 KiB
Markdown
717 lines
17 KiB
Markdown
# MediConnect - Sistema de Agendamento Médico
|
||
|
||
Sistema completo de gestão de consultas médicas desenvolvido pela **Squad 18** do programa Rise Up.
|
||
|
||
## 🚀 Acesso ao Sistema
|
||
|
||
- **URL Principal:** https://mediconnectbrasil.app/
|
||
- **URL Cloudflare:** https://mediconnect-5oz.pages.dev/
|
||
|
||
## 📋 Índice
|
||
|
||
1. [Visão Geral](#visão-geral)
|
||
2. [Arquitetura](#arquitetura)
|
||
3. [Tecnologias](#tecnologias)
|
||
4. [Estrutura do Projeto](#estrutura-do-projeto)
|
||
5. [API e Serviços](#api-e-serviços)
|
||
6. [Autenticação](#autenticação)
|
||
7. [Fluxos Principais](#fluxos-principais)
|
||
8. [Deploy](#deploy)
|
||
9. [Desenvolvimento Local](#desenvolvimento-local)
|
||
|
||
---
|
||
|
||
## 🎯 Visão Geral
|
||
|
||
O MediConnect é uma plataforma web que conecta **pacientes**, **médicos** e **secretárias** de forma eficiente e segura, permitindo:
|
||
|
||
- ✅ Agendamento de consultas online
|
||
- ✅ Gestão de disponibilidade dos médicos
|
||
- ✅ Acompanhamento de consultas e prontuários
|
||
- ✅ Sistema de avatares com upload de imagens
|
||
- ✅ Recuperação de senha
|
||
- ✅ Sistema de roles e permissões
|
||
- ✅ Interface responsiva e acessível
|
||
|
||
---
|
||
|
||
## 🏗️ Arquitetura
|
||
|
||
### Diagrama de Arquitetura
|
||
|
||
```
|
||
┌─────────────────┐
|
||
│ Frontend │
|
||
│ React + TS │
|
||
│ (Cloudflare) │
|
||
└────────┬────────┘
|
||
│
|
||
│ HTTPS
|
||
▼
|
||
┌─────────────────┐
|
||
│ Supabase API │
|
||
│ (Backend) │
|
||
├─────────────────┤
|
||
│ • Auth │
|
||
│ • PostgreSQL │
|
||
│ • Functions │
|
||
│ • Storage │
|
||
└─────────────────┘
|
||
```
|
||
|
||
### Camadas da Aplicação
|
||
|
||
1. **Apresentação (UI)**
|
||
|
||
- React 18.3.1 com TypeScript
|
||
- React Router para navegação
|
||
- Tailwind CSS para estilização
|
||
- Lucide React para ícones
|
||
|
||
2. **Lógica de Negócio (Services)**
|
||
|
||
- Services organizados por domínio
|
||
- Axios para requisições HTTP
|
||
- Interceptors para autenticação automática
|
||
|
||
3. **Persistência (Supabase)**
|
||
- PostgreSQL com Row Level Security (RLS)
|
||
- Supabase Auth para autenticação JWT
|
||
- Supabase Storage para upload de avatares
|
||
- Supabase Functions para lógica serverless
|
||
|
||
---
|
||
|
||
## 🛠️ Tecnologias
|
||
|
||
### 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
|
||
- **Supabase Storage** - Armazenamento de avatares
|
||
|
||
### Deploy
|
||
|
||
- **Cloudflare Pages** - Hospedagem frontend
|
||
- **Wrangler** 4.44.0 - CLI Cloudflare
|
||
|
||
---
|
||
|
||
## 📁 Estrutura do Projeto
|
||
|
||
```
|
||
MEDICONNECT 2/
|
||
├── src/
|
||
│ ├── pages/ # Páginas da aplicação
|
||
│ │ ├── Home.tsx
|
||
│ │ ├── LoginPaciente.tsx
|
||
│ │ ├── LoginMedico.tsx
|
||
│ │ ├── LoginSecretaria.tsx
|
||
│ │ ├── ResetPassword.tsx
|
||
│ │ ├── PainelMedico.tsx
|
||
│ │ ├── PainelSecretaria.tsx
|
||
│ │ ├── AcompanhamentoPaciente.tsx
|
||
│ │ └── ...
|
||
│ │
|
||
│ ├── components/ # Componentes reutilizáveis
|
||
│ │ ├── Header.tsx
|
||
│ │ ├── HeroBanner.tsx
|
||
│ │ ├── Chatbot.tsx
|
||
│ │ ├── MetricCard.tsx
|
||
│ │ ├── auth/
|
||
│ │ ├── agenda/
|
||
│ │ ├── consultas/
|
||
│ │ └── secretaria/
|
||
│ │
|
||
│ ├── services/ # Serviços de API
|
||
│ │ ├── api/
|
||
│ │ │ ├── client.ts # Cliente HTTP
|
||
│ │ │ └── config.ts # Configurações
|
||
│ │ ├── auth/
|
||
│ │ │ ├── authService.ts
|
||
│ │ │ └── types.ts
|
||
│ │ ├── patients/
|
||
│ │ ├── doctors/
|
||
│ │ ├── appointments/
|
||
│ │ ├── availability/
|
||
│ │ └── users/
|
||
│ │
|
||
│ ├── hooks/ # React Hooks customizados
|
||
│ │ ├── useAuth.ts
|
||
│ │ └── useAccessibilityPrefs.ts
|
||
│ │
|
||
│ ├── context/ # Context API
|
||
│ │ └── AuthContext.tsx
|
||
│ │
|
||
│ ├── i18n/ # Internacionalização
|
||
│ │ ├── pt-BR.ts
|
||
│ │ └── en-US.ts
|
||
│ │
|
||
│ ├── types/ # Definições de tipos
|
||
│ │ └── api.d.ts
|
||
│ │
|
||
│ └── utils/ # Utilitários
|
||
│ └── validators.ts
|
||
│
|
||
├── public/ # Arquivos públicos
|
||
├── dist/ # Build de produção
|
||
├── scripts/ # Scripts utilitários
|
||
│ ├── manage-users.js
|
||
│ └── cleanup-users.js
|
||
│
|
||
├── vite.config.ts # Configuração Vite
|
||
├── tailwind.config.js # Configuração Tailwind
|
||
└── tsconfig.json # Configuração TypeScript
|
||
```
|
||
|
||
---
|
||
|
||
## 🔌 API e Serviços
|
||
|
||
### 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`,
|
||
FUNCTIONS_URL: `${SUPABASE_URL}/functions/v1`,
|
||
APP_URL: "https://mediconnectbrasil.app",
|
||
TIMEOUT: 30000,
|
||
STORAGE_KEYS: {
|
||
ACCESS_TOKEN: "mediconnect_access_token",
|
||
REFRESH_TOKEN: "mediconnect_refresh_token",
|
||
USER: "mediconnect_user",
|
||
},
|
||
};
|
||
```
|
||
|
||
### Serviços Disponíveis
|
||
|
||
#### 1. **authService** - Autenticação
|
||
|
||
```typescript
|
||
// Login
|
||
await authService.login({ email, password });
|
||
|
||
// Registro
|
||
await authService.signup({ email, password, full_name });
|
||
|
||
// Logout
|
||
await authService.logout();
|
||
|
||
// Recuperação de senha
|
||
await authService.requestPasswordReset(email);
|
||
await authService.updatePassword(accessToken, newPassword);
|
||
|
||
// Refresh token
|
||
await authService.refreshToken(refreshToken);
|
||
```
|
||
|
||
#### 2. **userService** - Usuários
|
||
|
||
```typescript
|
||
// Buscar informações do usuário autenticado
|
||
const userInfo = await userService.getUserInfo();
|
||
|
||
// Criar usuário com role
|
||
await userService.createUser({ email, full_name, role });
|
||
|
||
// Deletar usuário
|
||
await userService.deleteUser(userId);
|
||
```
|
||
|
||
#### 3. **patientService** - Pacientes
|
||
|
||
```typescript
|
||
// Listar pacientes
|
||
const patients = await patientService.list();
|
||
|
||
// Buscar por ID
|
||
const patient = await patientService.getById(id);
|
||
|
||
// Criar paciente
|
||
await patientService.create({ email, full_name, cpf, phone });
|
||
|
||
// Atualizar
|
||
await patientService.update(id, data);
|
||
|
||
// Registrar paciente (público)
|
||
await patientService.register({ email, full_name, cpf });
|
||
```
|
||
|
||
#### 4. **doctorService** - Médicos
|
||
|
||
```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);
|
||
```
|
||
|
||
#### 5. **appointmentService** - Consultas
|
||
|
||
```typescript
|
||
// Listar consultas
|
||
const appointments = await appointmentService.list();
|
||
|
||
// Criar consulta
|
||
await appointmentService.create({
|
||
patient_id,
|
||
doctor_id,
|
||
scheduled_at,
|
||
reason,
|
||
});
|
||
|
||
// Atualizar status
|
||
await appointmentService.updateStatus(id, status);
|
||
|
||
// Cancelar
|
||
await appointmentService.cancel(id, reason);
|
||
```
|
||
|
||
#### 6. **availabilityService** - Disponibilidade
|
||
|
||
```typescript
|
||
// Gerenciar disponibilidade do médico
|
||
await availabilityService.create({
|
||
doctor_id,
|
||
day_of_week,
|
||
start_time,
|
||
end_time,
|
||
});
|
||
|
||
// Listar disponibilidade
|
||
const slots = await availabilityService.listByDoctor(doctorId);
|
||
```
|
||
|
||
---
|
||
|
||
## 🔐 Autenticação
|
||
|
||
### Fluxo de Autenticação
|
||
|
||
1. **Login**
|
||
|
||
```typescript
|
||
// 1. Usuário envia credenciais
|
||
const response = await authService.login({ email, password });
|
||
|
||
// 2. Recebe tokens JWT
|
||
localStorage.setItem("access_token", response.access_token);
|
||
localStorage.setItem("refresh_token", response.refresh_token);
|
||
|
||
// 3. Busca informações completas
|
||
const userInfo = await userService.getUserInfo();
|
||
|
||
// 4. Valida roles
|
||
if (userInfo.roles.includes("medico")) {
|
||
navigate("/painel-medico");
|
||
}
|
||
```
|
||
|
||
2. **Interceptor Automático**
|
||
|
||
```typescript
|
||
// Todo request adiciona o token automaticamente
|
||
axios.interceptors.request.use((config) => {
|
||
const token = localStorage.getItem("access_token");
|
||
if (token) {
|
||
config.headers.Authorization = `Bearer ${token}`;
|
||
}
|
||
return config;
|
||
});
|
||
```
|
||
|
||
3. **Refresh Token Automático**
|
||
|
||
```typescript
|
||
axios.interceptors.response.use(
|
||
(response) => response,
|
||
async (error) => {
|
||
if (error.response?.status === 401) {
|
||
// Token expirado, tenta refresh
|
||
const refreshToken = localStorage.getItem("refresh_token");
|
||
const newTokens = await authService.refreshToken(refreshToken);
|
||
// Retry request original
|
||
}
|
||
}
|
||
);
|
||
```
|
||
|
||
### Roles e Permissões
|
||
|
||
| Role | Acesso |
|
||
| -------------- | ------------------------------------------- |
|
||
| **admin** | Acesso total ao sistema |
|
||
| **gestor** | Gestão de médicos, secretárias e relatórios |
|
||
| **medico** | Painel médico, consultas, prontuários |
|
||
| **secretaria** | Agendamento, gestão de pacientes |
|
||
| **paciente** | Agendamento, visualização de consultas |
|
||
|
||
**Hierarquia de Roles:**
|
||
|
||
```
|
||
admin > gestor > medico/secretaria > paciente
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 Fluxos Principais
|
||
|
||
### 1. Fluxo de Agendamento de Consulta
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
Paciente->>LoginPaciente: Faz login
|
||
LoginPaciente->>Supabase: POST /auth/v1/token
|
||
Supabase-->>LoginPaciente: access_token
|
||
LoginPaciente->>UserService: getUserInfo()
|
||
UserService-->>LoginPaciente: dados + roles
|
||
Paciente->>ListaMedicos: Escolhe médico
|
||
ListaMedicos->>DoctorService: getAvailableSlots()
|
||
DoctorService-->>ListaMedicos: horários disponíveis
|
||
Paciente->>AppointmentService: create()
|
||
AppointmentService->>Supabase: POST /rest/v1/appointments
|
||
Supabase-->>Paciente: Consulta criada ✅
|
||
```
|
||
|
||
### 2. Fluxo de Recuperação de Senha
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
Usuário->>LoginPage: Clica "Esqueceu a senha?"
|
||
LoginPage->>AuthService: requestPasswordReset(email)
|
||
AuthService->>Supabase: POST /auth/v1/recover
|
||
Supabase->>Email: Envia link de recuperação
|
||
Usuário->>Email: Clica no link
|
||
Email->>ResetPassword: Redireciona com token
|
||
ResetPassword->>AuthService: updatePassword(token, novaSenha)
|
||
AuthService->>Supabase: PUT /auth/v1/user
|
||
Supabase-->>ResetPassword: Senha atualizada ✅
|
||
```
|
||
|
||
### 3. Fluxo de Validação de Roles
|
||
|
||
```typescript
|
||
// LoginMedico.tsx
|
||
const handleLogin = async () => {
|
||
// 1. Login
|
||
const loginResponse = await authService.login({ email, password });
|
||
|
||
// 2. Buscar roles
|
||
const userInfo = await userService.getUserInfo();
|
||
const roles = userInfo.roles || [];
|
||
|
||
// 3. Validar permissão
|
||
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;
|
||
}
|
||
|
||
// 4. Redirecionar
|
||
navigate("/painel-medico");
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 Deploy
|
||
|
||
### Cloudflare Pages
|
||
|
||
O projeto é hospedado no **Cloudflare Pages** com deploy automático via Wrangler CLI.
|
||
|
||
#### Comandos de Deploy
|
||
|
||
```bash
|
||
# Build de produção
|
||
pnpm build
|
||
|
||
# Deploy para Cloudflare
|
||
npx wrangler pages deploy dist --project-name=mediconnect
|
||
|
||
# Deploy com branch específica
|
||
npx wrangler pages deploy dist --project-name=mediconnect --branch=production
|
||
|
||
# Deploy ignorando mudanças não commitadas
|
||
npx wrangler pages deploy dist --project-name=mediconnect --commit-dirty=true
|
||
```
|
||
|
||
#### Configuração do Cloudflare
|
||
|
||
1. **Production Branch:** `production`
|
||
2. **Build Command:** `pnpm build`
|
||
3. **Build Output:** `dist`
|
||
4. **Custom Domain:** `mediconnectbrasil.app`
|
||
|
||
#### URLs de Deploy
|
||
|
||
- **Production:** https://mediconnectbrasil.app/
|
||
- **Preview:** https://mediconnect-5oz.pages.dev/
|
||
- **Branch Preview:** https://[branch].mediconnect-5oz.pages.dev/
|
||
|
||
---
|
||
|
||
## 💻 Desenvolvimento Local
|
||
|
||
### Pré-requisitos
|
||
|
||
- Node.js 18+
|
||
- pnpm 8+
|
||
- Git
|
||
|
||
### Instalação
|
||
|
||
```bash
|
||
# 1. Clone o repositório
|
||
git clone https://git.popcode.com.br/RiseUP/riseup-squad18.git
|
||
cd riseup-squad18/"MEDICONNECT 2"
|
||
|
||
# 2. Instale as dependências
|
||
pnpm install
|
||
|
||
# 3. Configure as variáveis de ambiente (se necessário)
|
||
# Crie um arquivo .env.local com as configurações do Supabase
|
||
|
||
# 4. Inicie o servidor de desenvolvimento
|
||
pnpm dev
|
||
|
||
# 5. Acesse http://localhost:5173
|
||
```
|
||
|
||
### Scripts Disponíveis
|
||
|
||
```bash
|
||
# Desenvolvimento
|
||
pnpm dev # Inicia servidor dev
|
||
|
||
# Build
|
||
pnpm build # Build de produção
|
||
pnpm preview # Preview do build
|
||
|
||
# Testes
|
||
pnpm test # Executa testes
|
||
pnpm test:ui # UI de testes
|
||
|
||
# Lint
|
||
pnpm lint # Verifica código
|
||
|
||
# Type check
|
||
pnpm type-check # Verifica tipos TypeScript
|
||
```
|
||
|
||
### Variáveis de Ambiente
|
||
|
||
```env
|
||
# .env.local (opcional - já configurado no código)
|
||
VITE_SUPABASE_URL=https://yuanqfswhberkoevtmfr.supabase.co
|
||
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||
VITE_APP_URL=http://localhost:5173
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 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)
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 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
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 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;
|
||
```
|
||
|
||
---
|
||
|
||
## <20> 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)
|
||
|
||
---
|
||
|
||
## 📝 Changelog
|
||
|
||
### v2.0.0 (Outubro 2024)
|
||
|
||
- ✅ Migração completa de Netlify Functions para Supabase
|
||
- ✅ Implementação de recuperação de senha
|
||
- ✅ Deploy no Cloudflare Pages
|
||
- ✅ Novo HeroBanner com imagens rotativas
|
||
- ✅ Chatbot integrado
|
||
- ✅ Sistema de roles e permissões
|
||
- ✅ Interface responsiva e dark mode
|
||
|
||
### v1.0.0 (Setembro 2024)
|
||
|
||
- ✅ Lançamento inicial
|
||
- ✅ Login de pacientes, médicos e secretárias
|
||
- ✅ Agendamento de consultas
|
||
- ✅ Gestão de disponibilidade
|
||
|
||
---
|
||
|
||
## 📄 Licença
|
||
|
||
Este projeto é parte do programa Rise Up e está sob licença proprietária da PopCode.
|
||
|
||
---
|
||
|
||
**Desenvolvido com ❤️ pela Squad 18**
|