import { auth } from "../lib/auth";
import { ownerFilter } from "../lib/authz";
import { getClosed, getMotivosCierre, getSystemUsers } from "../lib/queries";
import { agg, EJECUTIVOS_VENTA } from "../lib/derive";
import LoginPrompt from "../components/LoginPrompt";
import OwnerControl from "../components/OwnerControl";
import ErrorNotice from "../components/ErrorNotice";
import { DYN, money, 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")}`;
}
function Panel({ title, hint, rows }) {
const maxN = Math.max(...rows.map((r) => r.n), 1);
return (
{title}
{hint &&
{hint}
}
{rows.map((r) => (
))}
);
}
export default async function PerdemosPage({ searchParams }) {
const session = await auth();
if (!session?.email) return ;
const { owner } = await searchParams;
const scope = ownerFilter(session, owner);
const mes = currentMonth();
let lost, allUsers;
try {
[lost, allUsers] = await Promise.all([
getClosed(session.accessToken, scope, 2, mes),
getSystemUsers(session.accessToken),
]);
const motivos = await getMotivosCierre(lost.map((o) => o.id), session.accessToken);
lost = lost.map((o) => ({ ...o, motivo: motivos[o.id]?.motivo || "Sin registrar", competidor: motivos[o.id]?.competidor }));
} catch (error) {
return ;
}
const owners = allUsers.filter((u) => EJECUTIVOS_VENTA.includes(u.name));
const mrrPerdido = lost.reduce((a, o) => a + (o.est || 0), 0);
const clientesNuevos = lost.filter((o) => o.segmento === "Clientes Nuevos").length;
const carteraActiva = lost.filter((o) => o.segmento === "Cartera Activa").length;
return (
Por qué Perdemos — {mes}
{lost.length}
Oportunidades perdidas
{money(mrrPerdido)}
MRR estimado perdido
{clientesNuevos}
Clientes Nuevos
{carteraActiva}
Cartera Activa
No hay seguimiento registrado en el CRM ≠ no hubo seguimiento — la mayoría del contacto comercial se hace por
WhatsApp, que no está integrado a Dynamics (ver CLAUDE.md).
o.est)}
/>
o.competidor && o.competidor !== "Ninguno"), "competidor", (o) => o.est)} />
o.est)} />
o.est)} />
Detalle de oportunidades perdidas
Clic en el nombre abre la oportunidad en Dynamics 365
{lost.length === 0 ? (
No hay oportunidades perdidas en este período para este filtro.
) : (
| Cliente |
Oportunidad |
Seg. |
Motivo de cierre |
Línea de Negocio |
Competidor |
{!scope.locked && Ejecutivo | }
MRR est. |
{[...lost].sort((a, b) => (b.est || 0) - (a.est || 0)).map((o) => (
| {o.cliente || "—"} |
{o.name}
|
{o.segmento === "Cartera Activa" ? "CA" : "CN"}
|
{o.motivo} |
{o.linea || "—"} |
{o.competidor || "—"} |
{!scope.locked && {o.owner} | }
{o.est ? money2(o.est) : "—"} |
))}
)}
);
}