diff --git a/backend/server.js b/backend/server.js index 3d52d82..8da32b4 100644 --- a/backend/server.js +++ b/backend/server.js @@ -349,6 +349,13 @@ async function pushGoogleEvent(action, ag) { } catch (e) { console.error('[google-push]', e.message); } } +// Área padrão a partir do papel do usuário (default ao criar especialidade/procedimento). +function roleToArea(role) { + if (role === 'biomedico') return 'biomedicina'; + if (role === 'protetico') return 'protese'; + return 'odontologia'; +} + // Catálogo padrão de Biomedicina Estética (especialidades + procedimentos com valor sugerido). const BIOMED_ESPECIALIDADES = [ 'BIOMEDICINA ESTÉTICA', 'HARMONIZAÇÃO OROFACIAL', 'ESTÉTICA FACIAL', 'ESTÉTICA CORPORAL', 'PROCEDIMENTOS INJETÁVEIS', @@ -375,16 +382,18 @@ async function seedBiomedicina(clinicaId) { const espId = {}; for (let i = 0; i < BIOMED_ESPECIALIDADES.length; i++) { const id = `esp_${Date.now()}_${i}_${Math.random().toString(36).slice(2, 6)}`; - await pool.query('INSERT INTO especialidades (id, nome, clinica_id, ordem, ativo) VALUES ($1,$2,$3,$4,true)', [id, BIOMED_ESPECIALIDADES[i], clinicaId, i]); + await pool.query("INSERT INTO especialidades (id, nome, clinica_id, ordem, ativo, area) VALUES ($1,$2,$3,$4,true,'biomedicina')", [id, BIOMED_ESPECIALIDADES[i], clinicaId, i]); espId[BIOMED_ESPECIALIDADES[i]] = id; } for (let i = 0; i < BIOMED_PROCEDIMENTOS.length; i++) { const pr = BIOMED_PROCEDIMENTOS[i]; const id = `proc_${Date.now()}_${i}_${Math.random().toString(36).slice(2, 6)}`; await pool.query( - 'INSERT INTO procedimentos (id, nome, especialidadeid, especialidadenome, valorparticular, clinica_id, ordem, ativo) VALUES ($1,$2,$3,$4,$5,$6,$7,true)', + "INSERT INTO procedimentos (id, nome, especialidadeid, especialidadenome, valorparticular, clinica_id, ordem, ativo, area) VALUES ($1,$2,$3,$4,$5,$6,$7,true,'biomedicina')", [id, pr.nome, espId[pr.esp] || null, pr.esp, pr.valor, clinicaId, i]); } + // marca a clínica como área de biomedicina + await pool.query("INSERT INTO clinica_areas (clinica_id, area) VALUES ($1,'biomedicina') ON CONFLICT DO NOTHING", [clinicaId]); return true; } @@ -2645,10 +2654,12 @@ app.post('/api/especialidades', authGuard, async (req, res) => { const { rows: userRows } = await pool.query('SELECT role FROM usuarios WHERE id = $1', [req.authUser.userId]); const isSuperAdmin = userRows[0]?.role === 'superadmin'; const clinicaId = isSuperAdmin ? null : (req.body.clinica_id || req.query.clinicaId || null); + const area = req.body.area || roleToArea(userRows[0]?.role); const { rows: counts } = await pool.query('SELECT COUNT(*) as count FROM especialidades'); const ordem = parseInt(counts[0].count); - const ins = buildInsert('especialidades', { ...req.body, clinica_id: clinicaId, ordem }); + const ins = buildInsert('especialidades', { ...req.body, clinica_id: clinicaId, ordem, area }); await pool.query(ins.text, ins.values); + if (clinicaId && area) await pool.query('INSERT INTO clinica_areas (clinica_id, area) VALUES ($1,$2) ON CONFLICT DO NOTHING', [clinicaId, area]); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); @@ -2779,10 +2790,12 @@ app.post('/api/procedimentos', authGuard, async (req, res) => { clinica_id: clinicaId, codigoInterno: codigoInternoVal, ordem: ordemValue, + area: proc.area || roleToArea(userRows[0]?.role), tipo_regiao: proc.tipo_regiao || 'GERAL', exige_face: proc.exige_face ? 1 : 0 }; if (!payload.id) payload.id = Math.random().toString(36).substring(2, 10).toUpperCase(); + if (clinicaId && payload.area) await pool.query('INSERT INTO clinica_areas (clinica_id, area) VALUES ($1,$2) ON CONFLICT DO NOTHING', [clinicaId, payload.area]); await withTransaction(async (client) => { const ins = buildInsert('procedimentos', payload); @@ -4057,6 +4070,33 @@ async function runMigrations() { `ALTER TABLE usuarios ADD COLUMN IF NOT EXISTS numero TEXT`, `ALTER TABLE especialidades ADD COLUMN IF NOT EXISTS ativo BOOLEAN DEFAULT true`, `ALTER TABLE procedimentos ADD COLUMN IF NOT EXISTS ativo BOOLEAN DEFAULT true`, + // ── Áreas de atuação (Odontologia / Biomedicina / Prótese) ────────── + `CREATE TABLE IF NOT EXISTS areas ( + slug TEXT PRIMARY KEY, nome TEXT NOT NULL, cor TEXT, icone TEXT, ordem INT DEFAULT 0 + )`, + `INSERT INTO areas (slug, nome, cor, icone, ordem) VALUES + ('odontologia','Odontologia','#2563eb','Stethoscope',1), + ('biomedicina','Biomedicina Estética','#db2777','Sparkles',2), + ('protese','Prótese','#7c3aed','FlaskConical',3) + ON CONFLICT (slug) DO NOTHING`, + `ALTER TABLE especialidades ADD COLUMN IF NOT EXISTS area TEXT`, + `ALTER TABLE procedimentos ADD COLUMN IF NOT EXISTS area TEXT`, + // Multi-área por clínica e por usuário (uma unidade pode ter odonto + biomedicina). + `CREATE TABLE IF NOT EXISTS clinica_areas (clinica_id TEXT NOT NULL, area TEXT NOT NULL, PRIMARY KEY (clinica_id, area))`, + `CREATE TABLE IF NOT EXISTS usuario_areas (usuario_id TEXT NOT NULL, area TEXT NOT NULL, PRIMARY KEY (usuario_id, area))`, + // Backfill idempotente do catálogo (só onde area ainda é NULL). + `UPDATE especialidades SET area = 'biomedicina' WHERE area IS NULL AND nome IN ('BIOMEDICINA ESTÉTICA','ESTÉTICA FACIAL','ESTÉTICA CORPORAL','PROCEDIMENTOS INJETÁVEIS')`, + `UPDATE especialidades SET area = 'odontologia' WHERE area IS NULL`, + `UPDATE procedimentos SET area = 'biomedicina' WHERE area IS NULL AND nome IN ('TOXINA BOTULÍNICA (BOTOX)','PREENCHIMENTO COM ÁCIDO HIALURÔNICO','BIOESTIMULADOR DE COLÁGENO','SKINBOOSTER','PEELING QUÍMICO','MICROAGULHAMENTO','LIMPEZA DE PELE','FIOS DE SUSTENTAÇÃO (PDO)','HARMONIZAÇÃO FACIAL','ESCLEROTERAPIA (VASINHOS)','INTRADERMOTERAPIA / MESOTERAPIA','LIPÓLISE ENZIMÁTICA')`, + `UPDATE procedimentos SET area = 'odontologia' WHERE area IS NULL`, + // Backfill das junções: usuário pela role; clínica pelas áreas do catálogo. + `INSERT INTO usuario_areas (usuario_id, area) + SELECT id, CASE role WHEN 'biomedico' THEN 'biomedicina' WHEN 'protetico' THEN 'protese' ELSE 'odontologia' END + FROM usuarios WHERE role IN ('dentista','biomedico','protetico','donoclinica','donoconsultorio','funcionario','admin') + ON CONFLICT DO NOTHING`, + `INSERT INTO clinica_areas (clinica_id, area) + SELECT DISTINCT clinica_id, area FROM especialidades WHERE clinica_id IS NOT NULL AND area IS NOT NULL + ON CONFLICT DO NOTHING`, `ALTER TABLE clinicas ADD COLUMN IF NOT EXISTS geo geography(Point,4326)`, `CREATE INDEX IF NOT EXISTS idx_salas_geo ON salas USING GIST (geo)`, `CREATE INDEX IF NOT EXISTS idx_usuarios_geo ON usuarios USING GIST (geo)`, @@ -4216,6 +4256,14 @@ app.post('/api/me/plugins', authGuard, async (req, res) => { } catch (err) { res.status(500).json({ error: err.message }); } }); +// Lista as áreas de atuação (referência). +app.get('/api/areas', authGuard, async (req, res) => { + try { + const { rows } = await pool.query('SELECT slug, nome, cor, icone, ordem FROM areas ORDER BY ordem'); + res.json(rows); + } catch (err) { res.status(500).json({ error: err.message }); } +}); + // Semeia o catálogo de biomedicina estética na clínica/consultório do usuário (idempotente). // Usado no 1º acesso do biomédico ao catálogo (cobre contas criadas antes do auto-seed). app.post('/api/me/seed-biomedicina', authGuard, async (req, res) => {