feat(agenda): 1h de espaçamento + filtro manhã/tarde no /slots

- Espaçamento/duração padrão do slot passa de 30 para 60 min (1h entre atendimentos).
- /slots aceita ?periodo=manha|tarde (a Secretária pergunta o período antes de oferecer
  e busca só aquele período).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-07-06 05:59:32 +02:00
parent ca2cfa1f35
commit 668d672531
+7 -3
View File
@@ -12,7 +12,7 @@ import express from 'express';
import { getConfig } from './config.js'; import { getConfig } from './config.js';
import { resolverFeriado, feriadoNacionalNome } from './feriados.js'; import { resolverFeriado, feriadoNacionalNome } from './feriados.js';
const SLOT_MIN = 30; // duração padrão do slot (min) const SLOT_MIN = 60; // duração/espaçamento padrão do slot (1h entre atendimentos)
const HORIZON_DAYS = 7; // janela padrão de busca const HORIZON_DAYS = 7; // janela padrão de busca
const MAX_SLOTS = 20; // quantos slots RETORNAR (após ordenar por horário) const MAX_SLOTS = 20; // quantos slots RETORNAR (após ordenar por horário)
const CANDIDATE_CAP = 240; // teto de GERAÇÃO (clínica + salas competem por horário) const CANDIDATE_CAP = 240; // teto de GERAÇÃO (clínica + salas competem por horário)
@@ -142,6 +142,7 @@ export function createAgendaBridge(pool) {
const duration = Math.max(10, parseInt(req.query.duration, 10) || SLOT_MIN); const duration = Math.max(10, parseInt(req.query.duration, 10) || SLOT_MIN);
const days = Math.min(30, Math.max(1, parseInt(req.query.days, 10) || HORIZON_DAYS)); const days = Math.min(30, Math.max(1, parseInt(req.query.days, 10) || HORIZON_DAYS));
const from = req.query.date ? new Date(`${req.query.date}T00:00:00`) : new Date(); const from = req.query.date ? new Date(`${req.query.date}T00:00:00`) : new Date();
const periodo = req.query.periodo ? String(req.query.periodo).toLowerCase() : null; // manha|tarde
try { try {
// Dentistas no escopo da clínica. // Dentistas no escopo da clínica.
@@ -243,8 +244,11 @@ export function createAgendaBridge(pool) {
} catch { /* sala_reservas pode não existir */ } } catch { /* sala_reservas pode não existir */ }
} }
slots.sort((a, b) => a.start.localeCompare(b.start)); let out = slots.sort((a, b) => a.start.localeCompare(b.start));
res.json({ slots: slots.slice(0, MAX_SLOTS) }); // Filtro manhã/tarde (a Secretária pergunta o período antes de oferecer).
if (periodo === 'manha' || periodo === 'manhã') out = out.filter((s) => parseInt(s.start.slice(11, 13), 10) < 12);
else if (periodo === 'tarde') out = out.filter((s) => parseInt(s.start.slice(11, 13), 10) >= 12);
res.json({ slots: out.slice(0, MAX_SLOTS) });
} catch (e) { } catch (e) {
res.status(500).json({ error: e.message }); res.status(500).json({ error: e.message });
} }