import { useState, useEffect, useMemo, Fragment } from "react"; import { api } from "../lib/api"; import { Loader, ErrorBox, Filters, Select } from "../components/UI"; import { AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, ReferenceLine, } from "recharts"; const MESES = ["ENERO","FEBRERO","MARZO","ABRIL","MAYO","JUNIO","JULIO","AGOSTO","SEPTIEMBRE","OCTUBRE","NOVIEMBRE","DICIEMBRE"]; export default function Programa({ inicial = null, onConsumirInicial } = {}) { const _hoy = new Date(); const [ano, setAno] = useState(String(inicial?.ano ?? _hoy.getFullYear())); const [mes, setMes] = useState(String(inicial?.mes ?? (_hoy.getMonth() + 1))); const [programa, setPrograma] = useState("TODOS"); const [filtros, setFiltros] = useState({ anos: [2026] }); const [progOpc, setProgOpc] = useState([]); // lista de programas del periodo const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [recargando, setRecargando] = useState(false); const [error, setError] = useState(null); const [reload, setReload] = useState(0); // Programa que se debe seleccionar automaticamente al llegar por enlace (Leads). const [progPendiente, setProgPendiente] = useState(inicial?.programa || null); // Expansion de la tabla Estado/Objecion (mismo patron que el apartado Leads) const [expEstado, setExpEstado] = useState({}); // estado -> abierto const [expEstAse, setExpEstAse] = useState({}); // "estado|asesor" -> abierto useEffect(() => { api.leadsFiltros().then(setFiltros).catch(() => {}); }, []); useEffect(() => { const onActualizar = () => setReload((n) => n + 1); window.addEventListener("datos-actualizar", onActualizar); return () => window.removeEventListener("datos-actualizar", onActualizar); }, []); // Cargar la lista de programas cuando cambia año/mes; resetea el programa elegido. // NO depende de 'reload' para no perder el programa seleccionado al guardar leyenda. useEffect(() => { api.programaOpciones(ano, mes) .then((r) => setProgOpc(r.programas || [])) .catch(() => setProgOpc([])); // Si venimos por enlace desde Leads, NO reseteamos a TODOS (el otro effect lo // seleccionara cuando la lista de programas ya lo contenga). if (!progPendiente) { setPrograma("TODOS"); setData(null); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ano, mes]); // Al llegar por enlace: cuando la lista de programas ya incluye el pendiente, // seleccionarlo una sola vez y consumir el estado inicial. useEffect(() => { if (!progPendiente) return; if (progOpc.includes(progPendiente)) { setPrograma(progPendiente); setProgPendiente(null); if (onConsumirInicial) onConsumirInicial(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [progOpc, progPendiente]); // Al guardar leyenda (reload), refrescar la lista de programas SIN resetear el elegido. useEffect(() => { if (reload === 0) return; api.programaOpciones(ano, mes) .then((r) => setProgOpc(r.programas || [])) .catch(() => {}); // eslint-disable-next-line react-hooks/exhaustive-deps }, [reload]); // Cargar datos del programa elegido useEffect(() => { if (programa === "TODOS") { setData(null); return; } let activo = true; if (data) setRecargando(true); else setLoading(true); api.programa(ano, mes, programa) .then((r) => { if (activo) { setData(r); setError(null); } }) .catch((e) => { if (activo) setError(e.message); }) .finally(() => { if (activo) { setLoading(false); setRecargando(false); } }); return () => { activo = false; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [ano, mes, programa, reload]); const num = (v) => Number(v || 0).toLocaleString("es-PE"); const optAnos = useMemo(() => (filtros.anos || [2026]).map((a) => ({ value: String(a), label: String(a) })), [filtros]); const optMes = useMemo(() => MESES.map((m, i) => ({ value: String(i + 1), label: m })), []); const optProg = useMemo(() => [{ value: "TODOS", label: "— Elige un programa —" }, ...progOpc.map((p) => ({ value: p, label: p }))], [progOpc]); return (

📘 Programa