feat: migrate ScoreOdonto CRM to Docker, PostgreSQL, and DragonflyDB

This commit is contained in:
Antigravity AI Agent
2026-05-13 07:13:41 +02:00
parent 4917d03bdf
commit 606da6de97
153 changed files with 35678 additions and 56 deletions
+809
View File
@@ -0,0 +1,809 @@
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 [];
}
}
};