// src/pages/Rentabilidad.jsx
import { useState, useEffect, useMemo } from "react";
import { api } from "../lib/api";
import { Loader, ErrorBox, Filters, Select } from "../components/UI";
import Modal from "../components/Modal";
import { useColumnasAjustables } from "../lib/useColumnasAjustables";
const MESES = ["ENERO","FEBRERO","MARZO","ABRIL","MAYO","JUNIO","JULIO","AGOSTO","SEPTIEMBRE","OCTUBRE","NOVIEMBRE","DICIEMBRE"];
const ANOS = [2024, 2025, 2026];
const SEDES = ["TODOS","LIMA","AREQUIPA","PIURA","TRUJILLO"];
const PROGRAMAS = ["TODOS","SEMINARIOS","OTROS","TEAC","TERC"];
const COLS = ["PROGRAMA","FECHA INICIO","TOTAL","RETIRADOS","EN CURSO","CUOTA PROM.","PROM. DESC. E.","V. VENTA INICIAL","V. COSTO INICIAL","M.B. INICIAL %","V. VENTA ACTUAL","V. COSTO ACTUAL","M.B. ACTUAL %","OPC."];
// índices: inicial = 7,8,9 | actual = 10,11,12
const BG_INI = "#eef4ff", BG_ACT = "#edfff6";
function toNum(v){ const n=parseFloat(String(v).replace("S/","").replace(/,/g,"").replace("%","").trim()); return isNaN(n)?0:n; }
export default function Rentabilidad() {
const hoy = new Date();
const [ano, setAno] = useState(hoy.getFullYear());
const [mes, setMes] = useState(hoy.getMonth() + 1);
const [sede, setSede] = useState("TODOS");
const [programa, setPrograma] = useState("TODOS");
const [mostrarInscritos, setMostrarInscritos] = useState(true); // TOTAL, RETIRADOS, EN CURSO
const [filas, setFilas] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [modalVer, setModalVer] = useState(null);
// Índices de columnas TOTAL(2), RETIRADOS(3), EN CURSO(4) — se ocultan si el check está apagado
const IDX_INSCRITOS = [2, 3, 4];
const ocultar = (i) => !mostrarInscritos && IDX_INSCRITOS.includes(i);
const ANCHOS_BASE = [260, 110, 80, 90, 90, 110, 120, 120, 120, 110, 120, 120, 110, 80];
const anchosVisibles = ANCHOS_BASE.filter((_, i) => !ocultar(i));
const cols = useColumnasAjustables(anchosVisibles);
const [modalCostos, setModalCostos] = useState(null);
function cargar() {
setLoading(true); setError(null);
api.rentabilidad(ano, mes, sede, programa)
.then((res) => { setFilas(res.filas || []); setLoading(false); })
.catch((e) => { setError(e.message); setLoading(false); });
}
useEffect(() => {
let activo = true;
setLoading(true); setError(null);
api.rentabilidad(ano, mes, sede, programa)
.then((res) => { if (activo) { setFilas(res.filas || []); setLoading(false); } })
.catch((e) => { if (activo) { setError(e.message); setLoading(false); } });
return () => { activo = false; };
}, [ano, mes, sede, programa]);
const datos = useMemo(() => filas.filter((f)=>String(f[0]).toUpperCase()!=="TOTAL GENERAL"), [filas]);
const total = useMemo(() => filas.find((f)=>String(f[0]).toUpperCase()==="TOTAL GENERAL"), [filas]);
const kpis = useMemo(() => {
if (!total) return null;
return {
programas: datos.length,
enCurso: total[4],
ventaAct: total[10],
costoAct: total[11],
mbAct: total[12],
};
}, [datos, total]);
function bgCol(ci){ if([7,8,9].includes(ci))return BG_INI; if([10,11,12].includes(ci))return BG_ACT; return ""; }
return (
📈 Rentabilidad
{loading ?
:
error ?
:
<>
{kpis && (
{[["📚","Programas",kpis.programas,"este mes"],
["🎓","En Curso",kpis.enCurso,"alumnos activos"],
["💵","Venta Actual",kpis.ventaAct,"ingreso total"],
["💸","Costo Actual",kpis.costoAct,"gasto total"],
["📊","M.B. Actual",kpis.mbAct,"margen bruto"]].map(([ico,label,val,sub])=>(
))}
)}
{COLS.filter((_,i)=>!ocultar(i)).map((c, vi)=>| {c} | )}
{datos.map((f, ri) => (
{f.slice(0,13).map((v,ci)=>{
if (ocultar(ci)) return null;
const isPct = ci===9||ci===12;
return (
| {v} |
);
})}
|
))}
{total && (
{total.slice(0,13).map((v,ci)=>(
ocultar(ci) ? null :
| {v} |
))}
— |
)}
>}
{modalVer &&
setModalVer(null)} />}
{modalCostos && setModalCostos(null)} onGuardado={cargar} />}
);
}
function ModalVerDetalle({ programa, ano, mes, onClose }) {
const [filas, setFilas] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let activo = true; setLoading(true);
api.rentabilidadDetalle(programa, ano, mes)
.then((res)=>{ if(activo){setFilas(res.filas||[]);setLoading(false);} })
.catch(()=>{ if(activo){setFilas([]);setLoading(false);} });
return ()=>{activo=false;};
}, [programa, ano, mes]);
const COLS_DET = ["VENDEDOR","ALUMNO","F. MATRÍCULA","F. CANCELACIÓN","TIPO","VALOR CUOTA","PROM. DESC. E.","VALOR VENTA"];
return (
{loading ? :
filas.length===0 ? Sin alumnos.
:
{COLS_DET.map((c)=>| {c} | )}
{filas.map((f,i)=>(
{f.map((v,j)=>(
| =5?"right":(j===1?"left":"center")}}>{v} |
))}
))}
}
);
}
function ModalCostos({ programa, ano, mes, onClose, onGuardado }) {
const [costos, setCostos] = useState(null);
const [loading, setLoading] = useState(true);
const [guardando, setGuardando] = useState(false);
const [msg, setMsg] = useState(null);
// [keyGuardar, etiqueta, prefijoBackend]
const CAMPOS = [["epp","COSTO EPP","epp"],["certificado","COSTO CERTIFICADO","cert"],["docente","COSTO DOCENTE","doc"],["marketing","COSTO MARKETING","mkt"],["consumibles","COSTO CONSUMIBLES","cons"]];
const [ini, setIni] = useState({});
const [act, setAct] = useState({});
useEffect(() => {
let activo = true; setLoading(true);
api.rentabilidadCostos(programa, ano, mes)
.then((res)=>{
if(!activo) return;
const c = res.costos || {};
setCostos(c);
const gi={}, ga={};
CAMPOS.forEach(([k,,bp])=>{ gi[k]=+(c[`${bp}_inicial`]||0); ga[k]=+(c[`${bp}_actual`]||0); });
setIni(gi); setAct(ga); setLoading(false);
})
.catch(()=>{ if(activo){setCostos(null);setLoading(false);} });
return ()=>{activo=false;};
}, [programa, ano, mes]);
const totIni = Object.values(ini).reduce((a,b)=>a+(+b||0),0);
const totAct = Object.values(act).reduce((a,b)=>a+(+b||0),0);
async function guardar() {
setGuardando(true); setMsg(null);
try {
await api.guardarCostos(costos.num_indice, ini, act);
if (onGuardado) onGuardado(); // recarga la tabla principal
onClose(); // cierra el modal
} catch (e) {
setMsg({ ok:false, txt:"❌ Error al guardar. Verifica Supabase." });
setGuardando(false);
}
}
return (
{loading ? :
!costos ? Sin datos de costos.
:
<>
{msg && {msg.txt}
}
>}
);
}
function Columna({ titulo, color, campos, valores, setVal, total }) {
return (
{titulo}
{campos.map(([k,label])=>(
setVal({...valores,[k]:+e.target.value})}
style={{width:"100%",padding:"7px 10px",border:"1px solid #cbd5e1",borderRadius:8,fontSize:13}} />
))}
TOTAL: S/ {total.toLocaleString("es-PE",{maximumFractionDigits:0})}
);
}