informe-comercial-alfa/app/page.js
2026-09-21 17:11:53 -06:00

99 lines
3.5 KiB
JavaScript

import { auth } from "./lib/auth";
import { ownerFilter } from "./lib/authz";
import { getClosed, getSystemUsers } from "./lib/queries";
import { TIPOS_META, EJECUTIVOS_VENTA } from "./lib/derive";
import { getMetas, getMetaGlobal } from "./lib/metas";
import LoginPrompt from "./components/LoginPrompt";
import OwnerControl from "./components/OwnerControl";
import ErrorNotice from "./components/ErrorNotice";
import { money2 } from "./lib/constants";
export const dynamic = "force-dynamic";
function currentMonth() {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
}
export default async function Home({ searchParams }) {
const session = await auth();
if (!session?.email) return <LoginPrompt />;
const { owner } = await searchParams;
const scope = ownerFilter(session, owner);
const mes = currentMonth();
let won, lost, allUsers;
try {
[won, lost, allUsers] = await Promise.all([
getClosed(session.accessToken, scope, 1, mes),
getClosed(session.accessToken, scope, 2, mes),
getSystemUsers(session.accessToken),
]);
} catch (error) {
return <ErrorNotice error={error} />;
}
const owners = allUsers.filter((u) => EJECUTIVOS_VENTA.includes(u.name));
const sumW = won.reduce((a, o) => a + (o.actual || 0), 0);
const sumWMeta = won
.filter((o) => TIPOS_META.includes(o.tipo))
.reduce((a, o) => a + (o.actual || 0), 0);
const arpu = won.length ? sumW / won.length : 0;
const conv = won.length + lost.length ? Math.round((won.length / (won.length + lost.length)) * 100) : 0;
const metas = getMetas();
const viewedEmail = scope.locked ? session.email : scope.email;
const metaDen = viewedEmail ? metas[viewedEmail.toLowerCase()] || null : getMetaGlobal();
const cumpl = metaDen ? Math.round((sumWMeta / metaDen) * 100) : null;
return (
<div style={{ padding: 24 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 12 }}>
<h2>Resumen {mes}</h2>
<OwnerControl
owners={owners}
current={scope.locked ? session.email : scope.email}
locked={scope.locked}
lockedLabel={session.email}
/>
</div>
<div className="kpis">
<div className="kpi">
<span className="n">{money2(sumW)}</span>
<span className="l">Ingresos reales (ganadas)</span>
</div>
<div className="kpi">
<span className="n">{won.length}</span>
<span className="l">Oportunidades ganadas</span>
</div>
<div className="kpi">
<span className="n">{money2(arpu)}</span>
<span className="l">ARPU logrado</span>
</div>
<div className="kpi">
<span className="n pierde">{lost.length}</span>
<span className="l">Oportunidades perdidas</span>
</div>
<div className="kpi">
<span className="n">{conv}%</span>
<span className="l">% Conversión (ganadas/total)</span>
</div>
<div className="kpi">
<span className={cumpl != null && cumpl < 70 ? "n pierde" : "n"}>
{cumpl != null ? cumpl + "%" : "n/d"}
</span>
<span className="l">
Cumplimiento de meta{metaDen ? ` (${money2(metaDen)})` : " (sin meta asignada)"}
</span>
</div>
</div>
<p className="hint">
Cumplimiento de meta cuenta solo Venta - Cliente Nuevo, Up-Selling, Cross-Selling y Plan de Cuenta (se
excluyen renovaciones y SAC).
</p>
</div>
);
}