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

785 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 || '/api';
// Returns headers always including the JWT token stored after login
const apiFetch = async (url: string, options: RequestInit = {}) => {
const token = localStorage.getItem('SCOREODONTO_AUTH_TOKEN');
const headers = {
...options.headers,
...(token ? { 'Authorization': `Bearer ${token}` } : {})
};
return fetch(url, { ...options, headers });
};
export const HybridBackend = {
dbConfig: {
database: 'scoreodonto'
},
// --- AUTH METHODS ---
login: async (email: string, pass: string): Promise<boolean> => {
const cleanEmail = email.toLowerCase().trim();
const cleanPass = pass.trim();
try {
const res = await apiFetch(`${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.href = '/';
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?.role || 'paciente';
},
getDashboardStats: async () => {
const res = await apiFetch(`${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 POSTGRESQL DETECTADO.",
`[INFO] CONECTANDO DB: ${HybridBackend.dbConfig.database}...`,
"[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 POSTGRESQL (scoreodonto)...",
" > 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;
},
syncGoogleSheetsToPg: 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 POSTGRESQL (${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 POSTGRESQL...");
onProgress("ATUALIZANDO 4 AGENDAMENTOS...");
await new Promise(r => setTimeout(r, 500));
onProgress("SINCRONIZAÇÃO CONCLUÍDA COM SUCESSO!");
return true;
},
getPacientes: async (termo: string = '', sort: string = 'recentes'): Promise<{ data: Paciente[]; total: number }> => {
const params = new URLSearchParams();
if (termo.trim()) params.set('q', termo.trim());
if (sort) params.set('sort', sort);
const res = await apiFetch(`${API_URL}/pacientes?${params}`);
return res.json();
},
checkCpfExists: async (cpf: string): Promise<boolean> => {
const cleanCpf = cpf.replace(/\D/g, '');
if (!cleanCpf) return false;
const res = await apiFetch(`${API_URL}/pacientes?q=${encodeURIComponent(cpf)}`);
const { data } = await res.json();
const pacientes: Paciente[] = data || [];
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 apiFetch(`${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 apiFetch(`${API_URL}/pacientes/${paciente.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return await res.json();
},
getGruposFamiliares: async (q?: string) => {
const url = q?.trim() ? `${API_URL}/grupos-familiares?q=${encodeURIComponent(q)}` : `${API_URL}/grupos-familiares`;
const res = await apiFetch(url);
return res.json();
},
criarGrupoFamiliar: async (nome: string, responsavel_id?: string) => {
const res = await apiFetch(`${API_URL}/grupos-familiares`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nome, responsavel_id })
});
return res.json();
},
getMembrosFamilia: async (grupoId: string) => {
const res = await apiFetch(`${API_URL}/grupos-familiares/${grupoId}/membros`);
return res.json();
},
getDentistas: async (clinicaId?: string): Promise<Dentista[]> => {
const url = clinicaId ? `${API_URL}/dentistas?clinicaId=${clinicaId}` : `${API_URL}/dentistas`;
const res = await apiFetch(url);
return await res.json();
},
getPlanos: async (): Promise<Plano[]> => {
const res = await apiFetch(`${API_URL}/planos`);
return await res.json();
},
updateDentista: async (dentista: Dentista) => {
const data = enforceUppercase(dentista);
const res = await apiFetch(`${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 apiFetch(`${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 apiFetch(`${API_URL}/dentistas/reorder`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orders })
});
return await res.json();
},
deleteDentista: async (id: string) => {
await apiFetch(`${API_URL}/dentistas/${id}`, { method: 'DELETE' });
},
getHorariosByDentista: async (dentistaId: string, clinicaId: string): Promise<DentistaHorario[]> => {
const res = await apiFetch(`${API_URL}/dentistas/${dentistaId}/horarios?clinicaId=${clinicaId}`);
return await res.json();
},
saveHorario: async (horario: Partial<DentistaHorario>) => {
const res = await apiFetch(`${API_URL}/dentistas/horarios`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(horario)
});
return await res.json();
},
deleteHorario: async (id: string) => {
await apiFetch(`${API_URL}/dentistas/horarios/${id}`, { method: 'DELETE' });
},
getProductivityReport: async (clinicaId: string, start?: string, end?: string) => {
const res = await apiFetch(`${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 apiFetch(`${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 apiFetch(`${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 apiFetch(`${API_URL}/especialidades/reorder`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orders })
});
return await res.json();
},
deleteEspecialidade: async (id: string) => {
await apiFetch(`${API_URL}/especialidades/${id}`, { method: 'DELETE' });
},
updateProcedimento: async (proc: Procedimento) => {
const data = enforceUppercase(proc);
const res = await apiFetch(`${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 apiFetch(`${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 apiFetch(`${API_URL}/procedimentos/reorder`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orders })
});
return await res.json();
},
deleteProcedimento: async (id: string) => {
await apiFetch(`${API_URL}/procedimentos/${id}`, { method: 'DELETE' });
},
updatePlano: async (plano: Plano) => {
const data = enforceUppercase(plano);
const res = await apiFetch(`${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 apiFetch(`${API_URL}/planos`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return await res.json();
},
deletePlano: async (id: string) => {
await apiFetch(`${API_URL}/planos/${id}`, { method: 'DELETE' });
},
getEspecialidades: async (): Promise<Especialidade[]> => {
const res = await apiFetch(`${API_URL}/especialidades`);
return await res.json();
},
getProcedimentos: async (): Promise<Procedimento[]> => {
const res = await apiFetch(`${API_URL}/procedimentos`);
return await res.json();
},
getFinanceiro: async (): Promise<FinanceiroItem[]> => {
const res = await apiFetch(`${API_URL}/financeiro`);
return await res.json();
},
saveFinanceiro: async (item: Partial<FinanceiroItem>) => {
const data = enforceUppercase(item);
const res = await apiFetch(`${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 apiFetch(`${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 apiFetch(`${API_URL}/financeiro/${id}`, { method: 'DELETE' });
},
getAgendamentos: async (): Promise<Agendamento[]> => {
const res = await apiFetch(`${API_URL}/agendamentos`, { });
return await res.json();
},
salvarAgendamento: async (ag: Partial<Agendamento>) => {
const res = await apiFetch(`${API_URL}/agendamentos`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
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 apiFetch(`${API_URL}/agendamentos/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
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 apiFetch(`${API_URL}/agendamentos/${id}`, { method: 'DELETE'});
},
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);
},
// --- 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 apiFetch(`${API_URL}/leads`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newLead)
});
return newLead;
},
getLeads: async (): Promise<Lead[]> => {
const res = await apiFetch(`${API_URL}/leads`);
return await res.json();
},
getNotifications: async (): Promise<Notificacao[]> => {
try {
const res = await apiFetch(`${API_URL}/notificacoes`);
return await res.json();
} catch {
return [];
}
},
markNotificationRead: async (id: string) => {
try {
await apiFetch(`${API_URL}/notificacoes/${id}/read`, { method: 'PUT' });
} catch (err) {
console.error("Erro ao marcar como lida:", err);
}
},
markAllNotificationsRead: async () => {
try {
await apiFetch(`${API_URL}/notificacoes/read-all`, { method: 'POST' });
} catch (err) {
console.error("Erro ao marcar todas como lidas:", err);
}
},
deleteNotification: async (id: string) => {
try {
await apiFetch(`${API_URL}/notificacoes/${id}`, { method: 'DELETE' });
} catch (err) {
console.error("Erro ao excluir notificação:", err);
}
},
getSettings: async (): Promise<Settings> => {
const res = await apiFetch(`${API_URL}/settings`);
return await res.json();
},
saveSettings: async (settings: Settings) => {
const res = await apiFetch(`${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 apiFetch(`${API_URL}/google/events`);
return await res.json();
} catch {
return [];
}
}
};