From 2ed13635284cfdf695fce45a36f65e81fbdbd0cc Mon Sep 17 00:00:00 2001 From: Pedro Araujo da Silveira Date: Sun, 16 Nov 2025 20:27:33 -0300 Subject: [PATCH] fix: criar paciente e usuario painel secretaria e admin --- .../secretaria/SecretaryPatientList.tsx | 109 +++++++++++++-- src/pages/PainelAdmin.tsx | 126 +++++++++++++++--- src/services/patients/patientService.ts | 66 ++++++++- src/services/users/userService.ts | 54 +++++--- 4 files changed, 305 insertions(+), 50 deletions(-) diff --git a/src/components/secretaria/SecretaryPatientList.tsx b/src/components/secretaria/SecretaryPatientList.tsx index 81cc10524..dff79e20d 100644 --- a/src/components/secretaria/SecretaryPatientList.tsx +++ b/src/components/secretaria/SecretaryPatientList.tsx @@ -40,6 +40,28 @@ const buscarEnderecoViaCEP = async (cep: string) => { } }; +// Função para validar campos numéricos e evitar overflow +const validateNumericField = ( + value: number, + fieldName: string, + min: number, + max: number +): number | null => { + if (isNaN(value)) { + console.warn(`[SecretaryPatientList] Campo ${fieldName} tem valor NaN`); + return null; + } + + if (value < min || value > max) { + console.warn( + `[SecretaryPatientList] Campo ${fieldName} fora do intervalo [${min}, ${max}]: ${value}` + ); + return null; + } + + return value; +}; + export function SecretaryPatientList({ onOpenAppointment, }: { @@ -296,8 +318,8 @@ export function SecretaryPatientList({ email: formData.email, phone_mobile: cleanPhone, blood_type: formData.tipo_sanguineo || null, - height_m: formData.altura ? parseFloat(formData.altura) : null, - weight_kg: formData.peso ? parseFloat(formData.peso) : null, + height_m: formData.altura ? validateNumericField(parseFloat(formData.altura), "height_m", 0.5, 3.0) : null, + weight_kg: formData.peso ? validateNumericField(parseFloat(formData.peso), "weight_kg", 0, 500) : null, cep: cleanCep, street: formData.endereco.rua || null, number: formData.endereco.numero || null, @@ -317,17 +339,56 @@ export function SecretaryPatientList({ ? formData.endereco.cep.replace(/\D/g, "") : null; + // Validações básicas obrigatórias + if (!formData.nome.trim()) { + toast.error("Nome do paciente é obrigatório"); + setLoading(false); + return; + } + if (!cleanCpf) { + toast.error("CPF é obrigatório"); + setLoading(false); + return; + } + if (cleanCpf.length !== 11) { + toast.error("CPF deve ter 11 dígitos"); + setLoading(false); + return; + } + + // Verificar se CPF já existe (apenas para criação) + if (!formData.id) { + console.log("[SecretaryPatientList] Verificando se CPF já existe:", cleanCpf); + const cpfJaExiste = await patientService.cpfExists(cleanCpf); + if (cpfJaExiste) { + toast.error("Este CPF já está registrado no sistema"); + setLoading(false); + return; + } + } + + if (!formData.email.trim()) { + toast.error("Email é obrigatório"); + setLoading(false); + return; + } + if (!cleanPhone) { + toast.error("Telefone é obrigatório"); + setLoading(false); + return; + } + const createData = { - full_name: formData.nome, + full_name: formData.nome.trim(), cpf: cleanCpf, - email: formData.email, + email: formData.email.trim(), phone_mobile: cleanPhone, birth_date: formData.dataNascimento || null, - social_name: formData.social_name || null, + social_name: formData.social_name?.trim() || null, sex: formData.sexo || null, blood_type: formData.tipo_sanguineo || null, - weight_kg: formData.peso ? parseFloat(formData.peso) : null, - height_m: formData.altura ? parseFloat(formData.altura) : null, + weight_kg: formData.peso ? validateNumericField(parseFloat(formData.peso), "weight_kg", 0, 500) : null, + height_m: formData.altura ? validateNumericField(parseFloat(formData.altura), "height_m", 0.5, 3.0) : null, street: formData.endereco.rua || null, number: formData.endereco.numero || null, complement: formData.endereco.complemento || null, @@ -338,15 +399,45 @@ export function SecretaryPatientList({ created_by: user?.id || undefined, }; + console.log("[SecretaryPatientList] Enviando dados:", createData); await patientService.create(createData); toast.success("Paciente cadastrado com sucesso!"); } setShowModal(false); loadPatients(); - } catch (error) { + } catch (error: any) { console.error("Erro ao salvar paciente:", error); - toast.error("Erro ao salvar paciente"); + + // Extrair mensagem de erro mais útil + let errorMessage = "Erro ao salvar paciente"; + + // Verificar se é erro de duplicação de CPF + const errorMsg = error?.message || + error?.response?.data?.message || + error?.response?.data?.error || + ""; + + if (errorMsg.includes("duplicate key") || errorMsg.includes("patients_cpf_key")) { + errorMessage = "Este CPF já está registrado no sistema"; + } else if (errorMsg.includes("email")) { + errorMessage = "Este email já está registrado no sistema"; + } else if (error?.message) { + errorMessage = error.message; + } else if (error?.response?.data?.message) { + errorMessage = error.response.data.message; + } else if (error?.response?.data?.error) { + errorMessage = error.response.data.error; + } else if (typeof error === "string") { + errorMessage = error; + } + + console.error("[SecretaryPatientList] Detalhes do erro:", { + errorMessage, + fullError: error, + }); + + toast.error(errorMessage); } finally { setLoading(false); } diff --git a/src/pages/PainelAdmin.tsx b/src/pages/PainelAdmin.tsx index a21bfa88d..900e918c8 100644 --- a/src/pages/PainelAdmin.tsx +++ b/src/pages/PainelAdmin.tsx @@ -516,6 +516,17 @@ const PainelAdmin: React.FC = () => { return; } + // Verificar se CPF já existe (apenas para criação nova) + if (!editingPaciente) { + console.log("[PainelAdmin] Verificando se CPF já existe:", cpfLimpo); + const cpfJaExiste = await patientService.cpfExists(cpfLimpo); + if (cpfJaExiste) { + toast.error("Este CPF já está registrado no sistema"); + setLoading(false); + return; + } + } + // Limpar telefone (remover formatação) const phoneLimpo = formPaciente.phone_mobile.replace(/\D/g, ""); @@ -552,20 +563,30 @@ const PainelAdmin: React.FC = () => { loadPacientes(); } else { // API create-patient já cria auth user + registro na tabela patients + // Enviar apenas os campos obrigatórios/esperados + + if (!user?.id) { + toast.error("Erro: ID do usuário não disponível"); + setLoading(false); + return; + } + console.log("[PainelAdmin] Criando paciente com API /create-patient:", { email: patientData.email, full_name: patientData.full_name, cpf: cpfLimpo, - phone_mobile: patientData.phone_mobile, + phone_mobile: phoneLimpo, + birth_date: patientData.birth_date, + created_by: user.id, }); await userService.createPatient({ email: patientData.email, full_name: patientData.full_name, cpf: cpfLimpo, - phone_mobile: patientData.phone_mobile, - birth_date: patientData.birth_date, - created_by: user?.id || "", // ID do admin/secretaria que está criando + phone_mobile: phoneLimpo, + birth_date: patientData.birth_date || undefined, + created_by: user.id, // ID do admin/secretaria que está criando }); toast.success( @@ -577,23 +598,58 @@ const PainelAdmin: React.FC = () => { } } catch (error: unknown) { console.error("Erro ao salvar paciente:", error); + const axiosError = error as { response?: { - data?: { message?: string; error?: string }; + data?: { message?: string; error?: string; detail?: string }; status?: number; + statusText?: string; }; message?: string; }; - const errorMessage = - axiosError?.response?.data?.message || - axiosError?.response?.data?.error || - axiosError?.message || - "Erro ao salvar paciente"; + + let errorMessage = "Erro ao salvar paciente"; + + // Tentar extrair mensagem de erro mais detalhada + if (axiosError?.response?.status === 500) { + // Erro do servidor - pode conter detalhes úteis + const serverMessage = + axiosError?.response?.data?.message || + axiosError?.response?.data?.detail || + axiosError?.response?.data?.error || + ""; + + // Verificar se é erro de chave duplicada + if (serverMessage.includes("duplicate key") || serverMessage.includes("patients_cpf_key")) { + errorMessage = "Este CPF já está registrado no sistema"; + } else if (serverMessage.includes("email")) { + errorMessage = "Este email já está registrado no sistema"; + } else { + errorMessage = serverMessage || "Erro do servidor (500) - verifique console para mais detalhes"; + } + } else if (axiosError?.response?.status === 400) { + errorMessage = + axiosError?.response?.data?.message || + axiosError?.response?.data?.error || + "Dados inválidos"; + } else if (axiosError?.response?.status === 409) { + errorMessage = "Paciente com este CPF ou email já existe"; + } else if (axiosError?.response?.status === 401) { + errorMessage = "Não autorizado. Verifique sua sessão"; + } else if (axiosError?.response?.status === 403) { + errorMessage = "Você não tem permissão para criar pacientes"; + } else if (axiosError?.message) { + errorMessage = axiosError.message; + } + toast.error(`Erro: ${errorMessage}`); if (axiosError?.response) { - console.error("Status:", axiosError.response.status); - console.error("Data:", axiosError.response.data); + console.error("[PainelAdmin] Detalhes do erro:", { + status: axiosError.response.status, + statusText: axiosError.response.statusText, + data: axiosError.response.data, + }); } } finally { setLoading(false); @@ -719,6 +775,8 @@ const PainelAdmin: React.FC = () => { cpf: cpfLimpo, crm: medicoData.crm, crm_uf: medicoData.crm_uf, + specialty: medicoData.specialty, + phone_mobile: phoneLimpo, }); await userService.createDoctor({ @@ -740,26 +798,50 @@ const PainelAdmin: React.FC = () => { } } catch (error: unknown) { console.error("Erro ao salvar médico:", error); + const axiosError = error as { response?: { - data?: { message?: string; error?: string }; + data?: { message?: string; error?: string; detail?: string }; status?: number; - headers?: unknown; + statusText?: string; }; message?: string; }; - const errorMessage = - axiosError?.response?.data?.message || - axiosError?.response?.data?.error || - axiosError?.message || - "Erro ao salvar médico"; + + let errorMessage = "Erro ao salvar médico"; + + // Tentar extrair mensagem de erro mais detalhada + if (axiosError?.response?.status === 500) { + // Erro do servidor - pode conter detalhes úteis + errorMessage = + axiosError?.response?.data?.message || + axiosError?.response?.data?.detail || + axiosError?.response?.data?.error || + "Erro do servidor (500) - verifique console para mais detalhes"; + } else if (axiosError?.response?.status === 400) { + errorMessage = + axiosError?.response?.data?.message || + axiosError?.response?.data?.error || + "Dados inválidos"; + } else if (axiosError?.response?.status === 409) { + errorMessage = "Médico com este CPF, email ou CRM já existe"; + } else if (axiosError?.response?.status === 401) { + errorMessage = "Não autorizado. Verifique sua sessão"; + } else if (axiosError?.response?.status === 403) { + errorMessage = "Você não tem permissão para criar médicos"; + } else if (axiosError?.message) { + errorMessage = axiosError.message; + } + toast.error(`Erro: ${errorMessage}`); // Log detalhado para debug if (axiosError?.response) { - console.error("Status:", axiosError.response.status); - console.error("Data:", axiosError.response.data); - console.error("Headers:", axiosError.response.headers); + console.error("[PainelAdmin] Detalhes do erro:", { + status: axiosError.response.status, + statusText: axiosError.response.statusText, + data: axiosError.response.data, + }); } } finally { setLoading(false); diff --git a/src/services/patients/patientService.ts b/src/services/patients/patientService.ts index 8f2b21a9e..d6c9bd509 100644 --- a/src/services/patients/patientService.ts +++ b/src/services/patients/patientService.ts @@ -104,11 +104,34 @@ class PatientService { */ async create(data: CreatePatientInput): Promise { try { + console.log("[patientService] Criando paciente com dados:", data); const response = await apiClient.post("/patients", data); + console.log("[patientService] Paciente criado com sucesso:", response.data); return response.data; - } catch (error) { - console.error("Erro ao criar paciente:", error); - throw error; + } catch (error: any) { + console.error("Erro ao criar paciente:", { + message: error.message, + status: error?.response?.status, + statusText: error?.response?.statusText, + data: error?.response?.data, + originalError: error, + }); + + // Melhorar mensagem de erro + let userMessage = "Erro ao criar paciente"; + if (error?.response?.status === 400) { + userMessage = error?.response?.data?.message || "Dados inválidos"; + } else if (error?.response?.status === 409) { + userMessage = "Paciente com este CPF ou email já existe"; + } else if (error?.response?.status === 401) { + userMessage = "Não autorizado. Verifique sua sessão"; + } else if (error?.response?.status === 403) { + userMessage = "Você não tem permissão para criar pacientes"; + } + + const customError = new Error(userMessage); + (customError as any).originalError = error; + throw customError; } } @@ -174,6 +197,43 @@ class PatientService { * Registro público de paciente (não requer autenticação) * Usa o endpoint register-patient que tem validações rigorosas */ + /** + * Verifica se um CPF já está registrado + */ + async cpfExists(cpf: string): Promise { + try { + const cleanCpf = cpf.replace(/\D/g, ""); + const response = await apiClient.get( + `/patients?cpf=eq.${cleanCpf}` + ); + return response.data && response.data.length > 0; + } catch (error: any) { + // Se 401, silenciar (não autenticado é ok para essa verificação) + if (error?.response?.status !== 401) { + console.error("[patientService] Erro ao verificar CPF:", error); + } + // Em caso de erro, retornar false (deixar o servidor validar) + return false; + } + } + + /** + * Verifica se um email já está registrado + */ + async emailExists(email: string): Promise { + try { + const response = await apiClient.get( + `/patients?email=ilike.${email}` + ); + return response.data && response.data.length > 0; + } catch (error: any) { + if (error?.response?.status !== 401) { + console.error("[patientService] Erro ao verificar email:", error); + } + return false; + } + } + async register(data: RegisterPatientInput): Promise { try { console.log("[patientService.register] Enviando dados:", data); diff --git a/src/services/users/userService.ts b/src/services/users/userService.ts index 2a1aec714..6dbac1784 100644 --- a/src/services/users/userService.ts +++ b/src/services/users/userService.ts @@ -172,14 +172,25 @@ class UserService { console.log("[userService.createDoctor] URL:", url); console.log("[userService.createDoctor] Data:", data); - const response = await axios.post(url, data, { - headers: { - "Content-Type": "application/json", - apikey: API_CONFIG.SUPABASE_ANON_KEY, - Authorization: `Bearer ${token}`, - }, - }); - return response.data; + try { + const response = await axios.post(url, data, { + headers: { + "Content-Type": "application/json", + apikey: API_CONFIG.SUPABASE_ANON_KEY, + Authorization: `Bearer ${token}`, + }, + }); + console.log("[userService.createDoctor] Sucesso:", response.data); + return response.data; + } catch (error: any) { + console.error("[userService.createDoctor] Erro completo:", { + message: error.message, + status: error?.response?.status, + statusText: error?.response?.statusText, + data: error?.response?.data, + }); + throw error; + } } /** @@ -198,14 +209,25 @@ class UserService { console.log("[userService.createPatient] URL:", url); console.log("[userService.createPatient] Data:", data); - const response = await axios.post(url, data, { - headers: { - "Content-Type": "application/json", - apikey: API_CONFIG.SUPABASE_ANON_KEY, - Authorization: `Bearer ${token}`, - }, - }); - return response.data; + try { + const response = await axios.post(url, data, { + headers: { + "Content-Type": "application/json", + apikey: API_CONFIG.SUPABASE_ANON_KEY, + Authorization: `Bearer ${token}`, + }, + }); + console.log("[userService.createPatient] Sucesso:", response.data); + return response.data; + } catch (error: any) { + console.error("[userService.createPatient] Erro completo:", { + message: error.message, + status: error?.response?.status, + statusText: error?.response?.statusText, + data: error?.response?.data, + }); + throw error; + } } /**