"use client"; import { useCallback, useEffect, useState } from "react"; import { useCan } from "@/lib/abilities"; import { getNotificationSchedules, setNotificationSchedule, type NotificationSchedule, type NotificationSchedules, type ScheduleKind, } from "@/lib/api"; import { formatDateTime } from "@/lib/labels"; /** * When the two automatic envíos run. * * Both cadences used to be source code: pólizas barría a las 06:00 desde un * `@Cron` en el servidor y servicios no corría solo en absoluto. Cambiar * cualquiera de los dos era un redeploy. Ahora se guardan en `app_settings` y * el servidor reinstala el job al guardar — sin reinicio. * * Los flags de la tarjeta de arriba NO se aplican aquí: una corrida * automática siempre manda de verdad. */ const KIND_LABEL: Record = { servicios: "Servicios", polizas: "Pólizas", }; const KIND_HINT: Record = { servicios: "Ejecuta los cuatro envíos en orden, igual que el botón «Ejecutar todos». El estado de cuenta sigue respetando sus gates de lunes/miércoles/viernes.", polizas: "Barrido de avisos de renovación: 30 y 15 días antes del vencimiento, y 7 días después.", }; const DAYS = [ { value: 0, label: "Dom" }, { value: 1, label: "Lun" }, { value: 2, label: "Mar" }, { value: 3, label: "Mié" }, { value: 4, label: "Jue" }, { value: 5, label: "Vie" }, { value: 6, label: "Sáb" }, ]; function timeValue(s: NotificationSchedule): string { return `${String(s.hour).padStart(2, "0")}:${String(s.minute).padStart(2, "0")}`; } function describe(s: NotificationSchedule): string { if (!s.enabled) return "Desactivado — solo se envía manualmente."; const days = s.weekdays.length ? s.weekdays .map((d) => DAYS.find((x) => x.value === d)?.label ?? d) .join(", ") : "todos los días"; return `${days} a las ${timeValue(s)} (hora de Tijuana).`; } export function NotificationScheduleCard() { const canEdit = useCan("setting:manage"); const [schedules, setSchedules] = useState(null); const [drafts, setDrafts] = useState>>({}); const [editing, setEditing] = useState(null); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [saved, setSaved] = useState(null); const load = useCallback(async () => { try { setSchedules(await getNotificationSchedules()); setError(null); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } }, []); useEffect(() => { void load(); }, [load]); function startEdit(kind: ScheduleKind) { if (!schedules) return; setDrafts((d) => ({ ...d, [kind]: { ...schedules[kind].value } })); setEditing(kind); setSaved(null); setError(null); } async function save(kind: ScheduleKind) { const draft = drafts[kind]; if (!draft) return; setSaving(true); setError(null); try { const result = await setNotificationSchedule(kind, draft); setSchedules((prev) => (prev ? { ...prev, [kind]: result } : prev)); setEditing(null); setSaved(kind); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setSaving(false); } } if (!schedules) { return (

Programación de envíos

{error ? (
{error}
) : (

Cargando…

)}
); } return (

Programación de envíos

Cuándo corre solo cada envío. Los cambios aplican de inmediato, sin reiniciar el servidor. Una corrida automática nunca usa los flags de arriba: siempre manda a los clientes reales.

{error && (
{error}
)}
{(Object.keys(KIND_LABEL) as ScheduleKind[]).map((kind) => { const current = schedules[kind]; const draft = drafts[kind]; const isEditing = editing === kind && draft; return (
{KIND_LABEL[kind]}

{KIND_HINT[kind]}

{canEdit && !isEditing && ( )}
{isEditing ? (
Días (ninguno seleccionado = todos los días)
{DAYS.map((d) => { const on = draft.weekdays.includes(d.value); return ( ); })}
) : (

{describe(current.value)}

{current.cron} {current.nextRun && ` · próxima corrida: ${formatDateTime(current.nextRun)}`} {current.source === "default" && " · valor por omisión, nadie lo ha cambiado"} {current.updatedAt && ` · última edición: ${formatDateTime(current.updatedAt)}`} {saved === kind && " · guardado"}

)}
); })}
{!canEdit && (

Solo un ADMIN puede cambiar la programación.

)}
); }