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)
<EFBFBD> Instalação e Execução
Pré-requisitos
- Node.js 18+
- pnpm (recomendado) ou npm
Instalação
# Instalar dependências
pnpm install
# Iniciar desenvolvimento
pnpm dev
# Acessar em http://localhost:5173
Build e Deploy
# 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
// 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
// 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
// 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
🔒 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(usarunknownquando 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.
<EFBFBD> Links Úteis
Ú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)
// 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:
- Usuário solicita magic link na tela de login
localStorage.setItem("magic_link_redirect", "/painel-medico")salva contexto- Supabase envia email com link único
- Usuário clica no link
Home.tsxdetecta hash params e redireciona para/auth/callbackAuthCallback.tsxprocessa tokens, salva no localStoragewindow.location.hrefredireciona para painel salvo- Página recarrega com
AuthContextatualizado - Usuário autenticado no painel correto ✅
👤 Usuários (userService)
// 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)
// 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)
// 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)
// 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)
// 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: truepermite 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
// 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
# 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.examplecomo referência (se houver).
2. Fluxo de Login e Validação de Roles
Exemplo: Login de Médico
// 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
- Usuário clica em "Esqueceu a senha?"
- Sistema envia email com link de recuperação
- Usuário clica no link → redireciona para
/reset-password - Sistema extrai token do URL (#access_token=...)
- Usuário define nova senha
- Sistema atualiza senha e redireciona para login
Implementação
// 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
- id (uuid, PK)
- email (text, unique)
- full_name (text)
- phone (text)
- created_at (timestamp)
- updated_at (timestamp)
patients
- 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
- id (uuid, PK, FK -> profiles)
- email (text, unique)
- full_name (text)
- specialty (text)
- crm (text, unique)
- phone (text)
- created_at (timestamp)
appointments
- 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
- 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:
- CSP + bloqueio de inline script não autorizado.
- Auditoria de dependências e lockfile imutável.
- (Opcional) Migrar refresh para cookie httpOnly + rotacionamento curto (exige backend/proxy).
Fallback / Migração:
- Em primeira utilização o
tokenStoremigra chaves legacy (authToken,refreshToken,authUser) e remove dolocalStorage.
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:
- Requisição falha com 401.
- Wrapper (
http.ts) obtém refresh dotokenStore.
7. Scripts Utilitários
Gerenciamento de Usuários
# 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
# 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
// 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
Desenvolvido com ❤️ pela Squad 18