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:
@@ -116,9 +116,24 @@ export default function CrmDashboard({
|
||||
ventasTotal > 0 && spendTotal > 0 ? spendTotal / ventasTotal : 0;
|
||||
|
||||
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) {
|
||||
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;
|
||||
}, [attribution]);
|
||||
@@ -189,9 +204,15 @@ export default function CrmDashboard({
|
||||
<HelpDetails title="Cómo leer este CRM">
|
||||
<p>{LENS_HELP[lens]}</p>
|
||||
<p>
|
||||
El % a cliente es del CRM, no de ads. El{' '}
|
||||
<strong>CAC</strong> = inversión ads del periodo ÷ ventas ganadas
|
||||
(deals won) del periodo; por canal usa la atribución del deal.
|
||||
<strong>Leads ads</strong> = conversiones que reporta Meta/Google Ads.{' '}
|
||||
<strong>Leads CRM</strong> = contactos en Clientify/GHL con esa fuente.
|
||||
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>
|
||||
</HelpDetails>
|
||||
|
||||
@@ -274,7 +295,7 @@ export default function CrmDashboard({
|
||||
<SectionHead
|
||||
icon={<Target className="w-4 h-4" />}
|
||||
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}>
|
||||
{!attribution.some((r) => r.spend > 0 || r.ventas > 0) ? (
|
||||
@@ -288,7 +309,8 @@ export default function CrmDashboard({
|
||||
<tr>
|
||||
<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">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">Importe</th>
|
||||
</tr>
|
||||
@@ -307,6 +329,11 @@ export default function CrmDashboard({
|
||||
<td className="py-3 pr-4 tabular-nums text-gray-800">
|
||||
{row.spend > 0 ? formatEur(row.spend) : '—'}
|
||||
</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">
|
||||
{row.ventas > 0 ? formatNum(row.ventas) : '—'}
|
||||
</td>
|
||||
@@ -325,6 +352,13 @@ export default function CrmDashboard({
|
||||
<td className="py-3 pr-4 tabular-nums">
|
||||
{spendTotal > 0 ? formatEur(spendTotal) : '—'}
|
||||
</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">
|
||||
{ventasTotal > 0 ? formatNum(ventasTotal) : '—'}
|
||||
</td>
|
||||
@@ -350,7 +384,7 @@ export default function CrmDashboard({
|
||||
<SectionHead
|
||||
icon={<GitCompare className="w-4 h-4" />}
|
||||
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}>
|
||||
{!channelRowsWithAds.length ? (
|
||||
@@ -363,18 +397,20 @@ export default function CrmDashboard({
|
||||
<thead className={tableHeadClass()}>
|
||||
<tr>
|
||||
<th className="py-3 px-4 font-semibold">Canal</th>
|
||||
<th className="py-3 pr-4 font-semibold min-w-[160px]">
|
||||
Leads
|
||||
<th className="py-3 pr-4 font-semibold">Leads ads</th>
|
||||
<th className="py-3 pr-4 font-semibold min-w-[140px]">
|
||||
Leads CRM
|
||||
</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">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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{channelRowsWithAds.map((row) => {
|
||||
const cacRow = cacByChannel.get(row.channel);
|
||||
const plat = Math.round(cacRow?.platformLeads || 0);
|
||||
return (
|
||||
<tr
|
||||
key={row.channel}
|
||||
@@ -383,6 +419,9 @@ export default function CrmDashboard({
|
||||
<td className="py-3 px-4 font-semibold capitalize text-gray-800">
|
||||
{row.channel}
|
||||
</td>
|
||||
<td className="py-3 pr-4 tabular-nums text-gray-800">
|
||||
{plat > 0 ? formatNum(plat) : '—'}
|
||||
</td>
|
||||
<td className="py-3 pr-4">
|
||||
<ShareBar
|
||||
value={row.leads}
|
||||
|
||||
Reference in New Issue
Block a user