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:
4Ventures
2026-09-28 10:23:36 +02:00
parent dd50251de2
commit cf27eba74d
30 changed files with 2367 additions and 514 deletions

75
scripts/sync_ads_only.ts Normal file
View File

@@ -0,0 +1,75 @@
/**
* Sync Meta + Google Ads → ad_daily (+ kpi_weekly rollup).
*
* Usage:
* npx tsx scripts/sync_ads_only.ts # last 30d all tenants
* DAYS=640 npx tsx scripts/sync_ads_only.ts # backfill ~from Jan 2025
* TENANT_SLUG=bps DAYS=640 npx tsx ...
*/
import { config } from 'dotenv';
import { resolve } from 'path';
config({ path: resolve(process.cwd(), '.env.local') });
import {
ADS_BACKFILL_MAX_DAYS,
syncAdsForTenants,
} from '../src/lib/ads-sync';
async function main() {
const slug = process.env.TENANT_SLUG?.trim();
let tenantId: string | undefined;
if (slug) {
const { serviceHeaders, supabaseUrl } = await import('../src/lib/agency');
const headers = serviceHeaders()!;
const r = await fetch(
`${supabaseUrl()}/rest/v1/tenants?slug=eq.${encodeURIComponent(slug)}&select=id&limit=1`,
{ headers }
);
const rows = (await r.json()) as { id: string }[];
tenantId = rows[0]?.id;
if (!tenantId) throw new Error(`Tenant slug not found: ${slug}`);
}
// Days since 2025-01-01 (inclusive) when DAYS unset and BACKFILL=1
const backfill = process.env.BACKFILL === '1' || process.env.BACKFILL === 'true';
const daysFrom2025 = Math.ceil(
(Date.now() - Date.UTC(2025, 0, 1)) / 86400000
);
const rawDays = Number(process.env.DAYS);
const days = Math.min(
ADS_BACKFILL_MAX_DAYS,
Math.max(
1,
Number.isFinite(rawDays) && rawDays > 0
? rawDays
: backfill
? daysFrom2025
: 30
)
);
console.log(
`Ads sync · days=${days} · since≈${new Date(Date.now() - days * 86400000).toISOString().slice(0, 10)}` +
(tenantId ? ` · tenant=${slug || tenantId}` : ' · all tenants')
);
const { results } = await syncAdsForTenants({ tenantId, days });
let ok = 0;
let err = 0;
for (const x of results) {
const rows = x.metaRows + x.googleRows;
if (rows > 0) ok += 1;
if (x.errors.length) err += 1;
console.log(
`${x.tenantName}: meta=${x.metaRows} google=${x.googleRows} weekly=${x.weeklyRows}` +
(x.errors.length
? ` · ERR ${x.errors.join('; ').slice(0, 200)}`
: '')
);
}
console.log(`\nDone · synced=${ok} withErrors=${err} / ${results.length}`);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});