diff --git a/backend/server.js b/backend/server.js index e3986c8..61ad1b2 100644 --- a/backend/server.js +++ b/backend/server.js @@ -2042,7 +2042,7 @@ app.delete('/api/dentistas/:id', authGuard, requireCapabilityRow('dentistas', 'd }); // --- GESTÃO DE HORÁRIOS --- -app.get('/api/dentistas/:dentistaId/horarios', authGuard, async (req, res) => { +app.get('/api/dentistas/:dentistaId/horarios', authGuard, requireCapability('agenda'), async (req, res) => { try { if (!await assertRowScoped(req, res, 'SELECT clinica_id AS cid FROM dentistas WHERE id = $1', [req.params.dentistaId])) return; const { dentistaId } = req.params; @@ -2059,7 +2059,7 @@ app.get('/api/dentistas/:dentistaId/horarios', authGuard, async (req, res) => { } catch (err) { res.status(500).json({ error: err.message }); } }); -app.post('/api/dentistas/horarios', authGuard, async (req, res) => { +app.post('/api/dentistas/horarios', authGuard, requireCapability('agenda'), async (req, res) => { try { if (!await assertCidScoped(req, res, req.body?.clinica_id)) return; const { id, dentista_id, clinica_id, dia_semana, hora_inicio, hora_fim, ativo } = req.body; @@ -2075,7 +2075,7 @@ app.post('/api/dentistas/horarios', authGuard, async (req, res) => { } catch (err) { res.status(500).json({ error: err.message }); } }); -app.delete('/api/dentistas/horarios/:id', authGuard, async (req, res) => { +app.delete('/api/dentistas/horarios/:id', authGuard, requireCapabilityRow('agenda', 'dentistas_horarios'), async (req, res) => { try { if (!await assertRowScoped(req, res, 'SELECT clinica_id AS cid FROM dentistas_horarios WHERE id = $1', [req.params.id])) return; await pool.query('DELETE FROM dentistas_horarios WHERE id = $1', [req.params.id]); @@ -3010,7 +3010,7 @@ app.get('/api/avaliacoes', authGuard, tenantGuard, async (req, res) => { }); // Avaliação ligada a um agendamento (o dentista abre pelo slot de avaliação). -app.get('/api/agendamentos/:id/avaliacao', authGuard, async (req, res) => { +app.get('/api/agendamentos/:id/avaliacao', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { if (!await loadAgendamentoScoped(req, res)) return; const { rows } = await pool.query('SELECT * FROM avaliacoes WHERE agendamento_id = $1 ORDER BY created_at DESC LIMIT 1', [req.params.id]); @@ -3100,7 +3100,7 @@ app.delete('/api/avaliacoes/fotos/:id', authGuard, async (req, res) => { }); // Histórico (auditoria) de um agendamento. -app.get('/api/agendamentos/:id/historico', authGuard, async (req, res) => { +app.get('/api/agendamentos/:id/historico', authGuard, requireCapabilityRow('agenda', 'agendamentos'), async (req, res) => { try { if (!await loadAgendamentoScoped(req, res)) return; const { rows } = await pool.query( @@ -3111,7 +3111,7 @@ app.get('/api/agendamentos/:id/historico', authGuard, async (req, res) => { }); // Pendências "a reagendar" da clínica (default: abertas). -app.get('/api/agenda/pendencias', authGuard, async (req, res) => { +app.get('/api/agenda/pendencias', authGuard, requireCapability('agenda'), async (req, res) => { try { const { clinicaId, status } = req.query; if (!clinicaId) return res.json({ success: true, pendencias: [] }); @@ -3123,7 +3123,7 @@ app.get('/api/agenda/pendencias', authGuard, async (req, res) => { }); // Baixa manual de pendência ("não vai reagendar" / dispensar). -app.post('/api/agenda/pendencias/:id/resolver', authGuard, async (req, res) => { +app.post('/api/agenda/pendencias/:id/resolver', authGuard, requireCapabilityRow('agenda', 'agenda_pendencias'), async (req, res) => { try { const actor = req.authUser?.userId || null; const actorNm = await actorNome(actor); @@ -3135,7 +3135,7 @@ app.post('/api/agenda/pendencias/:id/resolver', authGuard, async (req, res) => { }); // Contagem de faltas de um paciente. -app.get('/api/pacientes/:id/faltas', authGuard, async (req, res) => { +app.get('/api/pacientes/:id/faltas', authGuard, requireCapabilityRow('agenda', 'pacientes'), async (req, res) => { try { if (!await assertRowScoped(req, res, 'SELECT clinica_id AS cid FROM pacientes WHERE id = $1', [req.params.id])) return; const { rows } = await pool.query(`SELECT count(*)::int AS faltas FROM agendamentos WHERE pacienteid=$1 AND lower(status)='falta'`, [req.params.id]); @@ -3145,7 +3145,7 @@ app.get('/api/pacientes/:id/faltas', authGuard, async (req, res) => { // Score do paciente: faltas (no-show sem aviso = ponto negativo) + remarcações (não pontua, // mas em grande número = atenção). Remarcações vêm do HISTÓRICO (audit_log 'remarcou'), durável. -app.get('/api/pacientes/:id/score', authGuard, async (req, res) => { +app.get('/api/pacientes/:id/score', authGuard, requireCapabilityRow('agenda', 'pacientes'), async (req, res) => { try { if (!await assertRowScoped(req, res, 'SELECT clinica_id AS cid FROM pacientes WHERE id = $1', [req.params.id])) return; const pid = req.params.id; @@ -3164,7 +3164,7 @@ app.get('/api/pacientes/:id/score', authGuard, async (req, res) => { // --- PRESENÇA NA AGENDA (Fase 2) --- // Heartbeat: registra a última visita (DB, persistente) + presença AO VIVO (Dragonfly, TTL). -app.post('/api/agenda/presenca', authGuard, async (req, res) => { +app.post('/api/agenda/presenca', authGuard, requireCapability('agenda'), async (req, res) => { try { const actor = req.authUser?.userId || null; const { clinicaId } = req.body || {}; @@ -3185,7 +3185,7 @@ app.post('/api/agenda/presenca', authGuard, async (req, res) => { }); // Quem está ONLINE agora (Dragonfly, janela de 45s) + últimas visitas (DB). -app.get('/api/agenda/presenca', authGuard, async (req, res) => { +app.get('/api/agenda/presenca', authGuard, requireCapability('agenda'), async (req, res) => { try { const { clinicaId } = req.query; if (!clinicaId) return res.json({ success: true, online: [], ultimas: [] }); @@ -3210,7 +3210,7 @@ app.get('/api/agenda/presenca', authGuard, async (req, res) => { }); // Feed de ATIVIDADE da equipe (auditoria da clínica) — default: hoje. -app.get('/api/agenda/atividade', authGuard, async (req, res) => { +app.get('/api/agenda/atividade', authGuard, requireCapability('agenda'), async (req, res) => { try { const { clinicaId, desde } = req.query; if (!clinicaId) return res.json({ success: true, atividade: [] }); @@ -3228,7 +3228,7 @@ app.get('/api/agenda/atividade', authGuard, async (req, res) => { // Importa agendamentos do Google Calendar p/ DENTRO do sistema (viram agendamentos editáveis). // Janela: últimos 7 dias + próximos N dias (default 60). Idempotente (dedup por google_event_id). -app.post('/api/agenda/importar-google', authGuard, async (req, res) => { +app.post('/api/agenda/importar-google', authGuard, requireCapability('agenda'), async (req, res) => { try { const { clinicaId, dias } = req.body || {}; if (!clinicaId) return res.status(400).json({ success: false, message: 'clinicaId obrigatório.' }); @@ -3277,7 +3277,7 @@ app.post('/api/agenda/importar-google', authGuard, async (req, res) => { }); // Grupo familiar do paciente (para chips compactos no modal de agenda). -app.get('/api/pacientes/:id/familia', authGuard, async (req, res) => { +app.get('/api/pacientes/:id/familia', authGuard, requireCapabilityRow('agenda', 'pacientes'), async (req, res) => { try { if (!await assertRowScoped(req, res, 'SELECT clinica_id AS cid FROM pacientes WHERE id = $1', [req.params.id])) return; const { rows: pr } = await pool.query('SELECT grupofamiliarid FROM pacientes WHERE id = $1', [req.params.id]); @@ -3292,7 +3292,7 @@ app.get('/api/pacientes/:id/familia', authGuard, async (req, res) => { // --- INTERESSES DE AGENDA (Fase 4) --- // Fila de interesse quando o horário do profissional está ocupado (não trava o slot). -app.post('/api/interesses', authGuard, async (req, res) => { +app.post('/api/interesses', authGuard, requireCapability('agenda'), async (req, res) => { try { const { dentistaId, profissionalUsuarioId, clinicaInteressadaId, solicitanteUsuarioId, start, end, tempoEsperaMin, observacoes } = req.body; if (!clinicaInteressadaId || !start) return res.status(400).json({ success: false, message: 'clinicaInteressadaId e start são obrigatórios.' }); @@ -3329,7 +3329,7 @@ app.post('/api/interesses', authGuard, async (req, res) => { } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); -app.get('/api/interesses', authGuard, async (req, res) => { +app.get('/api/interesses', authGuard, requireCapability('agenda'), async (req, res) => { try { const { profissionalId, clinicaId } = req.query; const uid = req.authUser?.userId;