Include paid ad channels with spend in CRM % a cliente table.

Google (and other channels with ads but no CRM-sourced leads) now appear with 0 leads so investment and CAC stay visible.

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

View File

@@ -123,19 +123,42 @@ export default function CrmDashboard({
return m; return m;
}, [attribution]); }, [attribution]);
const paidChannels = channelRows.filter((r) => // Include paid channels that have ad spend/ventas even with 0 CRM leads
['meta', 'google'].includes(r.channel) // (e.g. Google Ads spend but no leads sourced as "google" in Clientify).
const channelRowsWithAds: CrmChannelStats[] = useMemo(() => {
const byCh = new Map(channelRows.map((r) => [r.channel, r]));
for (const r of attribution) {
if (r.spend <= 0 && r.ventas <= 0) continue;
if (byCh.has(r.channel)) continue;
byCh.set(r.channel, {
channel: r.channel,
leads: 0,
cliente: 0,
enOportunidad: 0,
leadFrio: 0,
leadPerdido: 0,
other: 0,
clienteRate: 0,
});
}
const all = [...byCh.values()];
const paid = all
.filter((r) => ['meta', 'google'].includes(r.channel))
.sort((a, b) => {
const sa = cacByChannel.get(a.channel)?.spend || 0;
const sb = cacByChannel.get(b.channel)?.spend || 0;
return sb - sa || b.leads - a.leads;
});
const other = all
.filter((r) => !['meta', 'google'].includes(r.channel))
.sort((a, b) => b.cliente - a.cliente || b.leads - a.leads);
return [...paid, ...other];
}, [channelRows, attribution, cacByChannel]);
const leadMax = Math.max(
1,
...channelRowsWithAds.map((r) => r.leads)
); );
const otherChannels = channelRows.filter(
(r) => !['meta', 'google'].includes(r.channel)
);
// Channels with ads spend / ventas but maybe no CRM lead row in this lens
const extraCacChannels = attribution.filter(
(r) =>
(r.spend > 0 || r.ventas > 0) &&
!channelRows.some((c) => c.channel === r.channel)
);
const leadMax = Math.max(1, ...channelRows.map((r) => r.leads));
const statusMax = Math.max(1, ...statusRows.map((r) => r.count)); const statusMax = Math.max(1, ...statusRows.map((r) => r.count));
return ( return (
@@ -330,9 +353,9 @@ export default function CrmDashboard({
hint="Estado ganado en el CRM. Ads spend y CAC del periodo son referencia." hint="Estado ganado en el CRM. Ads spend y CAC del periodo son referencia."
/> />
<Surface padded={false}> <Surface padded={false}>
{!channelRows.length && !extraCacChannels.length ? ( {!channelRowsWithAds.length ? (
<p className="text-sm text-gray-500 py-10 text-center"> <p className="text-sm text-gray-500 py-10 text-center">
Sin leads en esta lente/periodo. Sin leads ni gasto ads en esta lente/periodo.
</p> </p>
) : ( ) : (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
@@ -350,7 +373,7 @@ export default function CrmDashboard({
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{[...paidChannels, ...otherChannels].map((row) => { {channelRowsWithAds.map((row) => {
const cacRow = cacByChannel.get(row.channel); const cacRow = cacByChannel.get(row.channel);
return ( return (
<tr <tr
@@ -375,7 +398,9 @@ export default function CrmDashboard({
row.clienteRate row.clienteRate
)}`} )}`}
> >
{row.clienteRate.toFixed(1)}% {row.leads > 0
? `${row.clienteRate.toFixed(1)}%`
: '—'}
</td> </td>
<td className="py-3 pr-4 tabular-nums text-gray-700"> <td className="py-3 pr-4 tabular-nums text-gray-700">
{cacRow && cacRow.spend > 0 {cacRow && cacRow.spend > 0