Show Google Ads platform leads separately from CRM-sourced leads.

Clarifies BPS case: Google Ads conversions exist while Clientify has no source=google contacts; ventas stay on ecommerce until UTM/gclid maps the deal.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-10-08 10:35:11 +02:00
parent b53161dc4d
commit b4e843762e

View File

@@ -116,9 +116,24 @@ export default function CrmDashboard({
ventasTotal > 0 && spendTotal > 0 ? spendTotal / ventasTotal : 0; ventasTotal > 0 && spendTotal > 0 ? spendTotal / ventasTotal : 0;
const cacByChannel = useMemo(() => { const cacByChannel = useMemo(() => {
const m = new Map<string, { cac: number; spend: number; ventas: number }>(); const m = new Map<
string,
{
cac: number;
spend: number;
ventas: number;
platformLeads: number;
importe: number;
}
>();
for (const r of attribution) { for (const r of attribution) {
m.set(r.channel, { cac: r.cac, spend: r.spend, ventas: r.ventas }); m.set(r.channel, {
cac: r.cac,
spend: r.spend,
ventas: r.ventas,
platformLeads: r.platformLeads,
importe: r.importe,
});
} }
return m; return m;
}, [attribution]); }, [attribution]);
@@ -189,9 +204,15 @@ export default function CrmDashboard({
<HelpDetails title="Cómo leer este CRM"> <HelpDetails title="Cómo leer este CRM">
<p>{LENS_HELP[lens]}</p> <p>{LENS_HELP[lens]}</p>
<p> <p>
El % a cliente es del CRM, no de ads. El{' '} <strong>Leads ads</strong> = conversiones que reporta Meta/Google Ads.{' '}
<strong>CAC</strong> = inversión ads del periodo ÷ ventas ganadas <strong>Leads CRM</strong> = contactos en Clientify/GHL con esa fuente.
(deals won) del periodo; por canal usa la atribución del deal. Si Google Ads tiene leads pero el CRM no, el contacto llegó sin UTM /
source=google (hay que mapear la fuente en el CRM).
</p>
<p>
<strong>Ventas / CAC</strong> usan deals won del CRM. Un pedido
WooCommerce suele entrar como <em>ecommerce</em>, aunque el clic haya
sido de Google Brand — sin UTM/gclid no se reasigna a Google.
</p> </p>
</HelpDetails> </HelpDetails>
@@ -274,7 +295,7 @@ export default function CrmDashboard({
<SectionHead <SectionHead
icon={<Target className="w-4 h-4" />} icon={<Target className="w-4 h-4" />}
title="CAC por canal" title="CAC por canal"
hint="Inversión ads ÷ ventas won atribuidas al canal (mismo criterio que Métricas)." hint="Leads ads = conversiones Meta/Google. Ventas = deals won en CRM con esa fuente."
/> />
<Surface padded={false}> <Surface padded={false}>
{!attribution.some((r) => r.spend > 0 || r.ventas > 0) ? ( {!attribution.some((r) => r.spend > 0 || r.ventas > 0) ? (
@@ -288,7 +309,8 @@ export default function CrmDashboard({
<tr> <tr>
<th className="py-3 px-4 font-semibold">Canal</th> <th className="py-3 px-4 font-semibold">Canal</th>
<th className="py-3 pr-4 font-semibold">Inversión</th> <th className="py-3 pr-4 font-semibold">Inversión</th>
<th className="py-3 pr-4 font-semibold">Ventas</th> <th className="py-3 pr-4 font-semibold">Leads ads</th>
<th className="py-3 pr-4 font-semibold">Ventas CRM</th>
<th className="py-3 pr-4 font-semibold">CAC</th> <th className="py-3 pr-4 font-semibold">CAC</th>
<th className="py-3 pr-4 font-semibold">Importe</th> <th className="py-3 pr-4 font-semibold">Importe</th>
</tr> </tr>
@@ -307,6 +329,11 @@ export default function CrmDashboard({
<td className="py-3 pr-4 tabular-nums text-gray-800"> <td className="py-3 pr-4 tabular-nums text-gray-800">
{row.spend > 0 ? formatEur(row.spend) : '—'} {row.spend > 0 ? formatEur(row.spend) : '—'}
</td> </td>
<td className="py-3 pr-4 tabular-nums text-gray-800">
{row.platformLeads > 0
? formatNum(Math.round(row.platformLeads))
: '—'}
</td>
<td className="py-3 pr-4 tabular-nums text-gray-800"> <td className="py-3 pr-4 tabular-nums text-gray-800">
{row.ventas > 0 ? formatNum(row.ventas) : '—'} {row.ventas > 0 ? formatNum(row.ventas) : '—'}
</td> </td>
@@ -325,6 +352,13 @@ export default function CrmDashboard({
<td className="py-3 pr-4 tabular-nums"> <td className="py-3 pr-4 tabular-nums">
{spendTotal > 0 ? formatEur(spendTotal) : '—'} {spendTotal > 0 ? formatEur(spendTotal) : '—'}
</td> </td>
<td className="py-3 pr-4 tabular-nums">
{formatNum(
Math.round(
attribution.reduce((a, r) => a + r.platformLeads, 0)
)
)}
</td>
<td className="py-3 pr-4 tabular-nums"> <td className="py-3 pr-4 tabular-nums">
{ventasTotal > 0 ? formatNum(ventasTotal) : '—'} {ventasTotal > 0 ? formatNum(ventasTotal) : '—'}
</td> </td>
@@ -350,7 +384,7 @@ export default function CrmDashboard({
<SectionHead <SectionHead
icon={<GitCompare className="w-4 h-4" />} icon={<GitCompare className="w-4 h-4" />}
title="% a cliente por canal" title="% a cliente por canal"
hint="Estado ganado en el CRM. Ads spend y CAC del periodo son referencia." hint="Leads CRM = fuente en Clientify. Leads ads = conversiones de la plataforma. No tienen por qué coincidir."
/> />
<Surface padded={false}> <Surface padded={false}>
{!channelRowsWithAds.length ? ( {!channelRowsWithAds.length ? (
@@ -363,18 +397,20 @@ export default function CrmDashboard({
<thead className={tableHeadClass()}> <thead className={tableHeadClass()}>
<tr> <tr>
<th className="py-3 px-4 font-semibold">Canal</th> <th className="py-3 px-4 font-semibold">Canal</th>
<th className="py-3 pr-4 font-semibold min-w-[160px]"> <th className="py-3 pr-4 font-semibold">Leads ads</th>
Leads <th className="py-3 pr-4 font-semibold min-w-[140px]">
Leads CRM
</th> </th>
<th className="py-3 pr-4 font-semibold">Clientes</th> <th className="py-3 pr-4 font-semibold">Clientes</th>
<th className="py-3 pr-4 font-semibold">% a cliente</th> <th className="py-3 pr-4 font-semibold">% a cliente</th>
<th className="py-3 pr-4 font-semibold">Inversión ads</th> <th className="py-3 pr-4 font-semibold">Inversión</th>
<th className="py-3 pr-4 font-semibold">CAC</th> <th className="py-3 pr-4 font-semibold">CAC</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{channelRowsWithAds.map((row) => { {channelRowsWithAds.map((row) => {
const cacRow = cacByChannel.get(row.channel); const cacRow = cacByChannel.get(row.channel);
const plat = Math.round(cacRow?.platformLeads || 0);
return ( return (
<tr <tr
key={row.channel} key={row.channel}
@@ -383,6 +419,9 @@ export default function CrmDashboard({
<td className="py-3 px-4 font-semibold capitalize text-gray-800"> <td className="py-3 px-4 font-semibold capitalize text-gray-800">
{row.channel} {row.channel}
</td> </td>
<td className="py-3 pr-4 tabular-nums text-gray-800">
{plat > 0 ? formatNum(plat) : '—'}
</td>
<td className="py-3 pr-4"> <td className="py-3 pr-4">
<ShareBar <ShareBar
value={row.leads} value={row.leads}