fix: mostrar Google Ads desde ad_daily cuando kpi_weekly está vacío

kpi_weekly es una MV de solo lectura sin canales paid; canal/seguimiento
ahora hacen fallback a ad_daily. Bump Google Ads API a v21 en sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-23 12:13:41 +02:00
parent a1a00840d5
commit fba025da76
7 changed files with 159 additions and 24 deletions

View File

@@ -11,6 +11,7 @@ import {
AdDailyRow,
KpiWeeklyRow,
buildChannelSeries,
resolveWeeklyRows,
sumDaily,
sumWeekly,
} from '@/lib/metrics';
@@ -76,7 +77,7 @@ export async function GET() {
`/rest/v1/kpi_weekly?tenant_id=eq.${tid}&select=*&order=week.asc`
),
restGet<AdDailyRow[]>(
`/rest/v1/ad_daily?tenant_id=eq.${tid}&select=*&order=date.asc&limit=800`
`/rest/v1/ad_daily?tenant_id=eq.${tid}&select=*&order=date.desc&limit=5000`
),
]);
entries = e.data || [];
@@ -93,14 +94,15 @@ export async function GET() {
supabase
.from('ad_daily')
.select('*')
.order('date', { ascending: true })
.limit(800),
.order('date', { ascending: false })
.limit(5000),
]);
entries = (e as SeguimientoEntry[]) || [];
weekly = (w as KpiWeeklyRow[]) || [];
daily = (d as AdDailyRow[]) || [];
}
weekly = resolveWeeklyRows(weekly, daily);
const channelSeries = buildChannelSeries(weekly);
const totalsWeekly = sumWeekly(weekly);
const totalsDaily = sumDaily(daily);

View File

@@ -5,10 +5,12 @@ import { redirect } from 'next/navigation';
import { resolveActiveTenant, restGet } from '@/lib/tenant-context';
import { isAgencyRole, userRole } from '@/lib/agency';
import {
AdDailyRow,
KpiWeeklyRow,
buildChannelSeries,
formatEur,
formatNum,
resolveWeeklyRows,
} from '@/lib/metrics';
import { ArrowLeft } from 'lucide-react';
@@ -40,20 +42,33 @@ export default async function CanalDashPage({
}
let weekly: KpiWeeklyRow[] = [];
let daily: AdDailyRow[] = [];
if (agency && ctx.viaView && ctx.tenantId) {
const tid = encodeURIComponent(ctx.tenantId);
const w = await restGet<KpiWeeklyRow[]>(
`/rest/v1/kpi_weekly?tenant_id=eq.${tid}&select=*&order=week.asc`
);
const [w, d] = await Promise.all([
restGet<KpiWeeklyRow[]>(
`/rest/v1/kpi_weekly?tenant_id=eq.${tid}&select=*&order=week.asc`
),
restGet<AdDailyRow[]>(
`/rest/v1/ad_daily?tenant_id=eq.${tid}&select=*&order=date.desc&limit=5000`
),
]);
weekly = w.data || [];
daily = d.data || [];
} else {
const { data } = await supabase
.from('kpi_weekly')
.select('*')
.order('week', { ascending: true });
weekly = (data as KpiWeeklyRow[]) || [];
const [{ data: w }, { data: d }] = await Promise.all([
supabase.from('kpi_weekly').select('*').order('week', { ascending: true }),
supabase
.from('ad_daily')
.select('*')
.order('date', { ascending: false })
.limit(5000),
]);
weekly = (w as KpiWeeklyRow[]) || [];
daily = (d as AdDailyRow[]) || [];
}
weekly = resolveWeeklyRows(weekly, daily);
const series = buildChannelSeries(weekly);
const points = series.points.filter((p) => p.channel === channel);
const spend = points.reduce((a, p) => a + p.spend, 0);

View File

@@ -8,9 +8,9 @@ import {
KpiWeeklyRow,
buildChannelSeries,
buildComparisons,
resolveWeeklyRows,
sumDaily,
sumWeekly,
weeklyFromDaily,
} from '@/lib/metrics';
import { isAgencyRole, userRole } from '@/lib/agency';
@@ -65,13 +65,7 @@ export default async function KpisPage() {
daily = (d as AdDailyRow[]) || [];
}
// kpi_weekly is often a stale MV — prefer ad_daily when it has more signal
const fromDaily = weeklyFromDaily(daily);
const weeklySpend = weekly.reduce((a, r) => a + Number(r.total_spend || 0), 0);
const dailySpend = daily.reduce((a, r) => a + Number(r.spend || 0), 0);
if (fromDaily.length && dailySpend >= weeklySpend) {
weekly = fromDaily;
}
weekly = resolveWeeklyRows(weekly, daily);
const comparisons = buildComparisons(weekly);
const month =