feat: KPIs con totales absolutos, backfill ads y ventas en cruce canal
Comparativas WoW/MoM/YoY muestran prev/Δ además del %; sync ads admite histórico desde 2025; tabla ads↔CRM añade cierres, ventas y ROAS. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
buildComparisons,
|
||||
mergeDealsIntoWeekly,
|
||||
resolveWeeklyRows,
|
||||
resolveWonCohort,
|
||||
sumDaily,
|
||||
sumWeekly,
|
||||
} from '@/lib/metrics';
|
||||
@@ -19,12 +20,13 @@ import { isAgencyRole, serviceHeaders, supabaseUrl, userRole } from '@/lib/agenc
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
/** Paginate CRM leads (source + created_at) for attribution cross. */
|
||||
/** Paginate CRM leads (source + created_at + status) for attribution + cohort. */
|
||||
async function fetchCrmLeads(
|
||||
tenantId: string | null,
|
||||
useServiceRole: boolean
|
||||
): Promise<CrmLeadRow[]> {
|
||||
const select = 'source,created_at';
|
||||
const select = 'source,created_at,status,tenant_id,updated_at,status_changed_at';
|
||||
const selectFallback = 'source,created_at,status,tenant_id';
|
||||
const pageSize = 1000;
|
||||
const out: CrmLeadRow[] = [];
|
||||
|
||||
@@ -35,12 +37,20 @@ async function fetchCrmLeads(
|
||||
const tidFilter = tenantId
|
||||
? `&tenant_id=eq.${encodeURIComponent(tenantId)}`
|
||||
: '';
|
||||
let useSelect = select;
|
||||
for (let offset = 0; offset < 50000; offset += pageSize) {
|
||||
const r = await fetch(
|
||||
`${base}/rest/v1/leads?select=${select}${tidFilter}&crm=eq.clientify&order=created_at.desc&limit=${pageSize}&offset=${offset}`,
|
||||
`${base}/rest/v1/leads?select=${useSelect}${tidFilter}&crm=eq.clientify&order=created_at.desc&limit=${pageSize}&offset=${offset}`,
|
||||
{ headers, cache: 'no-store' }
|
||||
);
|
||||
if (!r.ok) break;
|
||||
if (!r.ok) {
|
||||
if (useSelect !== selectFallback && offset === 0) {
|
||||
useSelect = selectFallback;
|
||||
offset = -pageSize;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
const chunk = (await r.json()) as CrmLeadRow[];
|
||||
out.push(...chunk);
|
||||
if (chunk.length < pageSize) break;
|
||||
@@ -50,12 +60,23 @@ async function fetchCrmLeads(
|
||||
|
||||
const supabase = await createClient();
|
||||
for (let offset = 0; offset < 50000; offset += pageSize) {
|
||||
const { data } = await supabase
|
||||
let { data, error } = await supabase
|
||||
.from('leads')
|
||||
.select(select)
|
||||
.eq('crm', 'clientify')
|
||||
.order('created_at', { ascending: false })
|
||||
.range(offset, offset + pageSize - 1);
|
||||
if (error && offset === 0) {
|
||||
const retry = await supabase
|
||||
.from('leads')
|
||||
.select(selectFallback)
|
||||
.eq('crm', 'clientify')
|
||||
.order('created_at', { ascending: false })
|
||||
.range(offset, offset + pageSize - 1);
|
||||
data = retry.data;
|
||||
error = retry.error;
|
||||
}
|
||||
if (error) break;
|
||||
const chunk = (data as CrmLeadRow[]) || [];
|
||||
out.push(...chunk);
|
||||
if (chunk.length < pageSize) break;
|
||||
@@ -163,6 +184,9 @@ export default async function KpisPage() {
|
||||
wonDeals = await fetchWonDeals(ctx.tenantId, false);
|
||||
}
|
||||
|
||||
const cohort = resolveWonCohort(wonDeals, crmLeads);
|
||||
wonDeals = cohort.deals;
|
||||
|
||||
weekly = mergeDealsIntoWeekly(resolveWeeklyRows(weekly, daily), wonDeals);
|
||||
|
||||
const comparisons = buildComparisons(weekly);
|
||||
@@ -188,6 +212,7 @@ export default async function KpisPage() {
|
||||
dailyRows={daily}
|
||||
crmLeads={crmLeads}
|
||||
wonDeals={wonDeals}
|
||||
cohortSource={cohort.source}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user