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:
@@ -11,6 +11,8 @@ import {
|
|||||||
TrendingUp,
|
TrendingUp,
|
||||||
Users,
|
Users,
|
||||||
MousePointerClick,
|
MousePointerClick,
|
||||||
|
Globe,
|
||||||
|
Search,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
AdDailyRow,
|
AdDailyRow,
|
||||||
@@ -300,6 +302,29 @@ export default function MetricsDashboard({
|
|||||||
hasCohort && periodTotals.revenue <= 0 && cohortFromLeads;
|
hasCohort && periodTotals.revenue <= 0 && cohortFromLeads;
|
||||||
const showWow = rangeDaySpan(since, until) <= 7;
|
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[] = [
|
const cards: Card[] = [
|
||||||
{
|
{
|
||||||
key: 'spend',
|
key: 'spend',
|
||||||
@@ -311,7 +336,7 @@ export default function MetricsDashboard({
|
|||||||
{
|
{
|
||||||
key: 'leads',
|
key: 'leads',
|
||||||
label: 'Leads plataforma',
|
label: 'Leads plataforma',
|
||||||
hint: 'Conversiones reportadas por ads',
|
hint: 'Conversiones reportadas por ads (no CRM)',
|
||||||
format: formatNum,
|
format: formatNum,
|
||||||
icon: <Users className="w-4 h-4 text-blue-600" />,
|
icon: <Users className="w-4 h-4 text-blue-600" />,
|
||||||
},
|
},
|
||||||
@@ -424,41 +449,6 @@ export default function MetricsDashboard({
|
|||||||
</p>
|
</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">
|
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||||
{cards.map((c) => {
|
{cards.map((c) => {
|
||||||
const emptyCohort = Boolean(c.cohort) && !hasCohort;
|
const emptyCohort = Boolean(c.cohort) && !hasCohort;
|
||||||
@@ -493,6 +483,81 @@ export default function MetricsDashboard({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</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">
|
<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 className="flex flex-col sm:flex-row sm:items-start justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
@@ -739,8 +804,8 @@ export default function MetricsDashboard({
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Windsor.ai Data — follows Contexto tenant */}
|
|
||||||
<WindsorAiSection
|
<WindsorAiSection
|
||||||
|
embedded
|
||||||
tenantId={tenantId}
|
tenantId={tenantId}
|
||||||
label={label}
|
label={label}
|
||||||
portfolioWide={portfolioWide}
|
portfolioWide={portfolioWide}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ type Props = {
|
|||||||
crmLeads?: CrmLeadRow[];
|
crmLeads?: CrmLeadRow[];
|
||||||
wonDeals?: DealWonRow[];
|
wonDeals?: DealWonRow[];
|
||||||
integrations?: any[];
|
integrations?: any[];
|
||||||
|
/** Same page as Métricas: sin caja Windsor ni KPIs duplicados. */
|
||||||
|
embedded?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GoogleCampaignAgg = {
|
type GoogleCampaignAgg = {
|
||||||
@@ -68,11 +70,14 @@ export default function WindsorAiSection({
|
|||||||
crmLeads = [],
|
crmLeads = [],
|
||||||
wonDeals = [],
|
wonDeals = [],
|
||||||
integrations = [],
|
integrations = [],
|
||||||
|
embedded = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { since, until, label: rangeLabel } = useDateRange();
|
const { since, until, label: rangeLabel } = useDateRange();
|
||||||
const [viewMode, setViewMode] = useState<'period' | 'all'>('period');
|
const [viewMode, setViewMode] = useState<'period' | 'all'>('period');
|
||||||
const [showAllCampaigns, setShowAllCampaigns] = useState(false);
|
const [showAllCampaigns, setShowAllCampaigns] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState<'all' | 'google' | 'organic' | 'matrix'>('all');
|
const [activeTab, setActiveTab] = useState<'google' | 'organic' | 'matrix'>(
|
||||||
|
'google'
|
||||||
|
);
|
||||||
|
|
||||||
const contextLabel =
|
const contextLabel =
|
||||||
(label && label.trim()) ||
|
(label && label.trim()) ||
|
||||||
@@ -409,9 +414,13 @@ export default function WindsorAiSection({
|
|||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
id="windsor-ai-section"
|
id="windsor-ai-section"
|
||||||
className="bg-white border-2 border-indigo-100 rounded-2xl p-5 sm:p-7 space-y-6 shadow-sm overflow-hidden relative"
|
className={
|
||||||
|
embedded
|
||||||
|
? 'space-y-5'
|
||||||
|
: 'bg-white border-2 border-indigo-100 rounded-2xl p-5 sm:p-7 space-y-6 shadow-sm overflow-hidden relative'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{/* Top Banner & Header */}
|
{!embedded ? (
|
||||||
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-4 border-b border-gray-100 pb-5">
|
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-4 border-b border-gray-100 pb-5">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -426,11 +435,11 @@ export default function WindsorAiSection({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-lg font-bold text-gray-900 mt-2 flex items-center gap-2">
|
<h3 className="text-lg font-bold text-gray-900 mt-2 flex items-center gap-2">
|
||||||
Atribución omnicanal · {contextLabel}
|
Desglose por fuente · {contextLabel}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-gray-600 mt-0.5">
|
<p className="text-xs text-gray-600 mt-0.5">
|
||||||
Atribución omnicanal y métricas agregadas multi-red (Google Ads, Meta Ads, GA4, Search
|
Lo de arriba es el scorecard ads+CRM. Aquí solo el detalle que no
|
||||||
Console y CRM) consolidadas en el warehouse de 4Ventures.
|
cabe ahí: campañas Google, SEO/GA4 y mix paid vs orgánico.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -460,8 +469,45 @@ export default function WindsorAiSection({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-semibold text-gray-900">
|
||||||
|
Detalle por fuente
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-gray-600 mt-0.5">
|
||||||
|
Campañas Google, SEO/GA4 y mix paid vs orgánico · mismo periodo
|
||||||
|
del header.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-100 p-1 rounded-lg flex items-center gap-1 border border-gray-200">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => 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})
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => 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
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Connectors Status Pills */}
|
{!embedded ? (
|
||||||
<div className="bg-slate-50 border border-slate-200/80 rounded-xl p-3">
|
<div className="bg-slate-50 border border-slate-200/80 rounded-xl p-3">
|
||||||
<p className="text-[11px] font-semibold uppercase tracking-wider text-slate-500 mb-2 flex items-center gap-1.5">
|
<p className="text-[11px] font-semibold uppercase tracking-wider text-slate-500 mb-2 flex items-center gap-1.5">
|
||||||
<Database className="w-3.5 h-3.5 text-indigo-600" />
|
<Database className="w-3.5 h-3.5 text-indigo-600" />
|
||||||
@@ -485,86 +531,55 @@ export default function WindsorAiSection({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top Blended KPI Cards */}
|
{/* Solo métricas que NO están en el scorecard ads+CRM de arriba */}
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
<div className="rounded-xl border border-indigo-200 bg-indigo-50/70 p-3.5 shadow-xs">
|
<div className="rounded-xl border border-emerald-200 bg-emerald-50/70 p-3.5">
|
||||||
<p className="text-[11px] font-semibold uppercase tracking-wider text-indigo-700 flex items-center gap-1">
|
<p className="text-[11px] font-semibold uppercase tracking-wider text-emerald-700">
|
||||||
<DollarSign className="w-3.5 h-3.5" />
|
Clics SEO (GSC)
|
||||||
Inversión Medios Blended
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
|
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
|
||||||
{formatEur(blendedTotals.totalMediaSpend)}
|
{formatNum(organicData.gscClicks)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-indigo-600 mt-1">
|
<p className="text-[10px] text-emerald-800 mt-1">
|
||||||
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'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="rounded-xl border border-sky-200 bg-sky-50/70 p-3.5">
|
||||||
<div className="rounded-xl border border-blue-200 bg-blue-50/70 p-3.5 shadow-xs">
|
<p className="text-[11px] font-semibold uppercase tracking-wider text-sky-700">
|
||||||
<p className="text-[11px] font-semibold uppercase tracking-wider text-blue-700 flex items-center gap-1">
|
Sesiones web (GA4)
|
||||||
<Globe className="w-3.5 h-3.5" />
|
|
||||||
Tráfico Multi-Red
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
|
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
|
||||||
{formatNum(blendedTotals.totalWebInteractions)}
|
{formatNum(organicData.ga4Sessions)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-blue-600 mt-1">
|
<p className="text-[10px] text-sky-800 mt-1">
|
||||||
Ads ({formatNum(blendedTotals.totalPaidClicks)}) + SEO ({formatNum(organicData.gscClicks)}) + Web ({formatNum(organicData.ga4Sessions)})
|
{ga4Id || 'Sin GA4 enlazado'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="rounded-xl border border-teal-200 bg-teal-50/70 p-3.5">
|
||||||
<div className="rounded-xl border border-emerald-200 bg-emerald-50/70 p-3.5 shadow-xs">
|
<p className="text-[11px] font-semibold uppercase tracking-wider text-teal-700">
|
||||||
<p className="text-[11px] font-semibold uppercase tracking-wider text-emerald-700 flex items-center gap-1">
|
Conv. web (GA4)
|
||||||
<Target className="w-3.5 h-3.5" />
|
|
||||||
Captación Omnicanal
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
|
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
|
||||||
{formatNum(Math.round(blendedTotals.totalConversions))}
|
{formatNum(organicData.ga4Conversions)}
|
||||||
</p>
|
|
||||||
<p className="text-[10px] text-emerald-600 mt-1">
|
|
||||||
{formatNum(Math.round(blendedTotals.totalPaidLeads))} leads ads + {formatNum(Math.round(organicData.ga4Conversions))} web
|
|
||||||
</p>
|
</p>
|
||||||
|
<p className="text-[10px] text-teal-800 mt-1">No son leads de ads</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="rounded-xl border border-amber-200 bg-amber-50/70 p-3.5">
|
||||||
<div className="rounded-xl border border-purple-200 bg-purple-50/70 p-3.5 shadow-xs">
|
<p className="text-[11px] font-semibold uppercase tracking-wider text-amber-700">
|
||||||
<p className="text-[11px] font-semibold uppercase tracking-wider text-purple-700 flex items-center gap-1">
|
Cuota orgánica
|
||||||
<TrendingUp className="w-3.5 h-3.5" />
|
|
||||||
CPL / CPA Blended
|
|
||||||
</p>
|
|
||||||
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
|
|
||||||
{blendedTotals.blendedCpl > 0 ? `${formatNum(blendedTotals.blendedCpl)} €` : '—'}
|
|
||||||
</p>
|
|
||||||
<p className="text-[10px] text-purple-600 mt-1">
|
|
||||||
Inversión media por conversión multi-canal
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rounded-xl border border-amber-200 bg-amber-50/70 p-3.5 shadow-xs col-span-2 sm:col-span-1">
|
|
||||||
<p className="text-[11px] font-semibold uppercase tracking-wider text-amber-700 flex items-center gap-1">
|
|
||||||
<Activity className="w-3.5 h-3.5" />
|
|
||||||
Cuota Orgánica (Free)
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
|
<p className="text-xl font-bold text-gray-900 mt-1 tabular-nums">
|
||||||
{formatPct(blendedTotals.organicAcquisitionShare)}
|
{formatPct(blendedTotals.organicAcquisitionShare)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-amber-700 mt-1">
|
<p className="text-[10px] text-amber-800 mt-1">
|
||||||
Conversiones generadas sin coste de pauta
|
Conv. GA4 / (leads ads + conv. web)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{/* Tabs navigation for in-depth Windsor data views */}
|
{/* Tabs: detalle, no scorecard duplicado */}
|
||||||
<div className="flex border-b border-gray-200 gap-4 text-sm font-medium">
|
<div className={`flex border-b border-gray-200 gap-4 text-sm font-medium ${embedded ? 'bg-white rounded-t-xl border-x border-t px-4 pt-3' : ''}`}>
|
||||||
<button
|
|
||||||
onClick={() => 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
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('google')}
|
onClick={() => setActiveTab('google')}
|
||||||
className={`pb-2.5 transition-colors relative flex items-center gap-1.5 ${
|
className={`pb-2.5 transition-colors relative flex items-center gap-1.5 ${
|
||||||
@@ -604,7 +619,7 @@ export default function WindsorAiSection({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* VIEW: GOOGLE ADS CAMPAIGNS (Windsor.ai Data missing from initial Cruce) */}
|
{/* VIEW: GOOGLE ADS CAMPAIGNS (Windsor.ai Data missing from initial Cruce) */}
|
||||||
{(activeTab === 'all' || activeTab === 'google') && (
|
{activeTab === 'google' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
@@ -744,7 +759,7 @@ export default function WindsorAiSection({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* VIEW: ORGANIC & WEB (GA4 + GSC from Windsor.ai pipeline) */}
|
{/* VIEW: ORGANIC & WEB (GA4 + GSC from Windsor.ai pipeline) */}
|
||||||
{(activeTab === 'all' || activeTab === 'organic') && (
|
{activeTab === 'organic' && (
|
||||||
<div className="space-y-4 pt-2">
|
<div className="space-y-4 pt-2">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
@@ -857,7 +872,7 @@ export default function WindsorAiSection({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* VIEW: OMNICHANNEL ATTRIBUTION MATRIX (Side-by-Side) */}
|
{/* VIEW: OMNICHANNEL ATTRIBUTION MATRIX (Side-by-Side) */}
|
||||||
{(activeTab === 'all' || activeTab === 'matrix') && (
|
{activeTab === 'matrix' && (
|
||||||
<div className="space-y-3 pt-2">
|
<div className="space-y-3 pt-2">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
@@ -925,7 +940,7 @@ export default function WindsorAiSection({
|
|||||||
<tfoot className="bg-indigo-50/60 border-t-2 border-indigo-200 font-bold text-gray-900">
|
<tfoot className="bg-indigo-50/60 border-t-2 border-indigo-200 font-bold text-gray-900">
|
||||||
<tr>
|
<tr>
|
||||||
<td className="py-2.5 px-3" colSpan={2}>
|
<td className="py-2.5 px-3" colSpan={2}>
|
||||||
Total Omnicanal Blended (Windsor.ai)
|
Total paid + orgánico
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2.5 px-3 text-right tabular-nums text-indigo-950">
|
<td className="py-2.5 px-3 text-right tabular-nums text-indigo-950">
|
||||||
{formatEur(blendedTotals.totalMediaSpend)}
|
{formatEur(blendedTotals.totalMediaSpend)}
|
||||||
@@ -950,7 +965,7 @@ export default function WindsorAiSection({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Windsor ETL Architecture Explanatory Note */}
|
{!embedded ? (
|
||||||
<div className="bg-slate-50 border border-slate-200 rounded-xl p-3.5 flex items-start gap-3">
|
<div className="bg-slate-50 border border-slate-200 rounded-xl p-3.5 flex items-start gap-3">
|
||||||
<Info className="w-4 h-4 text-indigo-600 shrink-0 mt-0.5" />
|
<Info className="w-4 h-4 text-indigo-600 shrink-0 mt-0.5" />
|
||||||
<div className="text-xs text-slate-600 space-y-1">
|
<div className="text-xs text-slate-600 space-y-1">
|
||||||
@@ -959,6 +974,7 @@ export default function WindsorAiSection({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user