// lib/api.ts import { ENV_CONFIG } from "@/lib/env-config"; import { API_KEY } from "@/lib/config"; export type ApiOk = { success?: boolean; data: T; message?: string; pagination?: { current_page?: number; per_page?: number; total_pages?: number; total?: number; }; }; // ===== TIPOS COMUNS ===== export type Endereco = { cep?: string; logradouro?: string; numero?: string; complemento?: string; bairro?: string; cidade?: string; estado?: string; }; // ===== PACIENTES ===== export type Paciente = { id: string; full_name: string; social_name?: string | null; cpf?: string; rg?: string | null; sex?: string | null; birth_date?: string | null; phone_mobile?: string; email?: string; cep?: string | null; street?: string | null; number?: string | null; complement?: string | null; neighborhood?: string | null; city?: string | null; state?: string | null; notes?: string | null; user_id?: string | null; }; export type PacienteInput = { full_name: string; social_name?: string | null; cpf: string; rg?: string | null; sex?: string | null; birth_date?: string | null; phone_mobile?: string | null; email?: string | null; cep?: string | null; street?: string | null; number?: string | null; complement?: string | null; neighborhood?: string | null; city?: string | null; state?: string | null; notes?: string | null; /** ID do usuário de Auth vinculado */ user_id?: string | null; }; // ===== MÉDICOS ===== export type FormacaoAcademica = { instituicao: string; curso: string; ano_conclusao: string; }; export type DadosBancarios = { banco: string; agencia: string; conta: string; tipo_conta: string; }; // ===== MÉDICOS ===== export type Medico = { id: string; full_name: string; // Altere 'nome' para 'full_name' nome_social?: string | null; cpf?: string; rg?: string | null; sexo?: string | null; data_nascimento?: string | null; telefone?: string; celular?: string; contato_emergencia?: string; email?: string; crm?: string; estado_crm?: string; rqe?: string; formacao_academica?: FormacaoAcademica[]; curriculo_url?: string | null; especialidade?: string; observacoes?: string | null; foto_url?: string | null; tipo_vinculo?: string; dados_bancarios?: DadosBancarios; agenda_horario?: string; valor_consulta?: number | string; active?: boolean; cep?: string; city?: string; complement?: string; neighborhood?: string; number?: string; phone2?: string; state?: string; street?: string; created_at?: string; created_by?: string; updated_at?: string; updated_by?: string; user_id?: string; }; // ===== MÉDICOS ===== // ...existing code... export type MedicoInput = { user_id?: string | null; crm: string; crm_uf: string; specialty: string; full_name: string; cpf: string; email: string; phone_mobile: string; phone2?: string | null; cep: string; street: string; number: string; complement?: string; neighborhood?: string; city: string; state: string; birth_date: string | null; rg?: string | null; active?: boolean; created_by?: string | null; updated_by?: string | null; }; // ===== CONFIG ===== const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "https://yuanqfswhberkoevtmfr.supabase.co"; const REST = `${API_BASE}/rest/v1`; // Token salvo no browser (aceita auth_token ou token) function getAuthToken(): string | null { if (typeof window === "undefined") return null; return ( localStorage.getItem("auth_token") || localStorage.getItem("token") || sessionStorage.getItem("auth_token") || sessionStorage.getItem("token") ); } // Cabeçalhos base function baseHeaders(): Record { const h: Record = { apikey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ", Accept: "application/json", }; const jwt = getAuthToken(); if (jwt) h.Authorization = `Bearer ${jwt}`; return h; } // Para POST/PATCH/DELETE e para GET com count function withPrefer(h: Record, prefer: string) { return { ...h, Prefer: prefer }; } // Parse genérico async function parse(res: Response): Promise { let json: any = null; try { json = await res.json(); } catch (error) { console.error("Erro ao parsear a resposta:", error); } if (!res.ok) { console.error("[API ERROR]", res.url, res.status, json); const code = (json && (json.error?.code || json.code)) ?? res.status; const message = (json && (json.error?.message || json.message)) ?? res.statusText; // Mensagens amigáveis para erros comuns let friendlyMessage = `${code}: ${message}`; // Erro de CPF duplicado if (code === "23505" && message.includes("patients_cpf_key")) { friendlyMessage = "Já existe um paciente cadastrado com este CPF. Por favor, verifique se o paciente já está registrado no sistema ou use um CPF diferente."; } // Erro de email duplicado (paciente) else if (code === "23505" && message.includes("patients_email_key")) { friendlyMessage = "Já existe um paciente cadastrado com este email. Por favor, use um email diferente."; } // Erro de CRM duplicado (médico) else if (code === "23505" && message.includes("doctors_crm")) { friendlyMessage = "Já existe um médico cadastrado com este CRM. Por favor, verifique se o médico já está registrado no sistema."; } // Erro de email duplicado (médico) else if (code === "23505" && message.includes("doctors_email_key")) { friendlyMessage = "Já existe um médico cadastrado com este email. Por favor, use um email diferente."; } // Erro de integridade referencial ao excluir paciente else if (code === "23503" && message.includes("reports_patient_id_fkey")) { friendlyMessage = "Não é possível excluir este paciente pois há relatórios vinculados a ele."; } // Outros erros de constraint unique else if (code === "23505") { friendlyMessage = "Registro duplicado: já existe um cadastro com essas informações no sistema."; } throw new Error(friendlyMessage); } return (json?.data ?? json) as T; } // Helper de paginação (Range/Range-Unit) function rangeHeaders(page?: number, limit?: number): Record { if (!page || !limit) return {}; const start = (page - 1) * limit; const end = start + limit - 1; return { Range: `${start}-${end}`, "Range-Unit": "items" }; } // ===== PACIENTES (CRUD) ===== export async function listarPacientes(parameters?: { page?: number; limit?: number; q?: string; }): Promise { const qs = new URLSearchParams(); if (parameters?.q) qs.set("q", parameters.q); const url = `${REST}/patients${qs.toString() ? `?${qs.toString()}` : ""}`; const res = await fetch(url, { method: "GET", headers: { ...baseHeaders(), ...rangeHeaders(parameters?.page, parameters?.limit), }, }); return await parse(res); } // Nova função para busca avançada de pacientes export async function buscarPacientes(termo: string): Promise { if (!termo || termo.trim().length < 2) { return []; } const searchTerm = termo.toLowerCase().trim(); const digitsOnly = searchTerm.replace(/\D/g, ""); // Monta queries para buscar em múltiplos campos const queries = []; // Busca por ID se parece com UUID if (searchTerm.includes("-") && searchTerm.length > 10) { queries.push(`id=eq.${searchTerm}`); } // Busca por CPF (com e sem formatação) if (digitsOnly.length >= 11) { queries.push(`cpf=eq.${digitsOnly}`); } else if (digitsOnly.length >= 3) { queries.push(`cpf=ilike.*${digitsOnly}*`); } // Busca por nome (usando ilike para busca case-insensitive) if (searchTerm.length >= 2) { queries.push(`full_name=ilike.*${searchTerm}*`); queries.push(`social_name=ilike.*${searchTerm}*`); } // Busca por email se contém @ if (searchTerm.includes("@")) { queries.push(`email=ilike.*${searchTerm}*`); } const results: Paciente[] = []; const seenIds = new Set(); // Executa as buscas e combina resultados únicos for (const query of queries) { try { const url = `${REST}/patients?${query}&limit=10`; const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const array = await parse(res); if (array?.length > 0) { for (const paciente of array) { if (!seenIds.has(paciente.id)) { seenIds.add(paciente.id); results.push(paciente); } } } } catch (error) { console.warn(`Erro na busca com query: ${query}`, error); } } return results.slice(0, 20); // Limita a 20 resultados } export async function buscarPacientePorId( id: string | number, ): Promise { const url = `${REST}/patients?id=eq.${id}`; const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const array = await parse(res); if (!array?.length) throw new Error("404: Paciente não encontrado"); return array[0]; } export async function criarPaciente(input: PacienteInput): Promise { const url = `${REST}/patients`; const res = await fetch(url, { method: "POST", headers: withPrefer( { ...baseHeaders(), "Content-Type": "application/json" }, "return=representation", ), body: JSON.stringify(input), }); const array = await parse(res); return Array.isArray(array) ? array[0] : (array as Paciente); } // Atualização parcial de paciente (PATCH) permite enviar apenas campos específicos, como user_id export async function atualizarPaciente( id: string | number, input: Partial & { user_id?: string | null }, ): Promise { const url = `${REST}/patients?id=eq.${id}`; const res = await fetch(url, { method: "PATCH", headers: withPrefer( { ...baseHeaders(), "Content-Type": "application/json" }, "return=representation", ), body: JSON.stringify(input), }); const array = await parse(res); return Array.isArray(array) ? array[0] : (array as Paciente); } export async function excluirPaciente(id: string | number): Promise { const url = `${REST}/patients?id=eq.${id}`; const res = await fetch(url, { method: "DELETE", headers: baseHeaders() }); await parse(res); } // ===== PACIENTES (Extra: verificação de CPF duplicado) ===== export async function verificarCpfDuplicado(cpf: string): Promise { const clean = (cpf || "").replace(/\D/g, ""); const url = `${API_BASE}/rest/v1/patients?cpf=eq.${clean}&select=id`; const res = await fetch(url, { method: "GET", headers: baseHeaders(), }); const data = await res.json().catch(() => []); return Array.isArray(data) && data.length > 0; } // ===== MÉDICOS (CRUD) ===== export async function listarMedicos(parameters?: { page?: number; limit?: number; q?: string; }): Promise { const qs = new URLSearchParams(); if (parameters?.q) qs.set("q", parameters.q); const url = `${REST}/doctors${qs.toString() ? `?${qs.toString()}` : ""}`; const res = await fetch(url, { method: "GET", headers: { ...baseHeaders(), ...rangeHeaders(parameters?.page, parameters?.limit), }, }); return await parse(res); } // Nova função para busca avançada de médicos export async function buscarMedicos(termo: string): Promise { if (!termo || termo.trim().length < 2) { return []; } const searchTerm = termo.toLowerCase().trim(); const digitsOnly = searchTerm.replace(/\D/g, ""); // Monta queries para buscar em múltiplos campos const queries = []; // Busca por ID se parece com UUID if (searchTerm.includes("-") && searchTerm.length > 10) { queries.push(`id=eq.${searchTerm}`); } // Busca por CRM (com e sem formatação) if (digitsOnly.length >= 3) { queries.push(`crm=ilike.*${digitsOnly}*`); } // Busca por nome (usando ilike para busca case-insensitive) if (searchTerm.length >= 2) { queries.push(`full_name=ilike.*${searchTerm}*`); queries.push(`nome_social=ilike.*${searchTerm}*`); } // Busca por email se contém @ if (searchTerm.includes("@")) { queries.push(`email=ilike.*${searchTerm}*`); } // Busca por especialidade if (searchTerm.length >= 2) { queries.push(`specialty=ilike.*${searchTerm}*`); } const results: Medico[] = []; const seenIds = new Set(); // Executa as buscas e combina resultados únicos for (const query of queries) { try { const url = `${REST}/doctors?${query}&limit=10`; const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const array = await parse(res); if (array?.length > 0) { for (const medico of array) { if (!seenIds.has(medico.id)) { seenIds.add(medico.id); results.push(medico); } } } } catch (error) { console.warn(`Erro na busca com query: ${query}`, error); } } return results.slice(0, 20); // Limita a 20 resultados } export async function buscarMedicoPorId(id: string | number): Promise { // Primeiro tenta buscar no Supabase (dados reais) try { const url = `${REST}/doctors?id=eq.${id}`; const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const array = await parse(res); if (array && array.length > 0) { console.log("Médico encontrado no Supabase:", array[0]); console.log("Campo especialidade no médico:", { especialidade: array[0].especialidade, specialty: (array[0] as any).specialty, hasEspecialidade: !!array[0].especialidade, hasSpecialty: !!(array[0] as any).specialty, }); return array[0]; } } catch (error) { console.warn("Erro ao buscar no Supabase, tentando mock API:", error); } // Se não encontrar no Supabase, tenta o mock API try { const url = `https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctors/${id}`; const res = await fetch(url, { method: "GET", headers: { Accept: "application/json", }, }); if (!res.ok) { if (res.status === 404) { throw new Error("404: Médico não encontrado"); } throw new Error(`Erro ao buscar médico: ${res.status} ${res.statusText}`); } const medico = await res.json(); console.log("Médico encontrado no Mock API:", medico); return medico as Medico; } catch (error) { console.error("Erro ao buscar médico em ambas as APIs:", error); throw new Error("404: Médico não encontrado"); } } // Dentro de lib/api.ts export async function criarMedico(input: MedicoInput): Promise { console.log("Enviando os dados para a API:", input); // Log para depuração const url = `${REST}/doctors`; // Endpoint de médicos const res = await fetch(url, { method: "POST", headers: withPrefer( { ...baseHeaders(), "Content-Type": "application/json" }, "return=representation", ), body: JSON.stringify(input), // Enviando os dados padronizados }); const array = await parse(res); // Resposta da API return Array.isArray(array) ? array[0] : (array as Medico); // Retorno do médico } export async function atualizarMedico( id: string | number, input: MedicoInput, ): Promise { console.log(` Tentando atualizar médico ID: ${id}`); console.log(` Payload original:`, input); // Criar um payload limpo apenas com campos básicos que sabemos que existem const cleanPayload = { full_name: input.full_name, crm: input.crm, specialty: input.specialty, email: input.email, phone_mobile: input.phone_mobile, cpf: input.cpf, cep: input.cep, street: input.street, number: input.number, city: input.city, state: input.state, active: input.active ?? true, }; console.log(` Payload limpo:`, cleanPayload); // Atualizar apenas no Supabase (dados reais) try { const url = `${REST}/doctors?id=eq.${id}`; console.log(` URL de atualização: ${url}`); const res = await fetch(url, { method: "PATCH", headers: withPrefer( { ...baseHeaders(), "Content-Type": "application/json" }, "return=representation", ), body: JSON.stringify(cleanPayload), }); console.log(` Resposta do servidor: ${res.status} ${res.statusText}`); if (res.ok) { const array = await parse(res); const result = Array.isArray(array) ? array[0] : (array as Medico); console.log(" Médico atualizado no Supabase:", result); return result; } else { // Vamos tentar ver o erro detalhado const errorText = await res.text(); console.error(` Erro detalhado do Supabase:`, { status: res.status, statusText: res.statusText, response: errorText, headers: Object.fromEntries(res.headers.entries()), }); throw new Error( `Supabase error: ${res.status} ${res.statusText} - ${errorText}`, ); } } catch (error) { console.error(" Erro ao atualizar médico:", error); throw error; } } export async function excluirMedico(id: string | number): Promise { const url = `${REST}/doctors?id=eq.${id}`; const res = await fetch(url, { method: "DELETE", headers: baseHeaders() }); await parse(res); } // ===== USUÁRIOS ===== export type AuthorizationRole = "paciente" | "medico"; // IMPORTANTE: O ENUM app_role no Supabase: // - user_roles: aceita "medico" (confirmado funcionando) // - patient_assignments: provavelmente aceita "user" para pacientes (baseado na API /functions/v1/create-user) // Valores aceitos pelo ENUM: admin | gestor | medico | secretaria | user type ServerAuthorizationRole = "medico" | "user"; const AUTHORIZATION_ROLE_TO_SERVER: Record< AuthorizationRole, ServerAuthorizationRole > = { paciente: "user", // Mapeia "paciente" interno para "user" no servidor medico: "medico", }; const SERVER_ROLE_TO_AUTHORIZATION: Record< ServerAuthorizationRole, AuthorizationRole > = { user: "paciente", // Mapeia "user" do servidor para "paciente" interno medico: "medico", }; export type UserRole = { id: string; user_id: string; role: string; created_at: string; }; export async function listarUserRoles(): Promise { const url = `${REST}/user_roles`; const res = await fetch(url, { method: "GET", headers: baseHeaders(), }); return await parse(res); } /** * Lista roles globais do usuário (tabela user_roles) e mapeia para AuthorizationRole */ export async function listarUserRolesDoUsuario( userId: string, ): Promise { const url = `${REST}/user_roles?user_id=eq.${userId}`; const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const rows = await parse>(res); if (!Array.isArray(rows) || rows.length === 0) return []; // O banco usa "medico" e "paciente" diretamente return rows .map((r) => r.role as AuthorizationRole) .filter((v, i, a) => a.indexOf(v) === i); } /** * Atualiza roles globais do usuário (user_roles): remove antigas e cria novas. */ export async function atualizarUserRoles( userId: string, roles: AuthorizationRole[], ): Promise { // Remove roles antigas const deleteUrl = `${REST}/user_roles?user_id=eq.${userId}`; const deleteRes = await fetch(deleteUrl, { method: "DELETE", headers: baseHeaders(), }); if (!deleteRes.ok) { throw new Error("Não foi possível remover roles anteriores (user_roles)"); } if (roles.length === 0) return []; // Mapeia para nome de role no servidor const payload = roles.map((r) => ({ user_id: userId, role: AUTHORIZATION_ROLE_TO_SERVER[r], })); console.log("[atualizarUserRoles] Payload:", payload); const res = await fetch(`${REST}/user_roles`, { method: "POST", headers: withPrefer( { ...baseHeaders(), "Content-Type": "application/json" }, "return=representation", ), body: JSON.stringify(payload), }); const data = await parse>(res); if (!Array.isArray(data)) return []; // O banco retorna "medico" e "paciente" diretamente return data .map((r) => r.role as AuthorizationRole) .filter((v, i, a) => a.indexOf(v) === i); } function buildRoleFilterParameter(roles: ServerAuthorizationRole[]): string { const encoded = roles .map((role) => encodeURIComponent(`"${role}"`)) .join(","); return `role=in.(${encoded})`; } /** * Lista atribuições de usuário a pacientes */ export async function listarAutorizacoesUsuario( userId: string, ): Promise { console.log("[listarAutorizacoesUsuario] Buscando roles para user_id:", userId); const url = `${REST}/patient_assignments?user_id=eq.${userId}`; const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const rows = await parse>(res); console.log("[listarAutorizacoesUsuario] Rows retornados:", rows); if (!Array.isArray(rows) || rows.length === 0) { // SEM atribuições = SEM ACESSO ao sistema! // Usuário precisa ser autorizado por um admin antes de fazer login console.warn("[listarAutorizacoesUsuario] Usuário sem atribuições - sem acesso!"); return []; } const roles = rows .map((r) => { // Mapear role do servidor para role interna const serverRole = r.role as ServerAuthorizationRole; return SERVER_ROLE_TO_AUTHORIZATION[serverRole] || null; }) .filter((role): role is AuthorizationRole => role !== null) .filter((role, index, self) => self.indexOf(role) === index); console.log("[listarAutorizacoesUsuario] Roles finais:", roles); return roles; } /** * Verifica se um usuário tem permissão para fazer login como um determinado tipo */ export async function verificarPermissaoLogin( userId: string, tipoLogin: "paciente" | "profissional" | "administrador", ): Promise { console.log("[verificarPermissaoLogin] Verificando:", { userId, tipoLogin }); // Admin sempre pode (não usa patient_assignments) if (tipoLogin === "administrador") { console.log("[verificarPermissaoLogin] Login admin: PERMITIDO (sem validação de roles)"); return true; } // Buscar roles do usuário em ambas as tabelas: // - patient_assignments (para pacientes) // - user_roles (para médicos e outros profissionais) const rolesPatientAssignments = await listarAutorizacoesUsuario(userId); const rolesUserRoles = await listarUserRolesDoUsuario(userId); // Combinar roles de ambas as tabelas const allRoles = [...new Set([...rolesPatientAssignments, ...rolesUserRoles])]; console.log("[verificarPermissaoLogin] Roles em patient_assignments:", rolesPatientAssignments); console.log("[verificarPermissaoLogin] Roles em user_roles:", rolesUserRoles); console.log("[verificarPermissaoLogin] Roles combinadas:", allRoles); // Se não tem nenhuma role em nenhuma tabela, bloqueia acesso if (allRoles.length === 0) { console.warn( "[verificarPermissaoLogin] BLOQUEADO - Usuário sem atribuições", ); return false; } // Mapear tipo de login para role necessária if (tipoLogin === "profissional") { const temPermissao = allRoles.includes("medico"); console.log( "[verificarPermissaoLogin] Login médico:", temPermissao ? "PERMITIDO" : "BLOQUEADO", ); return temPermissao; } if (tipoLogin === "paciente") { const temPermissao = allRoles.includes("paciente"); console.log( "[verificarPermissaoLogin] Login paciente:", temPermissao ? "PERMITIDO" : "BLOQUEADO", ); return temPermissao; } return false; } /** * Atualiza atribuições de usuário: remove as antigas e cria novas */ export async function atualizarAutorizacoesUsuario( userId: string, patientId: string, roles: AuthorizationRole[], ): Promise { // Remove atribuições antigas const deleteUrl = `${REST}/patient_assignments?user_id=eq.${userId}`; const deleteRes = await fetch(deleteUrl, { method: "DELETE", headers: baseHeaders(), }); if (!deleteRes.ok) { throw new Error("Não foi possível remover atribuições anteriores"); } // Se não há novas roles, retorna vazio if (roles.length === 0) { return []; } // Cria novas atribuições (IMPORTANTE: patient_id é obrigatório!) // Mapear roles para os valores aceitos pelo servidor const payload = roles.map((role) => ({ user_id: userId, patient_id: patientId, role: AUTHORIZATION_ROLE_TO_SERVER[role], })); console.log("[atualizarAutorizacoesUsuario] Payload:", payload); const res = await fetch(`${REST}/patient_assignments`, { method: "POST", headers: withPrefer( { ...baseHeaders(), "Content-Type": "application/json" }, "return=representation", ), body: JSON.stringify(payload), }); const data = await parse>(res); if (!Array.isArray(data)) return []; return data .map((r) => { // Mapear role do servidor para role interna const serverRole = r.role as ServerAuthorizationRole; return SERVER_ROLE_TO_AUTHORIZATION[serverRole] || null; }) .filter((role): role is AuthorizationRole => role !== null) .filter((v, index, a) => a.indexOf(v) === index); } export type User = { id: string; email: string; email_confirmed_at: string; created_at: string; last_sign_in_at: string; }; export type CurrentUser = { id: string; email: string; email_confirmed_at: string; created_at: string; last_sign_in_at: string; }; export type Profile = { id: string; full_name: string; email: string; phone: string; avatar_url: string; disabled: boolean; created_at: string; updated_at: string; }; export type ProfileInput = Partial< Omit >; export type Permissions = { isAdmin: boolean; isManager: boolean; isDoctor: boolean; isSecretary: boolean; isAdminOrManager: boolean; }; export type UserInfo = { user: User; profile: Profile; roles: string[]; permissions: Permissions; }; export async function getCurrentUser(): Promise { const url = `${API_BASE}/auth/v1/user`; const res = await fetch(url, { method: "GET", headers: baseHeaders(), }); return await parse(res); } export async function getUserInfo(): Promise { const url = `${API_BASE}/functions/v1/user-info`; const res = await fetch(url, { method: "GET", headers: baseHeaders(), }); return await parse(res); } /** * Busca um usuário pelo email no sistema de autenticação * Retorna o user_id se encontrar, ou null se não encontrar */ export async function buscarUsuarioPorEmail(email: string): Promise { try { // Buscar na tabela de perfis (profiles) que é vinculada ao auth const url = `${REST}/profiles?email=eq.${encodeURIComponent(email)}`; const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const profiles = await parse(res); if (profiles && profiles.length > 0) { console.log("[buscarUsuarioPorEmail] Usuário encontrado:", profiles[0].id); return profiles[0].id; // O ID do profile é o mesmo do user no auth } console.log("[buscarUsuarioPorEmail] Usuário não encontrado para email:", email); return null; } catch (error) { console.error("[buscarUsuarioPorEmail] Erro ao buscar usuário:", error); return null; } } export type CreateUserInput = { email: string; full_name: string; phone: string; role: string; password?: string; }; export type CreatedUser = { id: string; email: string; full_name: string; phone: string; role: string; }; export type CreateUserResponse = { success: boolean; user: CreatedUser; password?: string; }; export type CreateUserWithPasswordResponse = { success: boolean; user: CreatedUser; email: string; password: string; }; // Função para gerar senha aleatória (formato: senhaXXX!) export function gerarSenhaAleatoria(): string { const number1 = Math.floor(Math.random() * 10); const number2 = Math.floor(Math.random() * 10); const number3 = Math.floor(Math.random() * 10); return `senha${number1}${number2}${number3}!`; } export async function criarUsuario( input: CreateUserInput, ): Promise { const url = `${API_BASE}/functions/v1/create-user`; const res = await fetch(url, { method: "POST", headers: { ...baseHeaders(), "Content-Type": "application/json" }, body: JSON.stringify(input), }); return await parse(res); } // ============================================ // CRIAÇÃO DE USUÁRIOS NO SUPABASE AUTH // Vínculo com pacientes/médicos por EMAIL // ============================================ // Criar usuário para MÉDICO no Supabase Auth (sistema de autenticação) export async function criarUsuarioMedico(medico: { email: string; full_name: string; phone_mobile: string; }): Promise { const senha = gerarSenhaAleatoria(); console.log("[CRIAR MÉDICO] Iniciando criação no Supabase Auth..."); console.log("Email:", medico.email); console.log("Nome:", medico.full_name); console.log("Telefone:", medico.phone_mobile); console.log("Senha gerada:", senha); // Endpoint do Supabase Auth (mesmo que auth.ts usa) const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`; const payload = { email: medico.email, password: senha, data: { userType: "profissional", // Para login em /login -> /profissional full_name: medico.full_name, phone: medico.phone_mobile, }, }; console.log("[CRIAR MÉDICO] Enviando para:", signupUrl); try { const response = await fetch(signupUrl, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", apikey: API_KEY, }, body: JSON.stringify(payload), }); console.log( "[CRIAR MÉDICO] Status da resposta:", response.status, response.statusText, ); if (!response.ok) { const errorText = await response.text(); console.error("[CRIAR MÉDICO] Erro na resposta:", errorText); // Tenta parsear o erro para pegar mensagem específica let errorMessage = `Erro ao criar usuário (${response.status})`; try { const errorData = JSON.parse(errorText); errorMessage = errorData.msg || errorData.message || errorData.error_description || errorMessage; // Mensagens amigáveis para erros comuns if ( errorMessage.includes("already registered") || errorMessage.includes("already exists") ) { errorMessage = "Este email já está cadastrado no sistema"; } else if (errorMessage.includes("invalid email")) { errorMessage = "Formato de email inválido"; } else if (errorMessage.includes("weak password")) { errorMessage = "Senha muito fraca"; } } catch (e) { // Se não conseguir parsear, usa mensagem genérica } throw new Error(errorMessage); } const responseData = await response.json(); console.log( "[CRIAR MÉDICO] Usuário criado com sucesso no Supabase Auth!", ); console.log("User ID:", responseData.user?.id || responseData.id); // AUTO-CONFIRMAR EMAIL: Fazer login automático logo após criar usuário // Isso força o Supabase a confirmar o email automaticamente if ( responseData.user?.email_confirmed_at === null || !responseData.user?.email_confirmed_at ) { console.warn( "[CRIAR MÉDICO] Email NÃO confirmado - tentando auto-confirmar via login...", ); try { const loginUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/token?grant_type=password`; console.log( "[AUTO-CONFIRMAR] Fazendo login automático para confirmar email...", ); const loginResponse = await fetch(loginUrl, { method: "POST", headers: { apikey: ENV_CONFIG.SUPABASE_ANON_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ email: medico.email, password: senha, }), }); if (loginResponse.ok) { const loginData = await loginResponse.json(); console.log( "[AUTO-CONFIRMAR] Login automático realizado com sucesso!", ); console.log( "[AUTO-CONFIRMAR] Email confirmado:", loginData.user?.email_confirmed_at ? "SIM" : "NÃO", ); // Atualizar responseData com dados do login (que tem email confirmado) if (loginData.user) { responseData.user = loginData.user; } } else { const errorText = await loginResponse.text(); console.error( "[AUTO-CONFIRMAR] Falha no login automático:", loginResponse.status, errorText, ); console.warn( "[AUTO-CONFIRMAR] Usuário pode não conseguir fazer login imediatamente!", ); } } catch (confirmError) { console.error( "[AUTO-CONFIRMAR] Erro ao tentar fazer login automático:", confirmError, ); console.warn( "[AUTO-CONFIRMAR] Continuando sem confirmação automática...", ); } } else { console.log("[CRIAR MÉDICO] Email confirmado automaticamente!"); } // Log bem visível com as credenciais para teste console.log("========================================"); console.log("CREDENCIAIS DO MÉDICO CRIADO:"); console.log("Email:", medico.email); console.log("Senha:", senha); console.log( "Pode fazer login?", responseData.user?.email_confirmed_at ? "SIM" : "NÃO (precisa confirmar email)", ); console.log("========================================"); return { success: true, user: responseData.user || responseData, email: medico.email, password: senha, }; } catch (error: any) { console.error("[CRIAR MÉDICO] Erro ao criar usuário:", error); throw error; } } // Criar usuário para PACIENTE no Supabase Auth (sistema de autenticação) export async function criarUsuarioPaciente(paciente: { email: string; full_name: string; phone_mobile: string; }): Promise { const senha = gerarSenhaAleatoria(); console.log("[CRIAR PACIENTE] Iniciando criação no Supabase Auth..."); console.log("Email:", paciente.email); console.log("Nome:", paciente.full_name); console.log("Telefone:", paciente.phone_mobile); console.log("Senha gerada:", senha); // Endpoint do Supabase Auth (mesmo que auth.ts usa) const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`; const payload = { email: paciente.email, password: senha, data: { userType: "paciente", // Para login em /login-paciente -> /paciente full_name: paciente.full_name, phone: paciente.phone_mobile, }, }; console.log("[CRIAR PACIENTE] Enviando para:", signupUrl); try { const response = await fetch(signupUrl, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", apikey: API_KEY, }, body: JSON.stringify(payload), }); console.log( "[CRIAR PACIENTE] Status da resposta:", response.status, response.statusText, ); if (!response.ok) { const errorText = await response.text(); console.error("[CRIAR PACIENTE] Erro na resposta:", errorText); // Tenta parsear o erro para pegar mensagem específica let errorMessage = `Erro ao criar usuário (${response.status})`; try { const errorData = JSON.parse(errorText); errorMessage = errorData.msg || errorData.message || errorData.error_description || errorMessage; // Mensagens amigáveis para erros comuns if ( errorMessage.includes("already registered") || errorMessage.includes("already exists") ) { errorMessage = "Este email já está cadastrado no sistema"; } else if (errorMessage.includes("invalid email")) { errorMessage = "Formato de email inválido"; } else if (errorMessage.includes("weak password")) { errorMessage = "Senha muito fraca"; } } catch (e) { // Se não conseguir parsear, usa mensagem genérica } throw new Error(errorMessage); } const responseData = await response.json(); console.log( "[CRIAR PACIENTE] Usuário criado com sucesso no Supabase Auth!", ); console.log("User ID:", responseData.user?.id || responseData.id); console.log( "[CRIAR PACIENTE] Resposta completa do Supabase:", JSON.stringify(responseData, undefined, 2), ); // VERIFICAÇÃO CRÍTICA: O usuário foi realmente criado? if (!responseData.user && !responseData.id) { console.error("AVISO: Supabase retornou sucesso mas SEM user ID!"); console.error( "Isso pode significar que o usuário NÃO foi criado de verdade!", ); } const userId = responseData.user?.id || responseData.id; // AUTO-CONFIRMAR EMAIL: Fazer login automático logo após criar usuário // Isso força o Supabase a confirmar o email automaticamente if ( responseData.user?.email_confirmed_at === null || !responseData.user?.email_confirmed_at ) { console.warn( "[CRIAR PACIENTE] Email NÃO confirmado - tentando auto-confirmar via login...", ); try { const loginUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/token?grant_type=password`; console.log( "[AUTO-CONFIRMAR] Fazendo login automático para confirmar email...", ); const loginResponse = await fetch(loginUrl, { method: "POST", headers: { apikey: ENV_CONFIG.SUPABASE_ANON_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ email: paciente.email, password: senha, }), }); console.log( "[AUTO-CONFIRMAR] Status do login automático:", loginResponse.status, ); if (loginResponse.ok) { const loginData = await loginResponse.json(); console.log( "[AUTO-CONFIRMAR] Login automático realizado com sucesso!", ); console.log( "[AUTO-CONFIRMAR] Dados completos do login:", JSON.stringify(loginData, undefined, 2), ); console.log( "[AUTO-CONFIRMAR] Email confirmado:", loginData.user?.email_confirmed_at ? "SIM" : "NÃO", ); console.log( "[AUTO-CONFIRMAR] UserType no metadata:", loginData.user?.user_metadata?.userType, ); console.log( "[AUTO-CONFIRMAR] Email verified:", loginData.user?.user_metadata?.email_verified, ); // Atualizar responseData com dados do login (que tem email confirmado) if (loginData.user) { responseData.user = loginData.user; } } else { const errorText = await loginResponse.text(); console.error( "[AUTO-CONFIRMAR] Falha no login automático:", loginResponse.status, errorText, ); console.warn( "[AUTO-CONFIRMAR] Usuário pode não conseguir fazer login imediatamente!", ); // Tentar parsear o erro para entender melhor try { const errorData = JSON.parse(errorText); console.error("[AUTO-CONFIRMAR] Detalhes do erro:", errorData); } catch (error) { console.error("[AUTO-CONFIRMAR] Erro não é JSON:", errorText); } } } catch (confirmError) { console.error( "[AUTO-CONFIRMAR] Erro ao tentar fazer login automático:", confirmError, ); console.warn( "[AUTO-CONFIRMAR] Continuando sem confirmação automática...", ); } } else { console.log("[CRIAR PACIENTE] Email confirmado automaticamente!"); } // Log bem visível com as credenciais para teste console.log("========================================"); console.log("CREDENCIAIS DO PACIENTE CRIADO:"); console.log("Email:", paciente.email); console.log("Senha:", senha); console.log("UserType:", "paciente"); console.log( "Pode fazer login?", responseData.user?.email_confirmed_at ? "SIM" : "NÃO (precisa confirmar email)", ); console.log("========================================"); return { success: true, user: responseData.user || responseData, email: paciente.email, password: senha, }; } catch (error: any) { console.error("[CRIAR PACIENTE] Erro ao criar usuário:", error); throw error; } } // ===== CEP (usado nos formulários) ===== export async function buscarCepAPI(cep: string): Promise<{ logradouro?: string; bairro?: string; localidade?: string; uf?: string; erro?: boolean; }> { const clean = (cep || "").replace(/\D/g, ""); try { const res = await fetch(`https://viacep.com.br/ws/${clean}/json/`); const json = await res.json(); if (json?.erro) return { erro: true }; return { logradouro: json.logradouro ?? "", bairro: json.bairro ?? "", localidade: json.localidade ?? "", uf: json.uf ?? "", erro: false, }; } catch { return { erro: true }; } } // ===== Stubs pra não quebrar imports dos forms (sem rotas de storage na doc) ===== export async function listarAnexos(_id: string | number): Promise { return []; } export async function adicionarAnexo( _id: string | number, _file: File, ): Promise { return {}; } export async function removerAnexo( _id: string | number, _anexoId: string | number, ): Promise {} export async function uploadFotoPaciente( _id: string | number, _file: File, ): Promise<{ foto_url?: string; thumbnail_url?: string }> { return {}; } export async function removerFotoPaciente( _id: string | number, ): Promise {} export async function listarAnexosMedico(_id: string | number): Promise { return []; } export async function adicionarAnexoMedico( _id: string | number, _file: File, ): Promise { return {}; } export async function removerAnexoMedico( _id: string | number, _anexoId: string | number, ): Promise {} export async function uploadFotoMedico( _id: string | number, _file: File, ): Promise<{ foto_url?: string; thumbnail_url?: string }> { return {}; } export async function removerFotoMedico(_id: string | number): Promise {} // ===== PERFIS DE USUÁRIOS ===== export async function listarPerfis(): Promise { const url = `${REST}/profiles`; const res = await fetch(url, { method: "GET", headers: baseHeaders(), }); return await parse(res); } export async function buscarPerfilPorId(id: string): Promise { const url = `${REST}/profiles?id=eq.${id}`; const res = await fetch(url, { method: "GET", headers: baseHeaders() }); const array = await parse(res); if (!array?.length) throw new Error("404: Perfil não encontrado"); return array[0]; } export async function atualizarPerfil( id: string, input: ProfileInput, ): Promise { const url = `${REST}/profiles?id=eq.${id}`; const res = await fetch(url, { method: "PATCH", headers: withPrefer( { ...baseHeaders(), "Content-Type": "application/json" }, "return=representation", ), body: JSON.stringify(input), }); const array = await parse(res); return Array.isArray(array) ? array[0] : (array as Profile); }