1358 lines
39 KiB
TypeScript

// lib/api.ts
import { ENV_CONFIG } from "@/lib/env-config";
import { API_KEY } from "@/lib/config";
export type ApiOk<T = any> = {
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<string, string> {
const h: Record<string, string> = {
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<string, string>, prefer: string) {
return { ...h, Prefer: prefer };
}
// Parse genérico
async function parse<T>(res: Response): Promise<T> {
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<string, string> {
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<Paciente[]> {
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<Paciente[]>(res);
}
// Nova função para busca avançada de pacientes
export async function buscarPacientes(termo: string): Promise<Paciente[]> {
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<string>();
// 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<Paciente[]>(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<Paciente> {
const url = `${REST}/patients?id=eq.${id}`;
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
const array = await parse<Paciente[]>(res);
if (!array?.length) throw new Error("404: Paciente não encontrado");
return array[0];
}
export async function criarPaciente(input: PacienteInput): Promise<Paciente> {
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<Paciente[] | Paciente>(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<PacienteInput> & { user_id?: string | null },
): Promise<Paciente> {
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<Paciente[] | Paciente>(res);
return Array.isArray(array) ? array[0] : (array as Paciente);
}
export async function excluirPaciente(id: string | number): Promise<void> {
const url = `${REST}/patients?id=eq.${id}`;
const res = await fetch(url, { method: "DELETE", headers: baseHeaders() });
await parse<any>(res);
}
// ===== PACIENTES (Extra: verificação de CPF duplicado) =====
export async function verificarCpfDuplicado(cpf: string): Promise<boolean> {
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<Medico[]> {
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<Medico[]>(res);
}
// Nova função para busca avançada de médicos
export async function buscarMedicos(termo: string): Promise<Medico[]> {
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<string>();
// 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<Medico[]>(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<Medico> {
// 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<Medico[]>(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<Medico> {
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<Medico[] | Medico>(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<Medico> {
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<Medico[] | Medico>(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<void> {
const url = `${REST}/doctors?id=eq.${id}`;
const res = await fetch(url, { method: "DELETE", headers: baseHeaders() });
await parse<any>(res);
}
// ===== USUÁRIOS =====
export type AuthorizationRole = "paciente" | "medico";
type ServerAuthorizationRole = "patient" | "doctor";
const AUTHORIZATION_ROLE_TO_SERVER: Record<
AuthorizationRole,
ServerAuthorizationRole
> = {
paciente: "patient",
medico: "doctor",
};
const SERVER_ROLE_TO_AUTHORIZATION: Record<
ServerAuthorizationRole,
AuthorizationRole
> = {
patient: "paciente",
doctor: "medico",
};
export type UserRole = {
id: string;
user_id: string;
role: string;
created_at: string;
};
export async function listarUserRoles(): Promise<UserRole[]> {
const url = `${REST}/user_roles`;
const res = await fetch(url, {
method: "GET",
headers: baseHeaders(),
});
return await parse<UserRole[]>(res);
}
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<AuthorizationRole[]> {
const url = `${REST}/patient_assignments?user_id=eq.${userId}`;
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
const rows = await parse<Array<{ role: string }>>(res);
if (!Array.isArray(rows) || rows.length === 0) {
// sem atribuições, retornamos acesso padrão de paciente
return ["paciente"];
}
return rows
.map((r) =>
r.role === "medico" ? "medico" : ("paciente" as AuthorizationRole),
)
.filter((role, index, self) => self.indexOf(role) === index);
}
/**
* Atualiza atribuições de usuário: remove as antigas e cria novas
*/
export async function atualizarAutorizacoesUsuario(
userId: string,
patientId: string,
roles: AuthorizationRole[],
): Promise<AuthorizationRole[]> {
// 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!)
const payload = roles.map((role) => ({
user_id: userId,
patient_id: patientId,
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<Array<{ role: string }>>(res);
if (!Array.isArray(data)) return [];
return data
.map((r) =>
r.role === "medico" ? "medico" : ("paciente" as AuthorizationRole),
)
.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<Profile, "id" | "created_at" | "updated_at">
>;
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<CurrentUser> {
const url = `${API_BASE}/auth/v1/user`;
const res = await fetch(url, {
method: "GET",
headers: baseHeaders(),
});
return await parse<CurrentUser>(res);
}
export async function getUserInfo(): Promise<UserInfo> {
const url = `${API_BASE}/functions/v1/user-info`;
const res = await fetch(url, {
method: "GET",
headers: baseHeaders(),
});
return await parse<UserInfo>(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<string | null> {
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<Profile[]>(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<CreateUserResponse> {
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<CreateUserResponse>(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<CreateUserWithPasswordResponse> {
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<CreateUserWithPasswordResponse> {
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, null, 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 (e) {
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<any[]> {
return [];
}
export async function adicionarAnexo(
_id: string | number,
_file: File,
): Promise<any> {
return {};
}
export async function removerAnexo(
_id: string | number,
_anexoId: string | number,
): Promise<void> {}
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<void> {}
export async function listarAnexosMedico(_id: string | number): Promise<any[]> {
return [];
}
export async function adicionarAnexoMedico(
_id: string | number,
_file: File,
): Promise<any> {
return {};
}
export async function removerAnexoMedico(
_id: string | number,
_anexoId: string | number,
): Promise<void> {}
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<void> {}
// ===== PERFIS DE USUÁRIOS =====
export async function listarPerfis(): Promise<Profile[]> {
const url = `${REST}/profiles`;
const res = await fetch(url, {
method: "GET",
headers: baseHeaders(),
});
return await parse<Profile[]>(res);
}
export async function buscarPerfilPorId(id: string): Promise<Profile> {
const url = `${REST}/profiles?id=eq.${id}`;
const res = await fetch(url, { method: "GET", headers: baseHeaders() });
const array = await parse<Profile[]>(res);
if (!array?.length) throw new Error("404: Perfil não encontrado");
return array[0];
}
export async function atualizarPerfil(
id: string,
input: ProfileInput,
): Promise<Profile> {
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<Profile[] | Profile>(res);
return Array.isArray(array) ? array[0] : (array as Profile);
}