From f22bca225cf60d29f25538527a60a734404651db Mon Sep 17 00:00:00 2001 From: guisilvagomes Date: Wed, 29 Oct 2025 10:33:53 -0300 Subject: [PATCH] fix: corrigir redirecionamento do magic link para painel correto baseado no contexto --- MEDICONNECT 2/API-CONFIG.md | 348 ++++++++++++++++++ MEDICONNECT 2/CHECKLIST-TESTES.md | 292 +++++++++++++++ MEDICONNECT 2/create-aurora-appointment.cjs | 128 +++++++ .../create-fernando-availability.cjs | 93 +++++ .../create-patient-with-password.cjs | 80 ++++ MEDICONNECT 2/fix-aurora-user-id.cjs | 72 ++++ MEDICONNECT 2/get-aurora-info.cjs | 59 +++ MEDICONNECT 2/get-fernando-complete.cjs | 80 ++++ MEDICONNECT 2/get-fernando-doctor-id.cjs | 82 +++++ MEDICONNECT 2/get-fernando-user-id.cjs | 38 ++ .../src/components/auth/RecoveryRedirect.tsx | 22 +- .../src/pages/AcompanhamentoPaciente.tsx | 6 +- MEDICONNECT 2/src/pages/AuthCallback.tsx | 30 +- MEDICONNECT 2/src/pages/Home.tsx | 2 +- MEDICONNECT 2/src/pages/LoginMedico.tsx | 5 +- MEDICONNECT 2/src/pages/LoginPaciente.tsx | 5 +- MEDICONNECT 2/src/pages/LoginSecretaria.tsx | 5 +- MEDICONNECT 2/src/pages/PainelMedico.tsx | 6 +- MEDICONNECT 2/src/pages/PerfilPaciente.tsx | 17 +- MEDICONNECT 2/src/pages/ResetPassword.tsx | 38 +- .../src/services/auth/authService.ts | 142 +++++-- .../src/services/avatars/avatarService.ts | 12 +- 22 files changed, 1464 insertions(+), 98 deletions(-) create mode 100644 MEDICONNECT 2/API-CONFIG.md create mode 100644 MEDICONNECT 2/CHECKLIST-TESTES.md create mode 100644 MEDICONNECT 2/create-aurora-appointment.cjs create mode 100644 MEDICONNECT 2/create-fernando-availability.cjs create mode 100644 MEDICONNECT 2/create-patient-with-password.cjs create mode 100644 MEDICONNECT 2/fix-aurora-user-id.cjs create mode 100644 MEDICONNECT 2/get-aurora-info.cjs create mode 100644 MEDICONNECT 2/get-fernando-complete.cjs create mode 100644 MEDICONNECT 2/get-fernando-doctor-id.cjs create mode 100644 MEDICONNECT 2/get-fernando-user-id.cjs diff --git a/MEDICONNECT 2/API-CONFIG.md b/MEDICONNECT 2/API-CONFIG.md new file mode 100644 index 000000000..96f465c27 --- /dev/null +++ b/MEDICONNECT 2/API-CONFIG.md @@ -0,0 +1,348 @@ +# Configuração das APIs - MediConnect + +## ✅ APIs Testadas e Funcionando + +### 1. Autenticação (Auth API) + +**Base URL:** `https://yuanqfswhberkoevtmfr.supabase.co/auth/v1` + +#### Endpoints Funcionais: + +- **Login** ✅ + + - `POST /token?grant_type=password` + - Body: `{ email, password }` + - Retorna: `{ access_token, refresh_token, user }` + +- **Recuperação de Senha** ✅ + + - `POST /recover` + - Body: `{ email, options: { redirectTo: url } }` + - Envia email com link de recuperação + +- **Atualizar Senha** ✅ + - `PUT /user` + - Headers: `Authorization: Bearer ` + - Body: `{ password: "nova_senha" }` + - **IMPORTANTE:** Nova senha deve ser diferente da anterior (erro 422 se for igual) + +### 2. REST API + +**Base URL:** `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1` + +#### Tabelas e Campos Corretos: + +##### **appointments** ✅ + +```typescript +{ + id: string (UUID) + order_number: string (auto-gerado: APT-YYYY-NNNN) + patient_id: string (UUID) + doctor_id: string (UUID) + scheduled_at: string (ISO 8601 DateTime) + duration_minutes: number + appointment_type: "presencial" | "telemedicina" + status: "requested" | "confirmed" | "checked_in" | "in_progress" | "completed" | "cancelled" | "no_show" + chief_complaint: string | null + patient_notes: string | null + notes: string | null + insurance_provider: string | null + checked_in_at: string | null + completed_at: string | null + cancelled_at: string | null + cancellation_reason: string | null + created_at: string + updated_at: string + created_by: string (UUID) + updated_by: string | null +} +``` + +**Criar Consulta:** + +```bash +POST /rest/v1/appointments +Headers: + - apikey: + - Authorization: Bearer + - Content-Type: application/json + - Prefer: return=representation + +Body: +{ + "patient_id": "uuid", + "doctor_id": "uuid", + "scheduled_at": "2025-11-03T10:00:00.000Z", + "duration_minutes": 30, + "appointment_type": "presencial", + "chief_complaint": "Motivo da consulta" +} +``` + +##### **doctor_availability** ✅ + +```typescript +{ + id: string (UUID) + doctor_id: string (UUID) + weekday: "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" + start_time: string (HH:MM:SS, ex: "07:00:00") + end_time: string (HH:MM:SS, ex: "19:00:00") + slot_duration_minutes: number (ex: 30) + appointment_type: "presencial" | "telemedicina" + is_active: boolean + created_at: string + updated_at: string + created_by: string (UUID) + updated_by: string | null +} +``` + +**Criar Disponibilidade:** + +```bash +POST /rest/v1/doctor_availability +Headers: + - apikey: + - Authorization: Bearer + - Content-Type: application/json + - Prefer: return=representation + +Body: +{ + "doctor_id": "uuid", + "weekday": "monday", // ⚠️ Texto, não número! + "start_time": "07:00:00", + "end_time": "19:00:00", + "slot_duration_minutes": 30, + "appointment_type": "presencial", + "is_active": true, + "created_by": "admin_user_id" +} +``` + +##### **patients** ✅ + +```typescript +{ + id: string(UUID); + user_id: string(UUID); // ⚠️ Deve estar vinculado ao auth.users + full_name: string; + email: string; + cpf: string; + phone_mobile: string; + // ... outros campos +} +``` + +**Atualizar Patient:** + +```bash +PATCH /rest/v1/patients?id=eq. +Headers: + - apikey: + - Authorization: Bearer + - Content-Type: application/json + +Body: +{ + "user_id": "auth_user_id" +} +``` + +##### **doctors** ✅ + +```typescript +{ + id: string(UUID); + user_id: string(UUID); + full_name: string; + email: string; + crm: string; + crm_uf: string; + specialty: string; + // ... outros campos +} +``` + +### 3. Edge Functions + +**Base URL:** `https://yuanqfswhberkoevtmfr.supabase.co/functions/v1` + +#### Funcionais: + +- **create-user-with-password** ✅ + - `POST /functions/v1/create-user-with-password` + - Cria usuário com senha e perfil completo + - Body: + ```json + { + "email": "email@example.com", + "password": "senha123", + "full_name": "Nome Completo", + "phone_mobile": "(11) 99999-9999", + "cpf": "12345678900", + "create_patient_record": true, + "role": "paciente" + } + ``` + +#### Com Problemas: + +- **request-password-reset** ❌ + - CORS blocking - não usar + - Usar diretamente `/auth/v1/recover` em vez disso + +## 🔑 Chaves de API + +```typescript +SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; +``` + +## 👥 Usuários de Teste + +### Admin + +- Email: `riseup@popcode.com.br` +- Senha: `riseup` + +### Dr. Fernando Pirichowski + +- Email: `fernando.pirichowski@souunit.com.br` +- Senha: `fernando123` +- User ID: `38aca60d-7418-4c35-95b6-cb206bb18a0a` +- Doctor ID: `6dad001d-229b-40b5-80f3-310243c4599c` +- CRM: `24245` +- Disponibilidade: Segunda a Domingo, 07:00-19:00 + +### Aurora Sabrina Clara Nascimento (Paciente) + +- Email: `aurora-nascimento94@gmx.com` +- Senha: `auroranasc94` +- User ID: `6dc15cc5-7dae-4b30-924a-a4b4fa142f24` +- Patient ID: `b85486f7-9135-4b67-9aa7-b884d9603d12` +- CPF: `66864784231` +- Telefone: `(21) 99856-3014` + +## ⚠️ Pontos de Atenção + +### 1. Weekday no doctor_availability + +- ❌ **NÃO** usar números (0-6) +- ✅ **USAR** strings em inglês: `"sunday"`, `"monday"`, `"tuesday"`, `"wednesday"`, `"thursday"`, `"friday"`, `"saturday"` + +### 2. scheduled_at em appointments + +- ❌ **NÃO** usar campos separados `appointment_date` e `appointment_time` +- ✅ **USAR** campo único `scheduled_at` com ISO 8601 DateTime +- Exemplo: `"2025-11-03T10:00:00.000Z"` + +### 3. user_id nas tabelas patients e doctors + +- ⚠️ Sempre vincular ao `auth.users.id` +- Sem esse vínculo, queries por `user_id` não funcionam + +### 4. Senha na recuperação + +- ⚠️ Nova senha DEVE ser diferente da anterior +- Erro 422 com `error_code: "same_password"` se tentar usar a mesma + +### 5. redirectTo no password recovery + +- ⚠️ Supabase pode ignorar o parâmetro `redirectTo` +- ✅ Implementar detecção de token no lado do cliente +- Verificar tanto query string `?token=` quanto hash `#access_token=` + +## 📦 Estrutura de Serviços no Frontend + +```typescript +// Tudo configurado em: +src / services / api / config.ts; // URLs e chaves +src / services / api / client.ts; // Cliente axios +src / + services / + appointments / // Serviço de consultas + src / + services / + availability / // Disponibilidade médicos + src / + services / + auth / // Autenticação + src / + services / + doctors / // Médicos + src / + services / + patients / // Pacientes + src / + services / + index.ts; // Exportações centralizadas +``` + +## ✅ Status Atual + +- [x] Autenticação funcionando +- [x] Recuperação de senha funcionando +- [x] Criação de usuários funcionando +- [x] Criação de pacientes funcionando +- [x] Criação de disponibilidade médica funcionando +- [x] Criação de consultas funcionando +- [x] Vinculação user_id ↔ patient_id corrigida +- [x] Todos os serviços usando campos corretos + +## 🚀 Próximos Passos + +1. Testar agendamento completo no frontend +2. Verificar listagem de consultas +3. Testar cancelamento e atualização de consultas +4. Verificar notificações SMS +5. Testar fluxo completo de check-in e prontuário + +## 📝 Exemplos de Uso + +### Criar Consulta + +```typescript +import { appointmentService } from "@/services"; + +const appointment = await appointmentService.create({ + patient_id: "patient-uuid", + doctor_id: "doctor-uuid", + scheduled_at: "2025-11-03T10:00:00.000Z", + duration_minutes: 30, + appointment_type: "presencial", + chief_complaint: "Consulta de rotina", +}); +``` + +### Criar Disponibilidade + +```typescript +import { availabilityService } from "@/services"; + +const availability = await availabilityService.create({ + doctor_id: "doctor-uuid", + weekday: "monday", + start_time: "07:00:00", + end_time: "19:00:00", + slot_duration_minutes: 30, + appointment_type: "presencial", +}); +``` + +### Login + +```typescript +import { authService } from "@/services"; + +const response = await authService.login({ + email: "user@example.com", + password: "senha123", +}); + +// response.access_token - JWT token +// response.user - dados do usuário +``` diff --git a/MEDICONNECT 2/CHECKLIST-TESTES.md b/MEDICONNECT 2/CHECKLIST-TESTES.md new file mode 100644 index 000000000..95bc8f4b0 --- /dev/null +++ b/MEDICONNECT 2/CHECKLIST-TESTES.md @@ -0,0 +1,292 @@ +# ✅ Checklist de Testes - MediConnect + +## 🎯 Testes Funcionais + +### 1. Autenticação ✅ + +#### Login + +- [x] Login de admin funcionando +- [x] Login de médico (Dr. Fernando) funcionando +- [x] Login de paciente (Aurora) funcionando +- [x] Token JWT sendo retornado corretamente +- [x] Refresh token funcionando + +#### Recuperação de Senha + +- [x] Email de recuperação sendo enviado +- [x] Token de recuperação detectado na URL +- [x] Reset de senha funcionando (senha diferente da anterior) +- [x] Erro 422 tratado quando senha é igual à anterior +- [x] Redirecionamento para página de reset funcionando + +### 2. Gestão de Usuários ✅ + +#### Criação de Paciente + +- [x] Edge Function `create-user-with-password` funcionando +- [x] Paciente criado com auth user +- [x] Registro na tabela `patients` criado +- [x] `user_id` vinculado corretamente ao `auth.users.id` +- [x] Credenciais de login funcionando após criação + +**Usuário Teste:** + +- Email: aurora-nascimento94@gmx.com +- Senha: auroranasc94 +- Patient ID: b85486f7-9135-4b67-9aa7-b884d9603d12 + +#### Médicos + +- [x] Dr. Fernando no sistema +- [x] User ID vinculado corretamente +- [x] Doctor ID identificado +- [x] CRM registrado + +**Médico Teste:** + +- Email: fernando.pirichowski@souunit.com.br +- Senha: fernando123 +- Doctor ID: 6dad001d-229b-40b5-80f3-310243c4599c + +### 3. Disponibilidade Médica ✅ + +#### Criação de Disponibilidade + +- [x] Script de criação funcionando +- [x] Campo `weekday` usando strings em inglês +- [x] Formato de horário correto (HH:MM:SS) +- [x] Disponibilidade criada para todos os dias da semana +- [x] Horário: 07:00 - 19:00 +- [x] Duração de slot: 30 minutos +- [x] Tipo: presencial + +**Status:** + +- ✅ 7 dias configurados (Domingo a Sábado) +- ✅ Dr. Fernando disponível das 07:00 às 19:00 + +### 4. Agendamento de Consultas ✅ + +#### Criação de Consulta + +- [x] API de appointments funcionando +- [x] Campo `scheduled_at` usando ISO 8601 DateTime +- [x] Consulta criada com status "requested" +- [x] Order number gerado automaticamente (APT-YYYY-NNNN) +- [x] Duração de 30 minutos configurada +- [x] Tipo presencial configurado + +**Consulta Teste:** + +- Paciente: Aurora +- Médico: Dr. Fernando +- Data: 03/11/2025 às 10:00 +- Order Number: APT-2025-00027 +- ID: cb4f608f-e580-437f-8653-75ec74621065 + +### 5. Frontend - Componentes + +#### AgendamentoConsulta.tsx ✅ + +- [x] Usando `appointmentService` correto +- [x] Campo `scheduled_at` implementado +- [x] Formato ISO 8601 DateTime +- [x] Integração com availability service +- [x] Integração com exceptions service +- [x] SMS notification configurado + +#### Outros Componentes + +- [ ] BookAppointment.tsx - não usado (pode ser removido) +- [ ] AgendamentoConsultaSimples.tsx - não usado (pode ser removido) + +## 🔧 Configurações Verificadas + +### API Config ✅ + +- [x] `src/services/api/config.ts` - URLs corretas +- [x] SUPABASE_ANON_KEY atualizada +- [x] Endpoints configurados corretamente + +### Services ✅ + +- [x] `appointmentService` - usando campos corretos +- [x] `availabilityService` - usando weekday strings +- [x] `authService` - recuperação de senha funcionando +- [x] `patientService` - CRUD funcionando +- [x] `doctorService` - CRUD funcionando +- [x] Todos exportados em `src/services/index.ts` + +## 🧪 Testes Pendentes + +### Fluxo Completo de Agendamento + +- [ ] Paciente faz login +- [ ] Paciente busca médicos disponíveis +- [ ] Paciente visualiza horários disponíveis +- [ ] Paciente agenda consulta +- [ ] Consulta aparece na lista do paciente +- [ ] Médico visualiza consulta na agenda +- [ ] Notificação SMS enviada + +### Check-in e Atendimento + +- [ ] Check-in de paciente +- [ ] Status da consulta muda para "checked_in" +- [ ] Médico inicia atendimento +- [ ] Status muda para "in_progress" +- [ ] Preenchimento de prontuário +- [ ] Finalização da consulta +- [ ] Status muda para "completed" + +### Cancelamento + +- [ ] Paciente cancela consulta +- [ ] Médico cancela consulta +- [ ] Status muda para "cancelled" +- [ ] Motivo do cancelamento registrado +- [ ] Horário fica disponível novamente + +### Exceções de Disponibilidade + +- [ ] Criar exceção (feriado, folga) +- [ ] Exceção bloqueia horários +- [ ] Listar exceções +- [ ] Remover exceção + +## 📊 Métricas e Relatórios + +- [ ] Dashboard de consultas +- [ ] Estatísticas de atendimento +- [ ] Relatório de faturamento +- [ ] Exportação de dados + +## 🔐 Segurança + +### Autenticação + +- [x] JWT tokens funcionando +- [x] Refresh tokens implementados +- [x] Session storage configurado +- [ ] Expiração de tokens tratada +- [ ] Logout funcionando corretamente + +### Autorização + +- [ ] RLS (Row Level Security) configurado no Supabase +- [ ] Paciente só vê suas próprias consultas +- [ ] Médico só vê consultas atribuídas +- [ ] Admin tem acesso total +- [ ] Secretária tem permissões específicas + +## 🌐 Deploy e Performance + +- [ ] Build de produção funcionando +- [ ] Deploy no Cloudflare Pages +- [ ] URLs de produção configuradas +- [ ] Performance otimizada +- [ ] Lazy loading de componentes +- [ ] Cache configurado + +## 📱 Responsividade + +- [ ] Desktop (1920x1080) +- [ ] Laptop (1366x768) +- [ ] Tablet (768x1024) +- [ ] Mobile (375x667) + +## ♿ Acessibilidade + +- [ ] Menu de acessibilidade funcionando +- [ ] Contraste de cores ajustável +- [ ] Tamanho de fonte ajustável +- [ ] Leitura de tela compatível +- [ ] Navegação por teclado + +## 🐛 Bugs Conhecidos + +Nenhum bug crítico identificado até o momento. + +## 📝 Notas Importantes + +### Campos Corretos nas APIs + +1. **appointments.scheduled_at** + + - ❌ NÃO: `appointment_date` e `appointment_time` separados + - ✅ SIM: `scheduled_at` com ISO 8601 DateTime + +2. **doctor_availability.weekday** + + - ❌ NÃO: Números 0-6 + - ✅ SIM: Strings "sunday", "monday", etc. + +3. **patients.user_id** + + - ⚠️ DEVE estar vinculado ao `auth.users.id` + - Sem isso, queries por user_id falham + +4. **Password Recovery** + - ⚠️ Nova senha DEVE ser diferente da anterior + - Erro 422 com `error_code: "same_password"` se igual + +### Scripts Úteis + +```bash +# Login como usuário +node get-fernando-user-id.cjs + +# Buscar dados de paciente +node get-aurora-info.cjs + +# Criar disponibilidade +node create-fernando-availability.cjs + +# Criar consulta +node create-aurora-appointment.cjs + +# Corrigir user_id +node fix-aurora-user-id.cjs +``` + +## 🚀 Próximas Funcionalidades + +1. **Telemedicina** + + - [ ] Integração com serviço de videochamada + - [ ] Sala de espera virtual + - [ ] Gravação de consultas (opcional) + +2. **Prontuário Eletrônico** + + - [ ] CRUD completo de prontuários + - [ ] Histórico de consultas + - [ ] Anexos e exames + - [ ] Prescrições médicas + +3. **Notificações** + + - [x] SMS via Twilio configurado + - [ ] Email notifications + - [ ] Push notifications (PWA) + - [ ] Lembretes de consulta + +4. **Pagamentos** + + - [ ] Integração com gateway de pagamento + - [ ] Registro de pagamentos + - [ ] Emissão de recibos + - [ ] Relatório financeiro + +5. **Telemática** + - [ ] Assinatura digital de documentos + - [ ] Certificação digital A1/A3 + - [ ] Integração com e-SUS + - [ ] Compliance LGPD + +--- + +**Última atualização:** 27/10/2025 +**Status:** ✅ APIs configuradas e funcionando +**Próximo passo:** Testar fluxo completo no frontend diff --git a/MEDICONNECT 2/create-aurora-appointment.cjs b/MEDICONNECT 2/create-aurora-appointment.cjs new file mode 100644 index 000000000..4112b36e2 --- /dev/null +++ b/MEDICONNECT 2/create-aurora-appointment.cjs @@ -0,0 +1,128 @@ +const axios = require("axios"); + +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +async function createAppointment() { + try { + console.log("🔐 Fazendo login como Aurora..."); + + // Login como Aurora + const loginResponse = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: "aurora-nascimento94@gmx.com", + password: "auroranasc94", + }, + { + headers: { + apikey: SUPABASE_ANON_KEY, + "Content-Type": "application/json", + }, + } + ); + + const auroraToken = loginResponse.data.access_token; + console.log("✅ Login realizado como Aurora"); + + // Buscar o patient_id da Aurora + console.log("\n👤 Buscando dados da paciente..."); + const patientResponse = await axios.get( + `${SUPABASE_URL}/rest/v1/patients?user_id=eq.${loginResponse.data.user.id}`, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${auroraToken}`, + }, + } + ); + + if (!patientResponse.data || patientResponse.data.length === 0) { + throw new Error("Paciente não encontrada"); + } + + const patientId = patientResponse.data[0].id; + console.log(`✅ Patient ID: ${patientId}`); + + // Buscar disponibilidade do Dr. Fernando para segunda-feira + console.log("\n📅 Buscando disponibilidade do Dr. Fernando..."); + const availabilityResponse = await axios.get( + `${SUPABASE_URL}/rest/v1/doctor_availability?doctor_id=eq.6dad001d-229b-40b5-80f3-310243c4599c&weekday=eq.monday`, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${auroraToken}`, + }, + } + ); + + if (!availabilityResponse.data || availabilityResponse.data.length === 0) { + throw new Error("Disponibilidade não encontrada"); + } + + const availability = availabilityResponse.data[0]; + console.log( + `✅ Disponibilidade encontrada: ${availability.start_time} - ${availability.end_time}` + ); + + // Criar consulta para próxima segunda-feira às 10:00 + const today = new Date(); + const daysUntilMonday = (1 - today.getDay() + 7) % 7 || 7; // Próxima segunda + const appointmentDate = new Date(today); + appointmentDate.setDate(today.getDate() + daysUntilMonday); + appointmentDate.setHours(10, 0, 0, 0); + + const scheduledAt = appointmentDate.toISOString(); + + console.log(`\n📝 Criando consulta para ${scheduledAt}...`); + + const appointmentData = { + patient_id: patientId, + doctor_id: "6dad001d-229b-40b5-80f3-310243c4599c", + scheduled_at: scheduledAt, + duration_minutes: 30, + appointment_type: "presencial", + chief_complaint: "Consulta de rotina", + }; + + const appointmentResponse = await axios.post( + `${SUPABASE_URL}/rest/v1/appointments`, + appointmentData, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${auroraToken}`, + "Content-Type": "application/json", + Prefer: "return=representation", + }, + } + ); + + console.log("\n✅ Consulta criada com sucesso!"); + console.log("\n📋 Detalhes da consulta:"); + console.log(` - Paciente: Aurora Sabrina Clara Nascimento`); + console.log(` - Médico: Dr. Fernando Pirichowski`); + console.log(` - Data/Hora: ${scheduledAt}`); + console.log(` - Duração: 30 minutos`); + console.log(` - Tipo: presencial`); + + if (appointmentResponse.data && appointmentResponse.data.length > 0) { + console.log(` - ID da consulta: ${appointmentResponse.data[0].id}`); + console.log( + ` - Order Number: ${appointmentResponse.data[0].order_number}` + ); + console.log(` - Status: ${appointmentResponse.data[0].status}`); + } + } catch (error) { + console.error( + "❌ Erro ao criar consulta:", + error.response?.data || error.message + ); + if (error.response?.data) { + console.error("Detalhes:", JSON.stringify(error.response.data, null, 2)); + } + } +} + +createAppointment(); diff --git a/MEDICONNECT 2/create-fernando-availability.cjs b/MEDICONNECT 2/create-fernando-availability.cjs new file mode 100644 index 000000000..97bdf85cc --- /dev/null +++ b/MEDICONNECT 2/create-fernando-availability.cjs @@ -0,0 +1,93 @@ +const axios = require("axios"); + +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +// IDs +const DOCTOR_ID = "6dad001d-229b-40b5-80f3-310243c4599c"; // Fernando (CRM 24245) +const ADMIN_ID = "c7fcd702-9a6e-4b7c-abd3-956b25af407d"; // Admin (riseup) + +async function main() { + try { + console.log("🔐 Fazendo login como admin..."); + + const loginResponse = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: "riseup@popcode.com.br", + password: "riseup", + }, + { + headers: { + "Content-Type": "application/json", + apikey: SUPABASE_ANON_KEY, + }, + } + ); + + const adminToken = loginResponse.data.access_token; + console.log("✅ Login realizado\n"); + + console.log("📅 Criando disponibilidade para Dr. Fernando..."); + console.log("⏰ Horário: 07:00 às 19:00"); + console.log("📆 Dias: Segunda a Domingo\n"); + + const weekdays = [ + { num: "sunday", name: "Domingo" }, + { num: "monday", name: "Segunda-feira" }, + { num: "tuesday", name: "Terça-feira" }, + { num: "wednesday", name: "Quarta-feira" }, + { num: "thursday", name: "Quinta-feira" }, + { num: "friday", name: "Sexta-feira" }, + { num: "saturday", name: "Sábado" }, + ]; + + for (const day of weekdays) { + try { + const availabilityData = { + doctor_id: DOCTOR_ID, + weekday: day.num, + start_time: "07:00:00", + end_time: "19:00:00", + slot_minutes: 30, + appointment_type: "presencial", + active: true, + created_by: ADMIN_ID, + }; + + const response = await axios.post( + `${SUPABASE_URL}/rest/v1/doctor_availability`, + availabilityData, + { + headers: { + "Content-Type": "application/json", + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${adminToken}`, + Prefer: "return=representation", + }, + } + ); + + console.log(`✅ ${day.name}: Disponibilidade criada`); + } catch (error) { + console.error( + `❌ ${day.name}: Erro -`, + error.response?.data?.message || error.message + ); + } + } + + console.log("\n🎉 Disponibilidade criada com sucesso!"); + console.log("\n📋 Resumo:"); + console.log("- Médico: Dr. Fernando Pirichowski"); + console.log("- Dias: Todos os dias da semana (Domingo a Sábado)"); + console.log("- Horário: 07:00 às 19:00"); + console.log("- Duração consulta: 30 minutos"); + console.log("- Tipo: Presencial"); + } catch (error) { + console.error("❌ Erro geral:", error.response?.data || error.message); + } +} + +main(); diff --git a/MEDICONNECT 2/create-patient-with-password.cjs b/MEDICONNECT 2/create-patient-with-password.cjs new file mode 100644 index 000000000..b898f36c5 --- /dev/null +++ b/MEDICONNECT 2/create-patient-with-password.cjs @@ -0,0 +1,80 @@ +const axios = require("axios"); + +// Configuração +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +// Credenciais do admin +const ADMIN_EMAIL = "riseup@popcode.com.br"; +const ADMIN_PASSWORD = "riseup"; + +// Dados do paciente (Aurora Sabrina Clara Nascimento) +const PATIENT_DATA = { + email: "aurora-nascimento94@gmx.com", + password: "auroranasc94", + full_name: "Aurora Sabrina Clara Nascimento", + phone_mobile: "(21) 99856-3014", + cpf: "66864784231", // CPF sem pontuação + create_patient_record: true, + role: "paciente", +}; + +async function main() { + try { + console.log("🔐 1. Fazendo login como admin..."); + + // 1. Login do admin + const loginResponse = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: ADMIN_EMAIL, + password: ADMIN_PASSWORD, + }, + { + headers: { + "Content-Type": "application/json", + apikey: SUPABASE_ANON_KEY, + }, + } + ); + + const adminToken = loginResponse.data.access_token; + console.log("✅ Login realizado com sucesso!"); + console.log("🔑 Token:", adminToken.substring(0, 30) + "..."); + + console.log("\n👤 2. Criando paciente..."); + console.log("Dados:", JSON.stringify(PATIENT_DATA, null, 2)); + + // 2. Criar paciente + const createResponse = await axios.post( + `${SUPABASE_URL}/functions/v1/create-user-with-password`, + PATIENT_DATA, + { + headers: { + "Content-Type": "application/json", + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${adminToken}`, + }, + } + ); + + console.log("\n✅ Paciente criado com sucesso!"); + console.log("Resposta:", JSON.stringify(createResponse.data, null, 2)); + + if (createResponse.data.patient_id) { + console.log("\n📋 ID do paciente:", createResponse.data.patient_id); + console.log("✉️ Email de confirmação enviado para:", PATIENT_DATA.email); + console.log("🔐 Senha temporária:", PATIENT_DATA.password); + console.log( + "\n⚠️ O usuário precisa confirmar o email antes de fazer login!" + ); + } + } catch (error) { + console.error("❌ Erro:", error.response?.data || error.message); + console.error("Status:", error.response?.status); + console.error("URL:", error.config?.url); + } +} + +main(); diff --git a/MEDICONNECT 2/fix-aurora-user-id.cjs b/MEDICONNECT 2/fix-aurora-user-id.cjs new file mode 100644 index 000000000..f03fc3c88 --- /dev/null +++ b/MEDICONNECT 2/fix-aurora-user-id.cjs @@ -0,0 +1,72 @@ +const axios = require("axios"); + +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +async function fixAuroraUserId() { + try { + console.log("🔐 Fazendo login como admin..."); + + const loginResponse = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: "riseup@popcode.com.br", + password: "riseup", + }, + { + headers: { + apikey: SUPABASE_ANON_KEY, + "Content-Type": "application/json", + }, + } + ); + + const adminToken = loginResponse.data.access_token; + console.log("✅ Login realizado"); + + // Fazer login como Aurora para pegar o user_id + console.log("\n👤 Fazendo login como Aurora..."); + const auroraLoginResponse = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: "aurora-nascimento94@gmx.com", + password: "auroranasc94", + }, + { + headers: { + apikey: SUPABASE_ANON_KEY, + "Content-Type": "application/json", + }, + } + ); + + const auroraUserId = auroraLoginResponse.data.user.id; + console.log(`✅ User ID da Aurora: ${auroraUserId}`); + + // Atualizar patient com user_id + console.log("\n📝 Atualizando registro da paciente..."); + const updateResponse = await axios.patch( + `${SUPABASE_URL}/rest/v1/patients?id=eq.b85486f7-9135-4b67-9aa7-b884d9603d12`, + { + user_id: auroraUserId, + }, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${adminToken}`, + "Content-Type": "application/json", + Prefer: "return=representation", + }, + } + ); + + console.log("✅ Registro atualizado com sucesso!"); + console.log(` - Patient ID: b85486f7-9135-4b67-9aa7-b884d9603d12`); + console.log(` - User ID: ${auroraUserId}`); + } catch (error) { + console.error("❌ Erro:", error.response?.data || error.message); + } +} + +fixAuroraUserId(); diff --git a/MEDICONNECT 2/get-aurora-info.cjs b/MEDICONNECT 2/get-aurora-info.cjs new file mode 100644 index 000000000..0c92e8ed0 --- /dev/null +++ b/MEDICONNECT 2/get-aurora-info.cjs @@ -0,0 +1,59 @@ +const axios = require("axios"); + +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +async function main() { + try { + console.log("🔐 Fazendo login como admin..."); + + const loginResponse = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: "riseup@popcode.com.br", + password: "riseup", + }, + { + headers: { + "Content-Type": "application/json", + apikey: SUPABASE_ANON_KEY, + }, + } + ); + + const adminToken = loginResponse.data.access_token; + console.log("✅ Login realizado\n"); + + console.log("👤 Buscando dados de Aurora na tabela patients..."); + + const patientsResponse = await axios.get( + `${SUPABASE_URL}/rest/v1/patients?email=eq.aurora-nascimento94@gmx.com`, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${adminToken}`, + }, + } + ); + + if (patientsResponse.data.length > 0) { + const patient = patientsResponse.data[0]; + console.log("✅ Paciente encontrada!\n"); + console.log("📋 DADOS DA AURORA:\n"); + console.log("User ID (auth):", patient.id); + console.log("Patient ID:", patient.id); // Em patients, o id é o mesmo do auth + console.log("Nome:", patient.full_name); + console.log("Email:", patient.email); + console.log("CPF:", patient.cpf); + console.log("Telefone:", patient.phone_mobile); + console.log("\n📄 Dados completos:", JSON.stringify(patient, null, 2)); + } else { + console.log("❌ Paciente não encontrada na tabela patients"); + } + } catch (error) { + console.error("❌ Erro:", error.response?.data || error.message); + } +} + +main(); diff --git a/MEDICONNECT 2/get-fernando-complete.cjs b/MEDICONNECT 2/get-fernando-complete.cjs new file mode 100644 index 000000000..a4a641d45 --- /dev/null +++ b/MEDICONNECT 2/get-fernando-complete.cjs @@ -0,0 +1,80 @@ +const axios = require("axios"); + +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +async function main() { + try { + console.log("🔐 Fazendo login como admin..."); + + const loginResponse = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: "riseup@popcode.com.br", + password: "riseup", + }, + { + headers: { + "Content-Type": "application/json", + apikey: SUPABASE_ANON_KEY, + }, + } + ); + + const adminToken = loginResponse.data.access_token; + console.log("✅ Login realizado\n"); + + console.log("🔍 Buscando Fernando em profiles..."); + + const profilesResponse = await axios.get( + `${SUPABASE_URL}/rest/v1/profiles?email=eq.fernando.pirichowski@souunit.com.br`, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${adminToken}`, + }, + } + ); + + if (profilesResponse.data.length > 0) { + console.log( + `✅ ${profilesResponse.data.length} perfil(is) encontrado(s)!\n` + ); + + profilesResponse.data.forEach((profile, index) => { + console.log(`📋 PERFIL ${index + 1}:\n`); + console.log("User ID:", profile.id); + console.log("Email:", profile.email); + console.log("Nome:", profile.full_name); + console.log("Telefone:", profile.phone || "Não informado"); + console.log("\n" + "=".repeat(60) + "\n"); + }); + + // Pegar roles do primeiro perfil + const userId = profilesResponse.data[0].id; + console.log("🔍 Buscando roles..."); + + const rolesResponse = await axios.get( + `${SUPABASE_URL}/rest/v1/user_roles?user_id=eq.${userId}`, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${adminToken}`, + }, + } + ); + + console.log( + "📌 Roles:", + rolesResponse.data.map((r) => r.role).join(", ") + ); + } else { + console.log("❌ Nenhum perfil encontrado"); + } + } catch (error) { + console.error("❌ Erro:", error.response?.data || error.message); + } +} + +main(); diff --git a/MEDICONNECT 2/get-fernando-doctor-id.cjs b/MEDICONNECT 2/get-fernando-doctor-id.cjs new file mode 100644 index 000000000..42d079b3e --- /dev/null +++ b/MEDICONNECT 2/get-fernando-doctor-id.cjs @@ -0,0 +1,82 @@ +const axios = require("axios"); + +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +async function main() { + try { + console.log("🔐 Fazendo login como admin..."); + + const loginResponse = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: "riseup@popcode.com.br", + password: "riseup", + }, + { + headers: { + "Content-Type": "application/json", + apikey: SUPABASE_ANON_KEY, + }, + } + ); + + const adminToken = loginResponse.data.access_token; + console.log("✅ Login realizado\n"); + + console.log("👨‍⚕️ Buscando dados de Fernando na tabela doctors..."); + + const doctorsResponse = await axios.get( + `${SUPABASE_URL}/rest/v1/doctors?email=eq.fernando.pirichowski@souunit.com.br`, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${adminToken}`, + Prefer: "return=representation", + }, + } + ); + + if (doctorsResponse.data.length > 0) { + console.log( + `✅ ${doctorsResponse.data.length} médico(s) encontrado(s)!\n` + ); + + doctorsResponse.data.forEach((doctor, index) => { + console.log(`📋 MÉDICO ${index + 1}:\n`); + console.log("Doctor ID:", doctor.id); + console.log("Nome:", doctor.full_name); + console.log("Email:", doctor.email); + console.log("CRM:", doctor.crm); + console.log("Especialidade:", doctor.specialty || "Não informada"); + console.log("Telefone:", doctor.phone || "Não informado"); + console.log("\n" + "=".repeat(60) + "\n"); + }); + } else { + console.log("❌ Nenhum médico chamado Fernando encontrado"); + console.log("\n🔍 Buscando todos os médicos..."); + + const allDoctorsResponse = await axios.get( + `${SUPABASE_URL}/rest/v1/doctors`, + { + headers: { + apikey: SUPABASE_ANON_KEY, + Authorization: `Bearer ${adminToken}`, + }, + } + ); + + console.log( + `\n📊 Total de médicos cadastrados: ${allDoctorsResponse.data.length}` + ); + allDoctorsResponse.data.forEach((doctor, index) => { + console.log(`${index + 1}. ${doctor.full_name} - ${doctor.email}`); + }); + } + } catch (error) { + console.error("❌ Erro:", error.response?.data || error.message); + } +} + +main(); diff --git a/MEDICONNECT 2/get-fernando-user-id.cjs b/MEDICONNECT 2/get-fernando-user-id.cjs new file mode 100644 index 000000000..c7fc2f186 --- /dev/null +++ b/MEDICONNECT 2/get-fernando-user-id.cjs @@ -0,0 +1,38 @@ +const axios = require("axios"); + +const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; +const SUPABASE_ANON_KEY = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ"; + +async function main() { + try { + console.log("🔐 Fazendo login como Fernando..."); + + const loginResponse = await axios.post( + `${SUPABASE_URL}/auth/v1/token?grant_type=password`, + { + email: "fernando.pirichowski@souunit.com.br", + password: "fernando123", + }, + { + headers: { + "Content-Type": "application/json", + apikey: SUPABASE_ANON_KEY, + }, + } + ); + + const userData = loginResponse.data; + console.log("✅ Login realizado\n"); + + console.log("📋 DADOS DO FERNANDO (AUTH):\n"); + console.log("User ID:", userData.user.id); + console.log("Email:", userData.user.email); + console.log("Role:", userData.user.role); + console.log("\n" + "=".repeat(60)); + } catch (error) { + console.error("❌ Erro:", error.response?.data || error.message); + } +} + +main(); diff --git a/MEDICONNECT 2/src/components/auth/RecoveryRedirect.tsx b/MEDICONNECT 2/src/components/auth/RecoveryRedirect.tsx index 8e676a90f..538a192a9 100644 --- a/MEDICONNECT 2/src/components/auth/RecoveryRedirect.tsx +++ b/MEDICONNECT 2/src/components/auth/RecoveryRedirect.tsx @@ -14,27 +14,31 @@ const RecoveryRedirect: React.FC = () => { console.log("[RecoveryRedirect] Pathname:", location.pathname); console.log("[RecoveryRedirect] Search:", location.search); console.log("[RecoveryRedirect] Hash:", location.hash); - + let shouldRedirect = false; - + // Verificar query string: ?token=xxx&type=recovery const searchParams = new URLSearchParams(location.search); const queryToken = searchParams.get("token"); const queryType = searchParams.get("type"); - + if (queryToken && queryType === "recovery") { - console.log("[RecoveryRedirect] ✅ Token de recovery no query string detectado"); + console.log( + "[RecoveryRedirect] ✅ Token de recovery no query string detectado" + ); shouldRedirect = true; } - + // Verificar hash: #access_token=xxx&type=recovery if (location.hash) { const hashParams = new URLSearchParams(location.hash.substring(1)); const hashToken = hashParams.get("access_token"); const hashType = hashParams.get("type"); - + if (hashToken && hashType === "recovery") { - console.log("[RecoveryRedirect] ✅ Token de recovery no hash detectado"); + console.log( + "[RecoveryRedirect] ✅ Token de recovery no hash detectado" + ); shouldRedirect = true; } } @@ -42,7 +46,9 @@ const RecoveryRedirect: React.FC = () => { if (shouldRedirect) { console.log("[RecoveryRedirect] 🔄 Redirecionando para /reset-password"); // Preservar os parâmetros e redirecionar - navigate(`/reset-password${location.search}${location.hash}`, { replace: true }); + navigate(`/reset-password${location.search}${location.hash}`, { + replace: true, + }); } else { console.log("[RecoveryRedirect] ℹ️ Nenhum token de recovery detectado"); } diff --git a/MEDICONNECT 2/src/pages/AcompanhamentoPaciente.tsx b/MEDICONNECT 2/src/pages/AcompanhamentoPaciente.tsx index d4ea306dd..59e52d6df 100644 --- a/MEDICONNECT 2/src/pages/AcompanhamentoPaciente.tsx +++ b/MEDICONNECT 2/src/pages/AcompanhamentoPaciente.tsx @@ -87,9 +87,9 @@ const AcompanhamentoPaciente: React.FC = () => { const testAvatar = async () => { for (const ext of extensions) { try { - const url = avatarService.getPublicUrl({ - userId: user.id, - ext: ext as "jpg" | "png" | "webp" + const url = avatarService.getPublicUrl({ + userId: user.id, + ext: ext as "jpg" | "png" | "webp", }); const response = await fetch(url, { method: "HEAD" }); if (response.ok) { diff --git a/MEDICONNECT 2/src/pages/AuthCallback.tsx b/MEDICONNECT 2/src/pages/AuthCallback.tsx index 6ecd3c591..c2935c9b3 100644 --- a/MEDICONNECT 2/src/pages/AuthCallback.tsx +++ b/MEDICONNECT 2/src/pages/AuthCallback.tsx @@ -22,23 +22,25 @@ export default function AuthCallback() { const handleCallback = async () => { try { console.log("[AuthCallback] Iniciando processamento"); - + // Verificar se é um token de recovery const hash = window.location.hash; const hashParams = new URLSearchParams(hash.substring(1)); const accessToken = hashParams.get("access_token"); const type = hashParams.get("type"); - + console.log("[AuthCallback] Hash:", hash); console.log("[AuthCallback] Type:", type); console.log("[AuthCallback] Access Token presente:", !!accessToken); // Se for recovery, redirecionar para página de reset if (type === "recovery" && accessToken) { - console.log("[AuthCallback] ✅ Token de recovery detectado, redirecionando para /reset-password"); + console.log( + "[AuthCallback] ✅ Token de recovery detectado, redirecionando para /reset-password" + ); setStatus("success"); setMessage("Redirecionando para página de redefinição de senha..."); - + // Redirecionar preservando o hash com o token setTimeout(() => { navigate(`/reset-password${hash}`, { replace: true }); @@ -81,20 +83,32 @@ export default function AuthCallback() { setMessage("Autenticado com sucesso! Redirecionando..."); toast.success("Login realizado com sucesso!"); - // Redirecionar baseado no role + // Redirecionar baseado no contexto salvo ou role do usuário setTimeout(() => { + // Verificar se há redirecionamento salvo do magic link + const savedRedirect = localStorage.getItem("magic_link_redirect"); + + if (savedRedirect) { + console.log("[AuthCallback] Redirecionando para:", savedRedirect); + localStorage.removeItem("magic_link_redirect"); // Limpar após uso + navigate(savedRedirect, { replace: true }); + return; + } + + // Fallback: redirecionar baseado no role const userRole = session.user.user_metadata?.role || "paciente"; + console.log("[AuthCallback] Redirecionando baseado no role:", userRole); switch (userRole) { case "medico": - navigate("/painel-medico"); + navigate("/painel-medico", { replace: true }); break; case "secretaria": - navigate("/painel-secretaria"); + navigate("/painel-secretaria", { replace: true }); break; case "paciente": default: - navigate("/acompanhamento"); + navigate("/acompanhamento", { replace: true }); break; } }, 1500); diff --git a/MEDICONNECT 2/src/pages/Home.tsx b/MEDICONNECT 2/src/pages/Home.tsx index a3377e723..9dbd6b08e 100644 --- a/MEDICONNECT 2/src/pages/Home.tsx +++ b/MEDICONNECT 2/src/pages/Home.tsx @@ -100,7 +100,7 @@ const Home: React.FC = () => {
{/* Componente invisível que detecta tokens de recuperação e redireciona */} - + {/* Hero Section com Background Rotativo */} diff --git a/MEDICONNECT 2/src/pages/LoginMedico.tsx b/MEDICONNECT 2/src/pages/LoginMedico.tsx index ae2612817..e0d34a756 100644 --- a/MEDICONNECT 2/src/pages/LoginMedico.tsx +++ b/MEDICONNECT 2/src/pages/LoginMedico.tsx @@ -202,9 +202,12 @@ const LoginMedico: React.FC = () => { } setLoading(true); try { + // Salvar contexto para redirecionamento correto após magic link + localStorage.setItem("magic_link_redirect", "/painel-medico"); + await authService.sendMagicLink( formData.email, - "https://mediconnectbrasil.netlify.app/medico/painel" + `${window.location.origin}/auth/callback` ); toast.success( "Link de acesso enviado para seu email! Verifique sua caixa de entrada.", diff --git a/MEDICONNECT 2/src/pages/LoginPaciente.tsx b/MEDICONNECT 2/src/pages/LoginPaciente.tsx index 34295c676..ca5df6896 100644 --- a/MEDICONNECT 2/src/pages/LoginPaciente.tsx +++ b/MEDICONNECT 2/src/pages/LoginPaciente.tsx @@ -312,9 +312,12 @@ const LoginPaciente: React.FC = () => { } setLoading(true); try { + // Salvar contexto para redirecionamento correto após magic link + localStorage.setItem("magic_link_redirect", "/acompanhamento"); + await authService.sendMagicLink( formData.email, - "https://mediconnectbrasil.netlify.app/paciente/agendamento" + `${window.location.origin}/auth/callback` ); toast.success( "Link de acesso enviado para seu email! Verifique sua caixa de entrada.", diff --git a/MEDICONNECT 2/src/pages/LoginSecretaria.tsx b/MEDICONNECT 2/src/pages/LoginSecretaria.tsx index 8577ae773..18d649247 100644 --- a/MEDICONNECT 2/src/pages/LoginSecretaria.tsx +++ b/MEDICONNECT 2/src/pages/LoginSecretaria.tsx @@ -212,9 +212,12 @@ const LoginSecretaria: React.FC = () => { } setLoading(true); try { + // Salvar contexto para redirecionamento correto após magic link + localStorage.setItem("magic_link_redirect", "/painel-secretaria"); + await authService.sendMagicLink( formData.email, - "https://mediconnectbrasil.netlify.app/secretaria/painel" + `${window.location.origin}/auth/callback` ); toast.success( "Link de acesso enviado para seu email! Verifique sua caixa de entrada.", diff --git a/MEDICONNECT 2/src/pages/PainelMedico.tsx b/MEDICONNECT 2/src/pages/PainelMedico.tsx index 0f9a1d6e2..9b173156f 100644 --- a/MEDICONNECT 2/src/pages/PainelMedico.tsx +++ b/MEDICONNECT 2/src/pages/PainelMedico.tsx @@ -109,9 +109,9 @@ const PainelMedico: React.FC = () => { const testAvatar = async () => { for (const ext of extensions) { try { - const url = avatarService.getPublicUrl({ - userId: user.id, - ext: ext as "jpg" | "png" | "webp" + const url = avatarService.getPublicUrl({ + userId: user.id, + ext: ext as "jpg" | "png" | "webp", }); const response = await fetch(url, { method: "HEAD" }); if (response.ok) { diff --git a/MEDICONNECT 2/src/pages/PerfilPaciente.tsx b/MEDICONNECT 2/src/pages/PerfilPaciente.tsx index fbfa705bc..fc9014346 100644 --- a/MEDICONNECT 2/src/pages/PerfilPaciente.tsx +++ b/MEDICONNECT 2/src/pages/PerfilPaciente.tsx @@ -192,9 +192,7 @@ export default function PerfilPaciente() {
-

- Carregando perfil... -

+

Carregando perfil...

); @@ -204,9 +202,7 @@ export default function PerfilPaciente() { return (
-

- Usuário não identificado -

+

Usuário não identificado

@@ -688,4 +680,3 @@ export default function PerfilPaciente() {
); } - diff --git a/MEDICONNECT 2/src/pages/ResetPassword.tsx b/MEDICONNECT 2/src/pages/ResetPassword.tsx index 09a1d643e..28a1b48c9 100644 --- a/MEDICONNECT 2/src/pages/ResetPassword.tsx +++ b/MEDICONNECT 2/src/pages/ResetPassword.tsx @@ -16,7 +16,7 @@ const ResetPassword: React.FC = () => { useEffect(() => { // Extrair access_token do hash da URL (#access_token=...) // OU do query string (?token=...&type=recovery) - + let token: string | null = null; let type: string | null = null; @@ -28,8 +28,11 @@ const ResetPassword: React.FC = () => { const hashParams = new URLSearchParams(hash.substring(1)); token = hashParams.get("access_token"); type = hashParams.get("type"); - - console.log("[ResetPassword] Token do hash:", token ? token.substring(0, 20) + "..." : "null"); + + console.log( + "[ResetPassword] Token do hash:", + token ? token.substring(0, 20) + "..." : "null" + ); console.log("[ResetPassword] Type do hash:", type); } @@ -37,23 +40,30 @@ const ResetPassword: React.FC = () => { if (!token) { const search = window.location.search; console.log("[ResetPassword] Query string completo:", search); - + if (search) { const queryParams = new URLSearchParams(search); token = queryParams.get("token"); type = queryParams.get("type"); - - console.log("[ResetPassword] Token do query:", token ? token.substring(0, 20) + "..." : "null"); + + console.log( + "[ResetPassword] Token do query:", + token ? token.substring(0, 20) + "..." : "null" + ); console.log("[ResetPassword] Type do query:", type); } } if (token) { setAccessToken(token); - console.log("[ResetPassword] ✅ Token de recuperação detectado e armazenado"); + console.log( + "[ResetPassword] ✅ Token de recuperação detectado e armazenado" + ); console.log("[ResetPassword] Type:", type); } else { - console.error("[ResetPassword] ❌ Token não encontrado no hash nem no query string"); + console.error( + "[ResetPassword] ❌ Token não encontrado no hash nem no query string" + ); console.log("[ResetPassword] URL completa:", window.location.href); toast.error("Link de recuperação inválido ou expirado"); setTimeout(() => navigate("/"), 3000); @@ -106,24 +116,24 @@ const ResetPassword: React.FC = () => { } catch (error: unknown) { console.error("[ResetPassword] Erro ao atualizar senha:", error); const err = error as { - response?: { - data?: { - error_description?: string; + response?: { + data?: { + error_description?: string; message?: string; msg?: string; error_code?: string; - } + }; }; message?: string; }; - + // Mensagem específica para senha igual if (err?.response?.data?.error_code === "same_password") { toast.error("A nova senha deve ser diferente da senha atual"); setLoading(false); return; } - + const errorMessage = err?.response?.data?.msg || err?.response?.data?.error_description || diff --git a/MEDICONNECT 2/src/services/auth/authService.ts b/MEDICONNECT 2/src/services/auth/authService.ts index 5b7de7e4a..7b72af31b 100644 --- a/MEDICONNECT 2/src/services/auth/authService.ts +++ b/MEDICONNECT 2/src/services/auth/authService.ts @@ -154,7 +154,7 @@ class AuthService { /** * Solicita reset de senha via email (público) * POST /auth/v1/recover - * + * * Envia redirectTo dentro de options para controlar para onde o Supabase redireciona */ async requestPasswordReset( @@ -162,40 +162,50 @@ class AuthService { ): Promise<{ success: boolean; message: string }> { try { const redirectUrl = `${API_CONFIG.APP_URL}/reset-password`; - + console.log("[authService.requestPasswordReset] Email:", email); - console.log("[authService.requestPasswordReset] Redirect URL:", redirectUrl); - console.log("[authService.requestPasswordReset] Usando endpoint /auth/v1/recover"); - + console.log( + "[authService.requestPasswordReset] Redirect URL:", + redirectUrl + ); + console.log( + "[authService.requestPasswordReset] Usando endpoint /auth/v1/recover" + ); + const payload = { email, options: { redirectTo: redirectUrl, }, }; - - console.log("[authService.requestPasswordReset] Payload:", JSON.stringify(payload, null, 2)); - - await axios.post( - `${API_CONFIG.AUTH_URL}/recover`, - payload, - { - headers: { - "Content-Type": "application/json", - apikey: API_CONFIG.SUPABASE_ANON_KEY, - }, - } + + console.log( + "[authService.requestPasswordReset] Payload:", + JSON.stringify(payload, null, 2) ); - console.log("[authService.requestPasswordReset] ✅ Email de recuperação enviado"); + await axios.post(`${API_CONFIG.AUTH_URL}/recover`, payload, { + headers: { + "Content-Type": "application/json", + apikey: API_CONFIG.SUPABASE_ANON_KEY, + }, + }); + + console.log( + "[authService.requestPasswordReset] ✅ Email de recuperação enviado" + ); return { success: true, - message: "Email de recuperação de senha enviado com sucesso. Verifique sua caixa de entrada.", + message: + "Email de recuperação de senha enviado com sucesso. Verifique sua caixa de entrada.", }; } catch (error: any) { console.error("[authService.requestPasswordReset] ❌ Erro:", error); - console.error("[authService.requestPasswordReset] Response:", error.response?.data); + console.error( + "[authService.requestPasswordReset] Response:", + error.response?.data + ); throw error; } } @@ -209,10 +219,21 @@ class AuthService { newPassword: string ): Promise<{ success: boolean; message: string }> { try { - console.log("[authService.updatePassword] Iniciando atualização de senha"); - console.log("[authService.updatePassword] Token (primeiros 30 chars):", accessToken.substring(0, 30)); - console.log("[authService.updatePassword] Nova senha length:", newPassword.length); - console.log("[authService.updatePassword] URL:", `${API_CONFIG.AUTH_URL}/user`); + console.log( + "[authService.updatePassword] Iniciando atualização de senha" + ); + console.log( + "[authService.updatePassword] Token (primeiros 30 chars):", + accessToken.substring(0, 30) + ); + console.log( + "[authService.updatePassword] Nova senha length:", + newPassword.length + ); + console.log( + "[authService.updatePassword] URL:", + `${API_CONFIG.AUTH_URL}/user` + ); const response = await axios.put( `${API_CONFIG.AUTH_URL}/user`, @@ -228,35 +249,76 @@ class AuthService { } ); - console.log("[authService.updatePassword] ✅ Senha atualizada com sucesso"); - console.log("[authService.updatePassword] Response status:", response.status); + console.log( + "[authService.updatePassword] ✅ Senha atualizada com sucesso" + ); + console.log( + "[authService.updatePassword] Response status:", + response.status + ); return { success: true, message: "Senha atualizada com sucesso", }; } catch (error: any) { - console.error("[authService.updatePassword] ❌ Erro ao atualizar senha:", error); - console.error("[authService.updatePassword] Error response:", error.response?.data); - console.error("[authService.updatePassword] Error response (stringified):", JSON.stringify(error.response?.data, null, 2)); - console.error("[authService.updatePassword] Error status:", error.response?.status); - console.error("[authService.updatePassword] Error statusText:", error.response?.statusText); - console.error("[authService.updatePassword] Error headers:", error.response?.headers); - console.error("[authService.updatePassword] Request URL:", error.config?.url); - console.error("[authService.updatePassword] Request headers:", error.config?.headers); - console.error("[authService.updatePassword] Request data:", error.config?.data); - + console.error( + "[authService.updatePassword] ❌ Erro ao atualizar senha:", + error + ); + console.error( + "[authService.updatePassword] Error response:", + error.response?.data + ); + console.error( + "[authService.updatePassword] Error response (stringified):", + JSON.stringify(error.response?.data, null, 2) + ); + console.error( + "[authService.updatePassword] Error status:", + error.response?.status + ); + console.error( + "[authService.updatePassword] Error statusText:", + error.response?.statusText + ); + console.error( + "[authService.updatePassword] Error headers:", + error.response?.headers + ); + console.error( + "[authService.updatePassword] Request URL:", + error.config?.url + ); + console.error( + "[authService.updatePassword] Request headers:", + error.config?.headers + ); + console.error( + "[authService.updatePassword] Request data:", + error.config?.data + ); + // Se for 422, mostrar mensagem específica if (error.response?.status === 422) { - const errorMsg = error.response?.data?.msg || error.response?.data?.message || error.response?.data?.error_description || "Token inválido ou expirado"; - console.error("[authService.updatePassword] ⚠️ Erro 422 - Possíveis causas:"); + const errorMsg = + error.response?.data?.msg || + error.response?.data?.message || + error.response?.data?.error_description || + "Token inválido ou expirado"; + console.error( + "[authService.updatePassword] ⚠️ Erro 422 - Possíveis causas:" + ); console.error(" 1. Token de recuperação expirado (válido por 1 hora)"); console.error(" 2. Token já foi usado"); console.error(" 3. Formato do token incorreto"); console.error(" 4. Usuário não existe"); - console.error("[authService.updatePassword] Mensagem do servidor:", errorMsg); + console.error( + "[authService.updatePassword] Mensagem do servidor:", + errorMsg + ); } - + throw error; } } diff --git a/MEDICONNECT 2/src/services/avatars/avatarService.ts b/MEDICONNECT 2/src/services/avatars/avatarService.ts index ffbc08b3f..af4384e64 100644 --- a/MEDICONNECT 2/src/services/avatars/avatarService.ts +++ b/MEDICONNECT 2/src/services/avatars/avatarService.ts @@ -22,11 +22,11 @@ class AvatarService { async upload(data: UploadAvatarInput): Promise { try { const token = localStorage.getItem(API_CONFIG.STORAGE_KEYS.ACCESS_TOKEN); - + if (!token) { throw new Error("Token de autenticação não encontrado"); } - + // Determina a extensão do arquivo const ext = data.file.name.split(".").pop()?.toLowerCase() || "jpg"; const filePath = `${data.userId}/avatar.${ext}`; @@ -46,14 +46,14 @@ class AvatarService { // Upload usando Supabase Storage API // x-upsert: true permite sobrescrever arquivos existentes - // Importante: NÃO definir Content-Type manualmente, deixar o axios/navegador + // Importante: NÃO definir Content-Type manualmente, deixar o axios/navegador // definir automaticamente com o boundary correto para multipart/form-data const response = await axios.post( `${this.STORAGE_URL}/${this.BUCKET_NAME}/${filePath}`, formData, { headers: { - "Authorization": `Bearer ${token}`, + Authorization: `Bearer ${token}`, "x-upsert": "true", }, } @@ -91,7 +91,9 @@ class AvatarService { try { // Não há endpoint de delete, então apenas removemos a referência do perfil // O upload futuro irá sobrescrever a imagem antiga - console.log("Avatar será removido do perfil. Upload futuro sobrescreverá a imagem."); + console.log( + "Avatar será removido do perfil. Upload futuro sobrescreverá a imagem." + ); } catch (error) { console.error("Erro ao deletar avatar:", error); throw error;