Unify metrics into a single dashboard scorecard.

Fold CRM and organic KPIs into Métricas and hide the duplicate Windsor strip when embedded.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-10-06 11:46:50 +02:00
parent ace73c2699
commit fb8adab162
2 changed files with 188 additions and 107 deletions

View File

@@ -11,6 +11,8 @@ import {
TrendingUp,
Users,
MousePointerClick,
Globe,
Search,
} from 'lucide-react';
import {
AdDailyRow,
@@ -300,6 +302,29 @@ export default function MetricsDashboard({
hasCohort && periodTotals.revenue <= 0 && cohortFromLeads;
const showWow = rangeDaySpan(since, until) <= 7;
const organicPeriod = useMemo(() => {
const rows = organicRows.filter((r) => {
const d = (r.day || r.date || '').slice(0, 10);
return d >= since && d <= until;
});
const gscClicks = rows.reduce((a, r) => a + (Number(r.gsc_clicks) || 0), 0);
const ga4Sessions = rows.reduce(
(a, r) => a + (Number(r.ga4_sessions) || 0),
0
);
const ga4Conversions = rows.reduce(
(a, r) => a + (Number(r.ga4_conversions) || 0),
0
);
const denom = periodTotals.leads + ga4Conversions;
return {
gscClicks,
ga4Sessions,
ga4Conversions,
organicShare: denom > 0 ? (ga4Conversions / denom) * 100 : 0,
};
}, [organicRows, since, until, periodTotals.leads]);
const cards: Card[] = [
{
key: 'spend',
@@ -311,7 +336,7 @@ export default function MetricsDashboard({
{
key: 'leads',
label: 'Leads plataforma',
hint: 'Conversiones reportadas por ads',
hint: 'Conversiones reportadas por ads (no CRM)',
format: formatNum,
icon: <Users className="w-4 h-4 text-blue-600" />,
},
@@ -424,41 +449,6 @@ export default function MetricsDashboard({
</p>
)}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
{[
{ label: 'Inversión periodo', value: formatEur(periodTotals.spend) },
{
label: 'Leads plataforma',
value: formatNum(periodTotals.leads),
},
{
label: 'Leads CRM',
value: hasCrm ? formatNum(crmPeriodTotal) : '—',
},
{
label: 'CPL CRM (paid)',
value:
hasCrm && crmPeriodTotal > 0
? `${formatNum(periodTotals.spend / crmPeriodTotal)} €`
: periodTotals.cpl
? `${formatNum(periodTotals.cpl)} €`
: '—',
},
].map((s) => (
<div
key={s.label}
className="rounded-xl border border-brand-200 bg-brand-50 px-4 py-3 shadow-sm"
>
<p className="text-[11px] font-semibold uppercase tracking-wider text-brand-700">
{s.label}
</p>
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
{s.value}
</p>
</div>
))}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4">
{cards.map((c) => {
const emptyCohort = Boolean(c.cohort) && !hasCohort;
@@ -493,6 +483,81 @@ export default function MetricsDashboard({
})}
</div>
<div className="grid grid-cols-2 lg:grid-cols-3 gap-3">
<div className="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm">
<p className="text-[11px] font-semibold uppercase tracking-wider text-gray-600">
Leads CRM
</p>
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
{hasCrm ? formatNum(crmPeriodTotal) : '—'}
</p>
<p className="text-xs text-gray-500 mt-1">
Altas Clientify en el periodo (no es el mismo número que ads)
</p>
</div>
<div className="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm">
<p className="text-[11px] font-semibold uppercase tracking-wider text-gray-600">
CPL CRM
</p>
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
{hasCrm && crmPeriodTotal > 0
? `${formatNum(periodTotals.spend / crmPeriodTotal)} €`
: '—'}
</p>
<p className="text-xs text-gray-500 mt-1">
Inversión ads ÷ leads CRM (distinto del CPL ads)
</p>
</div>
<div className="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm">
<p className="text-[11px] font-semibold uppercase tracking-wider text-gray-600">
Gap plat vs CRM
</p>
<p
className={`text-xl font-bold mt-1 tabular-nums ${
hasCrm ? gapClass(crmPeriodTotal - periodTotals.leads) : 'text-gray-400'
}`}
>
{hasCrm
? `${crmPeriodTotal - periodTotals.leads > 0 ? '+' : ''}${formatNum(crmPeriodTotal - periodTotals.leads)}`
: '—'}
</p>
<p className="text-xs text-gray-500 mt-1">
CRM − leads plataforma · detalle por canal abajo
</p>
</div>
<div className="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm">
<p className="text-[11px] font-semibold uppercase tracking-wider text-gray-600 inline-flex items-center gap-1">
<Search className="w-3.5 h-3.5" /> Clics SEO
</p>
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
{formatNum(organicPeriod.gscClicks)}
</p>
<p className="text-xs text-gray-500 mt-1">Search Console · mismo periodo</p>
</div>
<div className="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm">
<p className="text-[11px] font-semibold uppercase tracking-wider text-gray-600 inline-flex items-center gap-1">
<Globe className="w-3.5 h-3.5" /> Sesiones web
</p>
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
{formatNum(organicPeriod.ga4Sessions)}
</p>
<p className="text-xs text-gray-500 mt-1">
GA4 · {formatNum(organicPeriod.ga4Conversions)} conv. web
</p>
</div>
<div className="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm">
<p className="text-[11px] font-semibold uppercase tracking-wider text-gray-600">
Cuota orgánica
</p>
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
{formatPct(organicPeriod.organicShare)}
</p>
<p className="text-xs text-gray-500 mt-1">
Conv. GA4 / (leads ads + conv. web)
</p>
</div>
</div>
<section className="bg-white border border-gray-200 rounded-xl p-5 space-y-4 shadow-sm">
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-2">
<div>
@@ -739,8 +804,8 @@ export default function MetricsDashboard({
</p>
</section>
{/* Windsor.ai Data — follows Contexto tenant */}
<WindsorAiSection
embedded
tenantId={tenantId}
label={label}
portfolioWide={portfolioWide}