Files
agenciapsilmno/src/services/Medicos.service.js
T

225 lines
7.7 KiB
JavaScript

/*
|--------------------------------------------------------------------------
| 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'));
}