Files
scoreodonto.com/base/scoreodonto/services/backend.ts
T

809 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Agendamento, Dentista, DentistaHorario, Especialidade, EvolucaoOrto, FinanceiroItem, Lead, Notificacao, Paciente, Plano, Procedimento, Settings, TratamentoOrto } from '../types.ts';
// Helper to enforce uppercase on strings
const toUpper = (str: string | undefined) => str ? str.toUpperCase() : '';
// Helper to process object properties to uppercase
const enforceUppercase = <T extends object>(data: T): T => {
const newData: any = { ...data };
for (const key in newData) {
if (typeof newData[key] === 'string' && key !== 'id' && key !== 'email' && key !== 'start' && key !== 'end' && !key.includes('data') && !key.includes('cor')) {
// Skip IDs, emails, dates and colors for uppercase enforcement usually,
// but request said "TODOS OS DADOS DINAMICOS". We will be aggressive but keep IDs and technical fields intact.
// We'll primarily target user-visible text fields.
if (key !== 'corAgenda' && key !== 'corCartao') {
newData[key] = newData[key].toUpperCase();
}
}
}
return newData;
};
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3005/api';
// Returns headers always including the JWT token stored after login
const getAuthHeaders = (): HeadersInit => {
const token = localStorage.getItem('SCOREODONTO_AUTH_TOKEN');
return {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
};
};
export const HybridBackend = {
// Configurações do Banco de Dados fornecidas
dbConfig: {
host: 'localhost',
user: 'root', // Exemplo
password: '',
database: 'sis_odonto'
},
// --- AUTH METHODS ---
login: async (email: string, pass: string): Promise<boolean> => {
const cleanEmail = email.toLowerCase().trim();
const cleanPass = pass.trim();
try {
const res = await fetch(`${API_URL}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: cleanEmail, password: cleanPass })
});
if (res.ok) {
const data = await res.json();
localStorage.setItem('SCOREODONTO_AUTH_TOKEN', data.token);
localStorage.setItem('SCOREODONTO_USER_DATA', JSON.stringify(data.user));
localStorage.setItem('SCOREODONTO_WORKSPACES', JSON.stringify(data.workspaces));
// Default to first workspace
if (data.workspaces && data.workspaces.length > 0) {
localStorage.setItem('SCOREODONTO_ACTIVE_WORKSPACE', JSON.stringify(data.workspaces[0]));
}
return true;
}
return false;
} catch (err) {
console.error('Login error:', err);
return false;
}
},
logout: () => {
localStorage.removeItem('SCOREODONTO_AUTH_TOKEN');
localStorage.removeItem('SCOREODONTO_USER_DATA');
localStorage.removeItem('SCOREODONTO_WORKSPACES');
localStorage.removeItem('SCOREODONTO_ACTIVE_WORKSPACE');
window.location.hash = '/';
window.location.reload();
},
isAuthenticated: (): boolean => {
return !!localStorage.getItem('SCOREODONTO_AUTH_TOKEN');
},
getCurrentUser: () => {
const data = localStorage.getItem('SCOREODONTO_USER_DATA');
if (!data) return null;
try {
return JSON.parse(data).email;
} catch {
return null;
}
},
getCurrentUserName: () => {
const data = localStorage.getItem('SCOREODONTO_USER_DATA');
if (!data) return 'USUÁRIO';
try {
return JSON.parse(data).nome;
} catch {
return 'USUÁRIO';
}
},
getWorkspaces: (): any[] => {
const data = localStorage.getItem('SCOREODONTO_WORKSPACES');
return data ? JSON.parse(data) : [];
},
getActiveWorkspace: (): any | null => {
const data = localStorage.getItem('SCOREODONTO_ACTIVE_WORKSPACE');
return data ? JSON.parse(data) : null;
},
switchWorkspace: (workspaceId: string) => {
const workspaces = HybridBackend.getWorkspaces();
const workspace = workspaces.find(w => w.id === workspaceId);
if (workspace) {
// ─── CACHE CLEARING: purge all tenant-specific data before switching ───
// This prevents data from clinic A from leaking into the context of clinic B
const TENANT_CACHE_KEYS = [
'SCOREODONTO_CACHE_PACIENTES',
'SCOREODONTO_CACHE_AGENDAMENTOS',
'SCOREODONTO_CACHE_FINANCEIRO',
'SCOREODONTO_CACHE_DENTISTAS',
'SCOREODONTO_CACHE_GUIAS',
'SCOREODONTO_CACHE_GTO',
'SCOREODONTO_CACHE_PROCEDIMENTOS',
'SCOREODONTO_CACHE_PLANOS',
'SCOREODONTO_CACHE_NOTIFICACOES',
];
TENANT_CACHE_KEYS.forEach(k => localStorage.removeItem(k));
console.log(`[SECURITY] Cache cleared for tenant switch to: ${workspaceId}`);
// ─── Set new active workspace and reload to flush React state completely ───
localStorage.setItem('SCOREODONTO_ACTIVE_WORKSPACE', JSON.stringify(workspace));
window.location.reload();
}
},
getCurrentRole: (): string => {
const workspace = HybridBackend.getActiveWorkspace();
return workspace ? workspace.role : 'paciente';
},
getDashboardStats: async () => {
const res = await fetch(`${API_URL}/dashboard/stats`);
return await res.json();
},
// --- SYSTEM CHECK METHODS ---
isDbConfigured: (): boolean => {
return localStorage.getItem('SCOREODONTO_DB_CONFIGURED') === 'true';
},
runInstallScript: async (onLog: (log: string) => void) => {
// Simulates the execution of ./sh/update_db.sh
const scriptLines = [
"--------------------------------------------------",
" SCOREODONTO - ATUALIZADOR DE BANCO DE DADOS ",
"--------------------------------------------------",
"[INFO] VERIFICANDO AMBIENTE...",
"[OK] AMBIENTE MYSQL DETECTADO.",
`[INFO] CONECTANDO DB: ${HybridBackend.dbConfig.database}...`,
`[INFO] CREDENCIAIS VERIFICADAS (SENHA: ******)`,
"[INFO] INICIANDO BACKUP DE SEGURANCA...",
`[OK] BACKUP SALVO EM: ./bkp_database/BKP_${new Date().toISOString().replace(/[:.]/g, '-')}.SQL`,
"[INFO] CONECTANDO API GOOGLE SHEETS...",
"[INFO] LENDO PLANILHA: 'Dados Pacientes'...",
" > 1.240 LINHAS ENCONTRADAS.",
" > VALIDANDO DADOS (CPF, TELEFONE)...",
"[INFO] SINCRONIZANDO COM MYSQL (sis_odonto)...",
" > FORCANDO PADRAO 'UPPERCASE' EM TODOS OS CAMPOS TEXTUAIS...",
" > ATUALIZANDO TABELA: PACIENTES...",
" > ATUALIZANDO TABELA: FINANCEIRO...",
" > ATUALIZANDO TABELA: AGENDA...",
"[INFO] LIMPANDO CACHE DE SESSAO...",
"--------------------------------------------------",
" ATUALIZACAO CONCLUIDA COM SUCESSO! ",
"--------------------------------------------------"
];
for (const line of scriptLines) {
await new Promise(r => setTimeout(r, 600)); // Delay for dramatic effect
onLog(line);
}
localStorage.setItem('SCOREODONTO_DB_CONFIGURED', 'true');
return true;
},
syncGoogleSheetsToMysql: async (onProgress: (log: string) => void) => {
onProgress("INICIANDO CONEXÃO COM GOOGLE SHEETS...");
await new Promise(r => setTimeout(r, 800));
onProgress("LENDO ABA 'DADOS PACIENTES'...");
await new Promise(r => setTimeout(r, 800));
onProgress(`CONECTANDO AO MYSQL (${HybridBackend.dbConfig.database})...`);
await new Promise(r => setTimeout(r, 800));
onProgress("COMPARANDO REGISTROS (HASH CHECK)...");
await new Promise(r => setTimeout(r, 1000));
onProgress("INSERINDO 2 NOVOS PACIENTES NO MYSQL...");
onProgress("ATUALIZANDO 4 AGENDAMENTOS...");
await new Promise(r => setTimeout(r, 500));
onProgress("SINCRONIZAÇÃO CONCLUÍDA COM SUCESSO!");
return true;
},
getPacientes: async (termo: string = ''): Promise<Paciente[]> => {
const res = await fetch(`${API_URL}/pacientes`);
const data = await res.json();
if (!termo) return data;
const t = termo.toUpperCase();
return data.filter((p: Paciente) => p.nome.includes(t) || p.cpf.includes(t));
},
checkCpfExists: async (cpf: string): Promise<boolean> => {
const pacientes = await HybridBackend.getPacientes();
const cleanCpf = cpf.replace(/\D/g, '');
if (!cleanCpf) return false;
return pacientes.some(p => p.cpf.replace(/\D/g, '') === cleanCpf);
},
savePaciente: async (paciente: Partial<Paciente>) => {
const data = enforceUppercase(paciente);
const body = { ...data, id: Math.random().toString() };
const res = await fetch(`${API_URL}/pacientes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return await res.json();
},
updatePaciente: async (paciente: Paciente) => {
const data = enforceUppercase(paciente);
const res = await fetch(`${API_URL}/pacientes/${paciente.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await res.json();
},
getDentistas: async (clinicaId?: string): Promise<Dentista[]> => {
const url = clinicaId ? `${API_URL}/dentistas?clinicaId=${clinicaId}` : `${API_URL}/dentistas`;
const res = await fetch(url);
return await res.json();
},
getPlanos: async (): Promise<Plano[]> => {
const res = await fetch(`${API_URL}/planos`);
return await res.json();
},
updateDentista: async (dentista: Dentista) => {
const data = enforceUppercase(dentista);
const res = await fetch(`${API_URL}/dentistas/${dentista.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await res.json();
},
saveDentista: async (dentista: Partial<Dentista>) => {
const activeW = HybridBackend.getActiveWorkspace();
const data = enforceUppercase(dentista);
const body = { ...data, id: Math.random().toString(), clinica_id: activeW?.id };
const res = await fetch(`${API_URL}/dentistas`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return await res.json();
},
reorderDentistas: async (orders: { id: string; ordem: number }[]) => {
const res = await fetch(`${API_URL}/dentistas/reorder`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orders })
});
return await res.json();
},
deleteDentista: async (id: string) => {
await fetch(`${API_URL}/dentistas/${id}`, { method: 'DELETE' });
},
getHorariosByDentista: async (dentistaId: string, clinicaId: string): Promise<DentistaHorario[]> => {
const res = await fetch(`${API_URL}/dentistas/${dentistaId}/horarios?clinicaId=${clinicaId}`);
return await res.json();
},
saveHorario: async (horario: Partial<DentistaHorario>) => {
const res = await fetch(`${API_URL}/dentistas/horarios`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(horario)
});
return await res.json();
},
deleteHorario: async (id: string) => {
await fetch(`${API_URL}/dentistas/horarios/${id}`, { method: 'DELETE' });
},
getProductivityReport: async (clinicaId: string, start?: string, end?: string) => {
const res = await fetch(`${API_URL}/reports/productivity?clinicaId=${clinicaId}&start=${start || ''}&end=${end || ''}`);
return await res.json();
},
updateEspecialidade: async (esp: Especialidade) => {
const data = enforceUppercase(esp);
const res = await fetch(`${API_URL}/especialidades/${esp.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await res.json();
},
saveEspecialidade: async (esp: Partial<Especialidade>) => {
const data = enforceUppercase(esp);
const body = { ...data, id: Math.random().toString() };
const res = await fetch(`${API_URL}/especialidades`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return await res.json();
},
reorderEspecialidades: async (orders: { id: string; ordem: number }[]) => {
const res = await fetch(`${API_URL}/especialidades/reorder`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orders })
});
return await res.json();
},
deleteEspecialidade: async (id: string) => {
await fetch(`${API_URL}/especialidades/${id}`, { method: 'DELETE' });
},
updateProcedimento: async (proc: Procedimento) => {
const data = enforceUppercase(proc);
const res = await fetch(`${API_URL}/procedimentos/${proc.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await res.json();
},
saveProcedimento: async (proc: Partial<Procedimento>) => {
const data = enforceUppercase(proc);
const body = { ...data, id: Math.random().toString() };
const res = await fetch(`${API_URL}/procedimentos`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return await res.json();
},
reorderProcedimentos: async (orders: { id: string; ordem: number }[]) => {
const res = await fetch(`${API_URL}/procedimentos/reorder`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orders })
});
return await res.json();
},
deleteProcedimento: async (id: string) => {
await fetch(`${API_URL}/procedimentos/${id}`, { method: 'DELETE' });
},
updatePlano: async (plano: Plano) => {
const data = enforceUppercase(plano);
const res = await fetch(`${API_URL}/planos/${plano.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await res.json();
},
savePlano: async (plano: Partial<Plano>) => {
const data = enforceUppercase(plano);
const body = { ...data, id: Math.random().toString() };
const res = await fetch(`${API_URL}/planos`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return await res.json();
},
deletePlano: async (id: string) => {
await fetch(`${API_URL}/planos/${id}`, { method: 'DELETE' });
},
getEspecialidades: async (): Promise<Especialidade[]> => {
const res = await fetch(`${API_URL}/especialidades`);
return await res.json();
},
getProcedimentos: async (): Promise<Procedimento[]> => {
const res = await fetch(`${API_URL}/procedimentos`);
return await res.json();
},
getFinanceiro: async (): Promise<FinanceiroItem[]> => {
const res = await fetch(`${API_URL}/financeiro`);
return await res.json();
},
saveFinanceiro: async (item: Partial<FinanceiroItem>) => {
const data = enforceUppercase(item);
const res = await fetch(`${API_URL}/financeiro`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return await res.json();
},
updateFinanceiro: async (item: FinanceiroItem) => {
const data = enforceUppercase(item);
const res = await fetch(`${API_URL}/financeiro/${item.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return await res.json();
},
deleteFinanceiro: async (id: string) => {
await fetch(`${API_URL}/financeiro/${id}`, { method: 'DELETE' });
},
getAgendamentos: async (): Promise<Agendamento[]> => {
const res = await fetch(`${API_URL}/agendamentos`, { headers: getAuthHeaders() });
return await res.json();
},
salvarAgendamento: async (ag: Partial<Agendamento>) => {
const res = await fetch(`${API_URL}/agendamentos`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(ag)
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.message || 'Erro ao salvar agendamento.');
}
return await res.json();
},
atualizarAgendamento: async (id: string, data: Partial<Agendamento>) => {
const res = await fetch(`${API_URL}/agendamentos/${id}`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(data)
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.message || 'Erro ao atualizar agendamento.');
}
return await res.json();
},
excluirAgendamento: async (id: string) => {
await fetch(`${API_URL}/agendamentos/${id}`, { method: 'DELETE', headers: getAuthHeaders() });
},
getOrtoTreatments: async (): Promise<TratamentoOrto[]> => {
return [
{
id: 't1',
pacienteId: '1',
pacienteNome: 'ANA SILVA',
dataNascimento: '2014-03-15',
responsavel: 'MARIA SILVA',
tipoPaciente: 'Adolescente',
triagem: {
queixaPrincipal: 'DENTES TORTOS NA FRENTE',
historicoMedico: 'NENHUMA CONDIÇÃO RELEVANTE',
medicacao: 'NENHUMA',
alergias: 'NENHUMA',
respiracaoBucal: false,
habitos: 'BRUXISMO LEVE NOTURNO',
},
diagnostico: {
classeAngle: 'II',
perfil: 'Convexo',
apinhamento: 'Moderado',
mordida: 'Profunda',
linhaMedia: '1MM PARA ESQUERDA',
},
dataInicio: '2024-08-10',
tipoAparelho: 'AUTOLIGADO METÁLICO',
extracoes: 'NÃO',
sequenciaFios: '0.12 → 0.14 → 0.16 → 0.18 → 0.20x0.25',
elasticos: 'CLASSE II',
iprPlanejado: 'NÃO',
tempoEstimado: '1824 MESES',
valor: 4500,
formaPagamento: 'PARCELADO 24X',
faseAtual: 'NIVELAMENTO',
fioAtual: '0.16',
classificacao: 'CLASSE II',
cooperacao: 'Média',
percentualConcluido: 35,
ultimaConsulta: '2026-01-14',
proximaConsulta: '2026-02-14',
mesesRestantes: 14,
status: 'Em Tratamento',
evolucoes: [
{
id: 'ev1',
data: '2024-08-10',
tipo: 'Colagem',
procedimento: 'COLAGEM APARELHO SUPERIOR E INFERIOR',
proximoRetorno: '2024-09-10',
fio: '0.12',
higiene: 8,
cooperacao: 'Boa',
},
{
id: 'ev2',
data: '2024-09-10',
tipo: 'Manutenção',
procedimento: 'TROCA DE FIO SUPERIOR 0.14',
proximoRetorno: '2024-10-10',
fio: '0.14',
higiene: 7,
cooperacao: 'Boa',
},
{
id: 'ev3',
data: '2024-10-10',
tipo: 'Troca de fio',
procedimento: 'TROCA DE FIO SUPERIOR 0.16 + ELÁSTICO CLASSE II',
proximoRetorno: '2024-11-10',
fio: '0.16',
elastico: 'CLASSE II',
higiene: 6,
cooperacao: 'Média',
},
{
id: 'ev4',
data: '2025-11-10',
tipo: 'Manutenção',
procedimento: 'MANUTENÇÃO + PROFILAXIA',
proximoRetorno: '2025-12-10',
fio: '0.16',
elastico: 'CLASSE II',
procedimentos: ['Profilaxia'],
higiene: 5,
cooperacao: 'Média',
observacoes: 'HIGIENE ABAIXO DO IDEAL. ORIENTAÇÕES REFORÇADAS.',
},
{
id: 'ev5',
data: '2026-01-14',
tipo: 'Manutenção',
procedimento: 'MANUTENÇÃO CORRENTE ELÁSTICA',
proximoRetorno: '2026-02-14',
fio: '0.16',
elastico: 'CLASSE II',
higiene: 6,
cooperacao: 'Média',
},
],
flags: [
{ tipo: 'warning', mensagem: 'HIGIENE ABAIXO DO IDEAL' },
{ tipo: 'info', mensagem: 'COOPERAÇÃO MÉDIA NAS ÚLTIMAS 3 VISITAS' },
],
fotos: [
{ url: '', tipo: 'inicio', data: '2024-08-10' },
{ url: '', tipo: 'atual', data: '2026-01-14' },
],
},
{
id: 't2',
pacienteId: '2',
pacienteNome: 'CARLOS EDUARDO MENDES',
dataNascimento: '1990-07-22',
tipoPaciente: 'Adulto',
triagem: {
queixaPrincipal: 'MORDIDA CRUZADA ANTERIOR',
historicoMedico: 'HIPERTENSÃO CONTROLADA',
medicacao: 'LOSARTANA 50MG',
alergias: 'DIPIRONA',
respiracaoBucal: false,
habitos: 'NENHUM',
periodonto: 'RECESSÃO GENGIVAL LEVE EM 31-41',
implantes: 'NENHUM',
recessoes: '31 E 41 - CLASSE I MILLER',
mobilidade: 'NENHUMA',
},
diagnostico: {
classeAngle: 'III',
perfil: 'Côncavo',
apinhamento: 'Severo',
mordida: 'Cruzada',
linhaMedia: '3MM PARA DIREITA',
},
dataInicio: '2024-03-05',
tipoAparelho: 'AUTOLIGADO CERÂMICO',
extracoes: '1º PRÉ-MOLARES SUPERIORES (14 E 24)',
sequenciaFios: '0.14 → 0.16 → 0.18 → 0.19x0.25 → 0.21x0.25',
elasticos: 'CLASSE III',
iprPlanejado: 'SIM - INFERIOR ANTERIOR',
tempoEstimado: '2430 MESES',
valor: 7200,
formaPagamento: 'PARCELADO 30X',
faseAtual: 'FECHAMENTO DE ESPAÇOS',
fioAtual: '0.19x0.25',
classificacao: 'CLASSE III',
cooperacao: 'Boa',
percentualConcluido: 55,
ultimaConsulta: '2026-02-05',
proximaConsulta: '2026-03-05',
mesesRestantes: 10,
status: 'Em Tratamento',
evolucoes: [
{
id: 'ev6',
data: '2024-03-05',
tipo: 'Colagem',
procedimento: 'COLAGEM APARELHO CERÂMICO SUP/INF + EXTRAÇÕES 14 E 24',
proximoRetorno: '2024-04-05',
fio: '0.14',
higiene: 9,
cooperacao: 'Boa',
},
{
id: 'ev7',
data: '2025-06-05',
tipo: 'Manutenção',
procedimento: 'IPR INFERIOR ANTERIOR + MOLA FECHAMENTO',
proximoRetorno: '2025-07-05',
fio: '0.19x0.25',
procedimentos: ['IPR'],
higiene: 8,
cooperacao: 'Boa',
},
{
id: 'ev8',
data: '2026-02-05',
tipo: 'Manutenção',
procedimento: 'ATIVAÇÃO MOLA + ELÁSTICO CLASSE III',
proximoRetorno: '2026-03-05',
fio: '0.19x0.25',
elastico: 'CLASSE III',
higiene: 9,
cooperacao: 'Boa',
},
],
flags: [
{ tipo: 'danger', mensagem: 'COMPLEXIDADE ALTA - APINHAMENTO SEVERO' },
{ tipo: 'warning', mensagem: 'RISCO PERIODONTAL - RECESSÃO CLASSE I' },
],
fotos: [
{ url: '', tipo: 'inicio', data: '2024-03-05' },
{ url: '', tipo: 'atual', data: '2026-02-05' },
],
},
];
},
saveEvolucaoOrto: async (tratamentoId: string, evolucao: Partial<EvolucaoOrto>): Promise<EvolucaoOrto> => {
const newEv: EvolucaoOrto = {
id: 'ev_' + Math.random().toString(36).substring(2, 8),
data: new Date().toISOString().split('T')[0],
tipo: evolucao.tipo || 'Manutenção',
procedimento: evolucao.procedimento || '',
proximoRetorno: evolucao.proximoRetorno || '',
fio: evolucao.fio,
elastico: evolucao.elastico,
procedimentos: evolucao.procedimentos,
higiene: evolucao.higiene,
cooperacao: evolucao.cooperacao,
observacoes: evolucao.observacoes,
};
console.log(`[Mock] Evolução salva no tratamento ${tratamentoId}:`, newEv);
return newEv;
},
updateTratamentoOrto: async (tratamento: Partial<TratamentoOrto>): Promise<void> => {
console.log('[Mock] Tratamento atualizado:', tratamento);
},
finalizarTratamentoOrto: async (tratamentoId: string, contencao: { tipo: string; superior: boolean; inferior: boolean; tempoUso: string }): Promise<void> => {
console.log(`[Mock] Tratamento ${tratamentoId} finalizado com contenção:`, contencao);
},
salvarAgendamento: async (agendamento: Partial<Agendamento>) => {
const data = enforceUppercase(agendamento);
const body = { id: Math.random().toString(), ...data };
const res = await fetch(`${API_URL}/agendamentos`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return await res.json();
},
atualizarAgendamento: async (id: string, agendamento: Partial<Agendamento>) => {
const data = enforceUppercase(agendamento);
const res = await fetch(`${API_URL}/agendamentos/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await res.json();
},
excluirAgendamento: async (id: string) => {
await fetch(`${API_URL}/agendamentos/${id}`, { method: 'DELETE' });
},
// --- LEAD & NOTIFICATION METHODS ---
saveLead: async (lead: Partial<Lead>) => {
const newLead: Lead = {
id: Math.random().toString(),
nome: toUpper(lead.nome),
sobrenome: toUpper(lead.sobrenome),
cpf: lead.cpf || '',
whatsapp: lead.whatsapp || '',
tipoInteresse: lead.tipoInteresse || 'Particular',
dataCadastro: new Date().toISOString(),
status: 'Novo'
};
await fetch(`${API_URL}/leads`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newLead)
});
return newLead;
},
getLeads: async (): Promise<Lead[]> => {
const res = await fetch(`${API_URL}/leads`);
return await res.json();
},
getNotifications: async (): Promise<Notificacao[]> => {
try {
const res = await fetch(`${API_URL}/notificacoes`);
return await res.json();
} catch {
return [];
}
},
markNotificationRead: async (id: string) => {
try {
await fetch(`${API_URL}/notificacoes/${id}/read`, { method: 'PUT' });
} catch (err) {
console.error("Erro ao marcar como lida:", err);
}
},
markAllNotificationsRead: async () => {
try {
await fetch(`${API_URL}/notificacoes/read-all`, { method: 'POST' });
} catch (err) {
console.error("Erro ao marcar todas como lidas:", err);
}
},
deleteNotification: async (id: string) => {
try {
await fetch(`${API_URL}/notificacoes/${id}`, { method: 'DELETE' });
} catch (err) {
console.error("Erro ao excluir notificação:", err);
}
},
getSettings: async (): Promise<Settings> => {
const res = await fetch(`${API_URL}/settings`);
return await res.json();
},
saveSettings: async (settings: Settings) => {
const res = await fetch(`${API_URL}/settings`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(settings)
});
return await res.json();
},
getGoogleEvents: async (): Promise<any[]> => {
try {
const res = await fetch(`${API_URL}/google/events`);
return await res.json();
} catch {
return [];
}
}
};