diff --git a/backend/server.js b/backend/server.js index a85e1ca..475f1ce 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1378,7 +1378,7 @@ app.post('/api/clinica-sync/import-pacientes', tenantGuard, async (req, res) => }); // Export CSV dos pacientes (o bem mais precioso). Sem "ultimo tratamento". Não precisa de Google. -app.get('/api/pacientes/export', authGuard, async (req, res) => { +app.get('/api/pacientes/export', authGuard, requireCapability('agenda'), async (req, res) => { try { const clinicaId = req.query.clinicaId; if (!clinicaId) return res.status(400).send('clinicaId obrigatório'); @@ -1897,7 +1897,7 @@ app.get('/api/pacientes', tenantGuard, async (req, res) => { } }); -app.post('/api/pacientes', authGuard, async (req, res) => { +app.post('/api/pacientes', authGuard, requireCapability('agenda'), async (req, res) => { try { const clinicaId = req.body.clinica_id || req.query.clinicaId; if (!clinicaId) return res.status(400).json({ error: 'clinica_id obrigatório.' }); @@ -1912,7 +1912,7 @@ app.post('/api/pacientes', authGuard, async (req, res) => { } }); -app.put('/api/pacientes/:id', authGuard, async (req, res) => { +app.put('/api/pacientes/:id', authGuard, requireCapabilityRow('agenda', 'pacientes'), async (req, res) => { try { const { rows: pc } = await pool.query('SELECT clinica_id FROM pacientes WHERE id = $1', [req.params.id]); if (!pc.length) return res.status(404).json({ error: 'Paciente não encontrado.' }); @@ -2627,7 +2627,7 @@ async function findOverbookingConflict(db, dentistaId, start, end, ignoreId) { } // POST with concurrency lock: prevents double-booking the same dentist+time slot -app.post('/api/agendamentos', authGuard, async (req, res) => { +app.post('/api/agendamentos', authGuard, requireCapability('agenda'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); @@ -2715,7 +2715,7 @@ app.post('/api/agendamentos', authGuard, async (req, res) => { }); // PUT = editar / REAGENDAR (mover pra novo slot). Lock otimista por `version`. -app.put('/api/agendamentos/:id', authGuard, async (req, res) => { +app.put('/api/agendamentos/:id', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); @@ -2837,7 +2837,7 @@ async function assertRowScoped(req, res, sql, params) { // DELETE = cancelamento SOFT (status 'cancelado'). NUNCA remove a linha — preserva // histórico/autoria ("quem cancelou") e gera pendência "a reagendar". -app.delete('/api/agendamentos/:id', authGuard, async (req, res) => { +app.delete('/api/agendamentos/:id', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); @@ -2859,7 +2859,7 @@ app.delete('/api/agendamentos/:id', authGuard, async (req, res) => { }); // Marcar FALTA (no-show) → contabiliza falta do paciente + pendência "a reagendar". -app.post('/api/agendamentos/:id/falta', authGuard, async (req, res) => { +app.post('/api/agendamentos/:id/falta', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); @@ -2878,7 +2878,7 @@ app.post('/api/agendamentos/:id/falta', authGuard, async (req, res) => { }); // REMARCAR (precisa nova data, SEM horário definido) → pendência "a reagendar". -app.post('/api/agendamentos/:id/remarcar', authGuard, async (req, res) => { +app.post('/api/agendamentos/:id/remarcar', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); @@ -2894,7 +2894,7 @@ app.post('/api/agendamentos/:id/remarcar', authGuard, async (req, res) => { }); // RESTAURAR (desfaz cancelamento/falta/remarcar) → volta a 'agendado' e baixa a pendência gerada. -app.post('/api/agendamentos/:id/restaurar', authGuard, async (req, res) => { +app.post('/api/agendamentos/:id/restaurar', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); @@ -2910,7 +2910,7 @@ app.post('/api/agendamentos/:id/restaurar', authGuard, async (req, res) => { // CONFIRMAR um agendamento IMPORTADO: vincula o paciente (exige CPF+telefone) e vira 'agendado'. // Não escreve no Google (vínculo é interno). O paciente é enriquecido se faltava CPF/telefone. -app.post('/api/agendamentos/:id/confirmar', authGuard, async (req, res) => { +app.post('/api/agendamentos/:id/confirmar', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); @@ -2952,7 +2952,7 @@ async function notificarDonoClinica(clinicaId, titulo, mensagem) { } // Dentista REENCAMINHA o agendamento de volta à clínica, com um motivo. -app.post('/api/agendamentos/:id/reencaminhar', authGuard, async (req, res) => { +app.post('/api/agendamentos/:id/reencaminhar', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); const { motivo } = req.body || {}; @@ -2967,7 +2967,7 @@ app.post('/api/agendamentos/:id/reencaminhar', authGuard, async (req, res) => { }); // Dentista SOLICITA UMA AVALIAÇÃO (Fase A: registra o pedido + avisa; odontograma/fotos = Fase B). -app.post('/api/agendamentos/:id/solicitar-avaliacao', authGuard, async (req, res) => { +app.post('/api/agendamentos/:id/solicitar-avaliacao', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); const { motivo } = req.body || {}; @@ -4167,7 +4167,7 @@ app.delete('/api/orcamentos/:id', tenantGuard, async (req, res) => { }); // --- LEADS --- -app.get('/api/leads', tenantGuard, async (req, res) => { +app.get('/api/leads', tenantGuard, requireCapability('leads'), async (req, res) => { try { const clinicaId = req.clinicaId; if (!clinicaId) return res.status(400).json({ error: 'clinicaId obrigatório.' }); @@ -4176,7 +4176,7 @@ app.get('/api/leads', tenantGuard, async (req, res) => { } catch (err) { res.status(500).json({ error: err.message }); } }); -app.post('/api/leads', tenantGuard, async (req, res) => { +app.post('/api/leads', tenantGuard, requireCapability('leads'), async (req, res) => { try { const clinicaId = req.clinicaId; if (!clinicaId) return res.status(400).json({ error: 'clinica_id obrigatório.' }); @@ -4188,7 +4188,7 @@ app.post('/api/leads', tenantGuard, async (req, res) => { }); // Converte um LEAD em PACIENTE (e opcionalmente cria o 1º agendamento). Escopo por clínica. -app.post('/api/leads/:id/converter', tenantGuard, async (req, res) => { +app.post('/api/leads/:id/converter', tenantGuard, requireCapability('leads'), async (req, res) => { try { const clinicaId = req.clinicaId; if (!clinicaId) return res.status(400).json({ success: false, message: 'clinicaId obrigatório.' });