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;
|
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}
|
||||||
|
|||||||
Reference in New Issue
Block a user