/* |-------------------------------------------------------------------------- | Agência PSI |-------------------------------------------------------------------------- | Criado e desenvolvido por Leonardo Nohama | | Tecnologia aplicada à escuta. | Estrutura para o cuidado. | | Arquivo: src/services/Medicos.service.js | Data: 2026 | Local: São Carlos/SP — Brasil |-------------------------------------------------------------------------- | © 2026 — Todos os direitos reservados |-------------------------------------------------------------------------- */ import { supabase } from '@/lib/supabase/client'; // ── Helpers ────────────────────────────────────────────────── async function getOwnerId() { const { data, error } = await supabase.auth.getUser(); if (error) throw error; const uid = data?.user?.id; if (!uid) throw new Error('Sessão inválida.'); return uid; } async function getActiveTenantId(uid) { const { data, error } = await supabase .from('tenant_members') .select('tenant_id') .eq('user_id', uid) .eq('status', 'active') .order('created_at', { ascending: false }) .limit(1) .single(); if (error) throw error; if (!data?.tenant_id) throw new Error('Tenant não encontrado.'); return data.tenant_id; } function normalizeNome(s) { return String(s || '').trim().toLowerCase().replace(/\s+/g, ' '); } function isUniqueViolation(err) { if (!err) return false; if (err.code === '23505') return true; return /duplicate key value violates unique constraint/i.test(String(err.message || '')); } // ── List ───────────────────────────────────────────────────── /** * Lista médicos ativos do owner com contagem de pacientes encaminhados. * A contagem é feita buscando quantos patients possuem o nome do médico * no campo `encaminhado_por` (text). */ export async function listMedicosWithPatientCounts() { const ownerId = await getOwnerId(); const { data: medicos, error } = await supabase .from('medicos') .select('id, nome, crm, especialidade, telefone_profissional, telefone_pessoal, email, clinica, cidade, estado, observacoes, ativo, owner_id, tenant_id, created_at, updated_at') .eq('owner_id', ownerId) .eq('ativo', true) .order('nome', { ascending: true }); if (error) throw error; // Busca pacientes do owner para contar encaminhamentos por médico const { data: patients, error: pErr } = await supabase .from('patients') .select('id, encaminhado_por') .eq('owner_id', ownerId); if (pErr) throw pErr; const countMap = new Map(); for (const med of medicos || []) { countMap.set(med.id, 0); } for (const p of patients || []) { const enc = String(p.encaminhado_por || '').toLowerCase(); if (!enc) continue; for (const med of medicos || []) { const nomeLower = med.nome.toLowerCase(); if (enc.includes(nomeLower)) { countMap.set(med.id, (countMap.get(med.id) || 0) + 1); } } } return (medicos || []).map((m) => ({ ...m, patients_count: countMap.get(m.id) || 0 })); } // ── Create ─────────────────────────────────────────────────── export async function createMedico(payload) { const ownerId = await getOwnerId(); const tenantId = await getActiveTenantId(ownerId); const nome = String(payload.nome || '').trim(); if (!nome) throw new Error('Nome do médico é obrigatório.'); const row = { owner_id: ownerId, tenant_id: tenantId, nome, crm: String(payload.crm || '').trim() || null, especialidade: payload.especialidade || null, telefone_profissional: payload.telefone_profissional || null, telefone_pessoal: payload.telefone_pessoal || null, email: String(payload.email || '').trim() || null, clinica: String(payload.clinica || '').trim() || null, cidade: String(payload.cidade || '').trim() || null, estado: String(payload.estado || '').trim() || null, observacoes: String(payload.observacoes || '').trim() || null, ativo: true }; const { data, error } = await supabase .from('medicos') .insert(row) .select('*') .single(); if (error) { if (isUniqueViolation(error)) throw new Error('Já existe um médico com este CRM.'); throw error; } return data; } // ── Update ─────────────────────────────────────────────────── export async function updateMedico(id, payload) { const ownerId = await getOwnerId(); if (!id) throw new Error('ID inválido.'); const nome = String(payload.nome || '').trim(); if (!nome) throw new Error('Nome do médico é obrigatório.'); const row = { nome, crm: String(payload.crm || '').trim() || null, especialidade: payload.especialidade || null, telefone_profissional: payload.telefone_profissional || null, telefone_pessoal: payload.telefone_pessoal || null, email: String(payload.email || '').trim() || null, clinica: String(payload.clinica || '').trim() || null, cidade: String(payload.cidade || '').trim() || null, estado: String(payload.estado || '').trim() || null, observacoes: String(payload.observacoes || '').trim() || null, updated_at: new Date().toISOString() }; const { data, error } = await supabase .from('medicos') .update(row) .eq('id', id) .eq('owner_id', ownerId) .select('*') .single(); if (error) { if (isUniqueViolation(error)) throw new Error('Já existe um médico com este CRM.'); throw error; } return data; } // ── Delete (soft) ──────────────────────────────────────────── export async function deleteMedico(id) { const ownerId = await getOwnerId(); if (!id) throw new Error('ID inválido.'); const { error } = await supabase .from('medicos') .update({ ativo: false, updated_at: new Date().toISOString() }) .eq('id', id) .eq('owner_id', ownerId); if (error) throw error; return true; } // ── Pacientes de um médico ─────────────────────────────────── /** * Busca pacientes do owner cujo campo `encaminhado_por` contém o nome do médico. */ export async function fetchPatientsByMedicoNome(medicoNome) { const ownerId = await getOwnerId(); const nomeLower = String(medicoNome || '').trim().toLowerCase(); if (!nomeLower) return []; const { data, error } = await supabase .from('patients') .select('id, nome_completo, email_principal, telefone, avatar_url, encaminhado_por') .eq('owner_id', ownerId) .ilike('encaminhado_por', `%${nomeLower}%`); if (error) throw error; return (data || []) .map((p) => ({ id: p.id, full_name: p.nome_completo || '—', email: p.email_principal || '—', phone: p.telefone || '—', avatar_url: p.avatar_url || null, encaminhado_por: p.encaminhado_por || '' })) .sort((a, b) => String(a.full_name).localeCompare(String(b.full_name), 'pt-BR')); }