// Feriados nacionais brasileiros (fixos + móveis) + resolução com exceções da // clínica (tabela `feriados`). Usado pela agenda-bridge para a Secretária saber // quando é feriado e o /slots pular esses dias. // // Fixos: datas nacionais. Móveis: derivados da Páscoa (Computus de Meeus). // Consciência Negra (20/11) é nacional desde 2024 (Lei 14.759/2023). const pad = (n) => String(n).padStart(2, '0'); const ymd = (y, m, d) => `${y}-${pad(m)}-${pad(d)}`; // Domingo de Páscoa (algoritmo de Meeus/Jones/Butcher) — retorna {mes, dia}. function pascoa(ano) { const a = ano % 19; const b = Math.floor(ano / 100); const c = ano % 100; const d = Math.floor(b / 4); const e = b % 4; const f = Math.floor((b + 8) / 25); const g = Math.floor((b - f + 1) / 3); const h = (19 * a + b - d - g + 15) % 30; const i = Math.floor(c / 4); const k = c % 4; const l = (32 + 2 * e + 2 * i - h - k) % 7; const m = Math.floor((a + 11 * h + 22 * l) / 451); const mes = Math.floor((h + l - 7 * m + 114) / 31); const dia = ((h + l - 7 * m + 114) % 31) + 1; return { mes, dia }; } // Soma dias a uma data (UTC, evita fuso) → 'YYYY-MM-DD'. function addDias(y, m, d, delta) { const base = new Date(Date.UTC(y, m - 1, d)); base.setUTCDate(base.getUTCDate() + delta); return ymd(base.getUTCFullYear(), base.getUTCMonth() + 1, base.getUTCDate()); } // Mapa { 'YYYY-MM-DD': 'Nome do feriado' } dos feriados NACIONAIS do ano. export function feriadosNacionais(ano) { const p = pascoa(ano); const py = ano, pm = p.mes, pd = p.dia; const map = { [ymd(ano, 1, 1)]: 'Confraternização Universal', [ymd(ano, 4, 21)]: 'Tiradentes', [ymd(ano, 5, 1)]: 'Dia do Trabalho', [ymd(ano, 9, 7)]: 'Independência do Brasil', [ymd(ano, 10, 12)]: 'Nossa Senhora Aparecida', [ymd(ano, 11, 2)]: 'Finados', [ymd(ano, 11, 15)]: 'Proclamação da República', [ymd(ano, 11, 20)]: 'Consciência Negra', [ymd(ano, 12, 25)]: 'Natal', // Móveis (relativos ao Domingo de Páscoa) [addDias(py, pm, pd, -48)]: 'Carnaval (segunda-feira)', [addDias(py, pm, pd, -47)]: 'Carnaval (terça-feira)', [addDias(py, pm, pd, -2)]: 'Sexta-feira Santa', [addDias(py, pm, pd, 60)]: 'Corpus Christi', }; return map; } // Nome do feriado nacional numa data 'YYYY-MM-DD', ou null. export function feriadoNacionalNome(dateStr) { const ano = Number(String(dateStr).slice(0, 4)); if (!ano) return null; return feriadosNacionais(ano)[dateStr] ?? null; } // Resolve o feriado de uma data para uma clínica: nacional OU exceção da clínica // (tabela `feriados`, que também pode ANULAR um dia — ver `fecha`). Retorna // { fecha: bool, nome, hora_inicio?, hora_fim? } ou null (dia normal). // // Regras: // - Exceção da clínica tem prioridade (permite abrir num feriado nacional, ou // fechar num dia comum, ou definir horário especial). // - Sem exceção: cai no feriado nacional (fecha o dia inteiro). export async function resolverFeriado(pool, clinicaId, dateStr) { try { const mmdd = String(dateStr).slice(5); // 'MM-DD' const { rows } = await pool.query( `SELECT nome, fecha, hora_inicio, hora_fim FROM feriados WHERE clinica_id = $1 AND (data = $2 OR (recorrente = 1 AND to_char(data,'MM-DD') = $3)) ORDER BY (data = $2) DESC LIMIT 1`, [clinicaId, dateStr, mmdd]); if (rows.length) { const r = rows[0]; return { fecha: r.fecha !== false, nome: r.nome || 'Fechamento', hora_inicio: r.hora_inicio || null, hora_fim: r.hora_fim || null, origem: 'clinica' }; } } catch { /* tabela pode não existir ainda */ } const nac = feriadoNacionalNome(dateStr); if (nac) return { fecha: true, nome: nac, hora_inicio: null, hora_fim: null, origem: 'nacional' }; return null; }