From fb8adab162cc9c0e3c2eb15c65fe234c75e52371 Mon Sep 17 00:00:00 2001
From: 4Ventures <4ventures@MacBook-Air-de-4Ventures.local>
Date: Tue, 6 Oct 2026 11:46:50 +0200
Subject: [PATCH] Unify metrics into a single dashboard scorecard.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fold CRM and organic KPIs into Métricas and hide the duplicate Windsor strip when embedded.
Co-authored-by: Cursor
---
src/components/MetricsDashboard.tsx | 139 ++++++++++++++++++-------
src/components/WindsorAiSection.tsx | 156 +++++++++++++++-------------
2 files changed, 188 insertions(+), 107 deletions(-)
diff --git a/src/components/MetricsDashboard.tsx b/src/components/MetricsDashboard.tsx
index beaf8a2..8b325b8 100644
--- a/src/components/MetricsDashboard.tsx
+++ b/src/components/MetricsDashboard.tsx
@@ -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: ,
},
@@ -424,41 +449,6 @@ export default function MetricsDashboard({
)}
-
- {[
- { 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) => (
-
-
- {s.label}
-
-
- {s.value}
-
-
- ))}
-
-
{cards.map((c) => {
const emptyCohort = Boolean(c.cohort) && !hasCohort;
@@ -493,6 +483,81 @@ export default function MetricsDashboard({
})}
+
+
+
+ Leads CRM
+
+
+ {hasCrm ? formatNum(crmPeriodTotal) : '—'}
+
+
+ Altas Clientify en el periodo (no es el mismo número que ads)
+
+
+
+
+ CPL CRM
+
+
+ {hasCrm && crmPeriodTotal > 0
+ ? `${formatNum(periodTotals.spend / crmPeriodTotal)} €`
+ : '—'}
+
+
+ Inversión ads ÷ leads CRM (distinto del CPL ads)
+
+
+
+
+ Gap plat vs CRM
+
+
+ {hasCrm
+ ? `${crmPeriodTotal - periodTotals.leads > 0 ? '+' : ''}${formatNum(crmPeriodTotal - periodTotals.leads)}`
+ : '—'}
+
+
+ CRM − leads plataforma · detalle por canal abajo
+
+
+
+
+ Clics SEO
+
+
+ {formatNum(organicPeriod.gscClicks)}
+
+
Search Console · mismo periodo
+
+
+
+ Sesiones web
+
+
+ {formatNum(organicPeriod.ga4Sessions)}
+
+
+ GA4 · {formatNum(organicPeriod.ga4Conversions)} conv. web
+
+
+
+
+ Cuota orgánica
+
+
+ {formatPct(organicPeriod.organicShare)}
+
+
+ Conv. GA4 / (leads ads + conv. web)
+
+
+
+
@@ -739,8 +804,8 @@ export default function MetricsDashboard({
- {/* Windsor.ai Data — follows Contexto tenant */}
('period');
const [showAllCampaigns, setShowAllCampaigns] = useState(false);
- const [activeTab, setActiveTab] = useState<'all' | 'google' | 'organic' | 'matrix'>('all');
+ const [activeTab, setActiveTab] = useState<'google' | 'organic' | 'matrix'>(
+ 'google'
+ );
const contextLabel =
(label && label.trim()) ||
@@ -409,9 +414,13 @@ export default function WindsorAiSection({
return (
- {/* Top Banner & Header */}
+ {!embedded ? (
@@ -426,11 +435,11 @@ export default function WindsorAiSection({
- Atribución omnicanal · {contextLabel}
+ Desglose por fuente · {contextLabel}
- Atribución omnicanal y métricas agregadas multi-red (Google Ads, Meta Ads, GA4, Search
- Console y CRM) consolidadas en el warehouse de 4Ventures.
+ Lo de arriba es el scorecard ads+CRM. Aquí solo el detalle que no
+ cabe ahí: campañas Google, SEO/GA4 y mix paid vs orgánico.
@@ -460,8 +469,45 @@ export default function WindsorAiSection({
+ ) : (
+
+
+
+ Detalle por fuente
+
+
+ Campañas Google, SEO/GA4 y mix paid vs orgánico · mismo periodo
+ del header.
+
+
+
+ setViewMode('period')}
+ className={`px-3 py-1 text-xs font-semibold rounded-md ${
+ viewMode === 'period'
+ ? 'bg-white text-gray-900 shadow-xs'
+ : 'text-gray-600'
+ }`}
+ >
+ Periodo ({rangeLabel})
+
+ setViewMode('all')}
+ className={`px-3 py-1 text-xs font-semibold rounded-md ${
+ viewMode === 'all'
+ ? 'bg-white text-gray-900 shadow-xs'
+ : 'text-gray-600'
+ }`}
+ >
+ Histórico
+
+
+
+ )}
- {/* Connectors Status Pills */}
+ {!embedded ? (
@@ -485,86 +531,55 @@ export default function WindsorAiSection({
- {/* Top Blended KPI Cards */}
-
-
-
-
- Inversión Medios Blended
+ {/* Solo métricas que NO están en el scorecard ads+CRM de arriba */}
+
+
+
+ Clics SEO (GSC)
- {formatEur(blendedTotals.totalMediaSpend)}
+ {formatNum(organicData.gscClicks)}
-
- Meta ({googleData.totalSpend > 0 ? Math.round((metaData.spend / blendedTotals.totalMediaSpend) * 100) : 100}%) + Google Ads ({googleData.totalSpend > 0 ? Math.round((googleData.totalSpend / blendedTotals.totalMediaSpend) * 100) : 0}%)
+
+ {gscSite || 'Search Console'}
-
-
-
-
- Tráfico Multi-Red
+
+
+ Sesiones web (GA4)
- {formatNum(blendedTotals.totalWebInteractions)}
+ {formatNum(organicData.ga4Sessions)}
-
- Ads ({formatNum(blendedTotals.totalPaidClicks)}) + SEO ({formatNum(organicData.gscClicks)}) + Web ({formatNum(organicData.ga4Sessions)})
+
+ {ga4Id || 'Sin GA4 enlazado'}
-
-
-
-
- Captación Omnicanal
+
+
+ Conv. web (GA4)
- {formatNum(Math.round(blendedTotals.totalConversions))}
-
-
- {formatNum(Math.round(blendedTotals.totalPaidLeads))} leads ads + {formatNum(Math.round(organicData.ga4Conversions))} web
+ {formatNum(organicData.ga4Conversions)}
+
No son leads de ads
-
-
-
-
- CPL / CPA Blended
-
-
- {blendedTotals.blendedCpl > 0 ? `${formatNum(blendedTotals.blendedCpl)} €` : '—'}
-
-
- Inversión media por conversión multi-canal
-
-
-
-
-
-
- Cuota Orgánica (Free)
+
+
+ Cuota orgánica
{formatPct(blendedTotals.organicAcquisitionShare)}
-
- Conversiones generadas sin coste de pauta
+
+ Conv. GA4 / (leads ads + conv. web)
+ ) : null}
- {/* Tabs navigation for in-depth Windsor data views */}
-
-
setActiveTab('all')}
- className={`pb-2.5 transition-colors relative ${
- activeTab === 'all'
- ? 'text-indigo-600 border-b-2 border-indigo-600 font-semibold'
- : 'text-gray-500 hover:text-gray-800'
- }`}
- >
- Vista General Completa
-
+ {/* Tabs: detalle, no scorecard duplicado */}
+
setActiveTab('google')}
className={`pb-2.5 transition-colors relative flex items-center gap-1.5 ${
@@ -604,7 +619,7 @@ export default function WindsorAiSection({
{/* VIEW: GOOGLE ADS CAMPAIGNS (Windsor.ai Data missing from initial Cruce) */}
- {(activeTab === 'all' || activeTab === 'google') && (
+ {activeTab === 'google' && (
@@ -744,7 +759,7 @@ export default function WindsorAiSection({
)}
{/* VIEW: ORGANIC & WEB (GA4 + GSC from Windsor.ai pipeline) */}
- {(activeTab === 'all' || activeTab === 'organic') && (
+ {activeTab === 'organic' && (
@@ -857,7 +872,7 @@ export default function WindsorAiSection({
)}
{/* VIEW: OMNICHANNEL ATTRIBUTION MATRIX (Side-by-Side) */}
- {(activeTab === 'all' || activeTab === 'matrix') && (
+ {activeTab === 'matrix' && (
@@ -925,7 +940,7 @@ export default function WindsorAiSection({
- Total Omnicanal Blended (Windsor.ai)
+ Total paid + orgánico
{formatEur(blendedTotals.totalMediaSpend)}
@@ -950,7 +965,7 @@ export default function WindsorAiSection({
)}
- {/* Windsor ETL Architecture Explanatory Note */}
+ {!embedded ? (
@@ -959,6 +974,7 @@ export default function WindsorAiSection({
+ ) : null}
);
}