fix: no mezclar banners/datos ECP ↔ De Tú a Tú Formación
Aísla marcas hermanas en creatives, sync y social cache para que cada cuenta solo muestre sus propias campañas y creatividades. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
buildGoogleCardsFromDaily,
|
||||
fetchGoogleCreativeCards,
|
||||
} from '@/lib/ad-creatives-google';
|
||||
import { filterRowsForTenantBrand } from '@/lib/tenant-brand-aliases';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -80,8 +81,12 @@ export async function GET(req: NextRequest) {
|
||||
'date,platform,campaign_name,ad_id,ad_name,impressions,clicks,link_clicks,spend,platform_leads';
|
||||
const selectNoLc =
|
||||
'date,platform,campaign_name,ad_id,ad_name,impressions,clicks,spend,platform_leads';
|
||||
const tid = encodeURIComponent(ctx.tenantId);
|
||||
const slugRes = await restGet<{ slug?: string }[]>(
|
||||
`/rest/v1/tenants?id=eq.${tid}&select=slug&limit=1`
|
||||
);
|
||||
const tenantSlug = slugRes.data?.[0]?.slug || null;
|
||||
if (agency && ctx.viaView) {
|
||||
const tid = encodeURIComponent(ctx.tenantId);
|
||||
let d = await restGet<AdDailyRow[]>(
|
||||
`/rest/v1/ad_daily?tenant_id=eq.${tid}&date=gte.${since}&date=lte.${until}&select=${selectWithLc}&order=date.desc&limit=5000`
|
||||
);
|
||||
@@ -95,6 +100,7 @@ export async function GET(req: NextRequest) {
|
||||
let q: any = await supabase
|
||||
.from('ad_daily')
|
||||
.select(selectWithLc)
|
||||
.eq('tenant_id', ctx.tenantId)
|
||||
.gte('date', since)
|
||||
.lte('date', until)
|
||||
.order('date', { ascending: false })
|
||||
@@ -103,6 +109,7 @@ export async function GET(req: NextRequest) {
|
||||
q = await supabase
|
||||
.from('ad_daily')
|
||||
.select(selectNoLc)
|
||||
.eq('tenant_id', ctx.tenantId)
|
||||
.gte('date', since)
|
||||
.lte('date', until)
|
||||
.order('date', { ascending: false })
|
||||
@@ -110,6 +117,7 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
daily = (q.data as AdDailyRow[]) || [];
|
||||
}
|
||||
daily = filterRowsForTenantBrand(tenantSlug, daily);
|
||||
|
||||
const warnings: string[] = [];
|
||||
let metaAds: AdCreativeCard[] = [];
|
||||
@@ -123,7 +131,7 @@ export async function GET(req: NextRequest) {
|
||||
// Fast path: warehouse only — correct spend/clicks/leads from sync, no Graph wait
|
||||
if (mode === 'fast') {
|
||||
if (platform === 'meta' || platform === 'all') {
|
||||
metaAds = buildMetaWarehouseCards(daily, since, until, 16);
|
||||
metaAds = buildMetaWarehouseCards(daily, since, until, 16, tenantSlug);
|
||||
metaClickSplit = metaClickSplitFromDaily(daily, since, until);
|
||||
}
|
||||
if (platform === 'google' || platform === 'all') {
|
||||
@@ -152,6 +160,7 @@ export async function GET(req: NextRequest) {
|
||||
since,
|
||||
until,
|
||||
limit: 16,
|
||||
tenantSlug,
|
||||
}),
|
||||
fetchMetaAccountClickSplit({
|
||||
tenantId: ctx.tenantId,
|
||||
@@ -161,7 +170,7 @@ export async function GET(req: NextRequest) {
|
||||
]);
|
||||
metaAds = meta.ads.length
|
||||
? meta.ads
|
||||
: buildMetaWarehouseCards(daily, since, until, 16);
|
||||
: buildMetaWarehouseCards(daily, since, until, 16, tenantSlug);
|
||||
metaClickSplit =
|
||||
clickSplit || metaClickSplitFromDaily(daily, since, until);
|
||||
if (
|
||||
@@ -171,7 +180,7 @@ export async function GET(req: NextRequest) {
|
||||
warnings.push(meta.warning);
|
||||
}
|
||||
} catch {
|
||||
metaAds = buildMetaWarehouseCards(daily, since, until, 16);
|
||||
metaAds = buildMetaWarehouseCards(daily, since, until, 16, tenantSlug);
|
||||
metaClickSplit = metaClickSplitFromDaily(daily, since, until);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ import { createClient } from '@/lib/supabase/server';
|
||||
import { resolveActiveTenant, restGet } from '@/lib/tenant-context';
|
||||
import { isAgencyRole, serviceHeaders, supabaseUrl, userRole } from '@/lib/agency';
|
||||
import { resolveMetaAccessTokens } from '@/lib/ads-discovery';
|
||||
import {
|
||||
isForeignSiblingAccountId,
|
||||
isForeignSiblingBrandText,
|
||||
} from '@/lib/tenant-brand-aliases';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const maxDuration = 90;
|
||||
@@ -66,18 +70,39 @@ async function cacheSocialSnapshot(
|
||||
);
|
||||
}
|
||||
|
||||
function readSocialCache(integrations: Integ[]): {
|
||||
function readSocialCache(
|
||||
integrations: Integ[],
|
||||
tenantSlug?: string | null
|
||||
): {
|
||||
facebook?: unknown;
|
||||
instagram?: unknown;
|
||||
at?: string;
|
||||
} {
|
||||
const out: { facebook?: unknown; instagram?: unknown; at?: string } = {};
|
||||
for (const i of integrations) {
|
||||
if (i.status && i.status !== 'connected') continue;
|
||||
if (isForeignSiblingAccountId(tenantSlug, i.external_account_id)) continue;
|
||||
const cache = i.meta?.social_cache;
|
||||
const at = typeof i.meta?.social_cached_at === 'string'
|
||||
? i.meta.social_cached_at
|
||||
: i.last_sync_at || undefined;
|
||||
if (!cache) continue;
|
||||
const cacheObj = cache as {
|
||||
username?: string;
|
||||
name?: string;
|
||||
id?: string;
|
||||
};
|
||||
if (
|
||||
isForeignSiblingBrandText(
|
||||
tenantSlug,
|
||||
cacheObj.username,
|
||||
cacheObj.name,
|
||||
typeof i.meta?.account_name === 'string' ? i.meta.account_name : null,
|
||||
i.label
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (i.provider === 'facebook') out.facebook = cache;
|
||||
if (i.provider === 'instagram') out.instagram = cache;
|
||||
if (at && (!out.at || at > out.at)) out.at = at;
|
||||
@@ -803,6 +828,10 @@ export async function GET(req: NextRequest) {
|
||||
warning: 'Selecciona un cliente en Contexto',
|
||||
});
|
||||
}
|
||||
const slugRes = await restGet<{ slug?: string }[]>(
|
||||
`/rest/v1/tenants?id=eq.${encodeURIComponent(ctx.tenantId)}&select=slug&limit=1`
|
||||
);
|
||||
const tenantSlug = slugRes.data?.[0]?.slug || null;
|
||||
|
||||
const def = defaultRange();
|
||||
const sinceParam = req.nextUrl.searchParams.get('since') || '';
|
||||
@@ -839,20 +868,24 @@ export async function GET(req: NextRequest) {
|
||||
i.provider === 'facebook' &&
|
||||
i.status === 'connected' &&
|
||||
i.external_account_id &&
|
||||
!i.external_account_id.startsWith('http')
|
||||
!i.external_account_id.startsWith('http') &&
|
||||
!isForeignSiblingAccountId(tenantSlug, i.external_account_id) &&
|
||||
!isForeignSiblingBrandText(tenantSlug, i.label)
|
||||
);
|
||||
const igInteg = integrations.find(
|
||||
(i) =>
|
||||
i.provider === 'instagram' &&
|
||||
i.status === 'connected' &&
|
||||
i.external_account_id &&
|
||||
!i.external_account_id.startsWith('http')
|
||||
!i.external_account_id.startsWith('http') &&
|
||||
!isForeignSiblingAccountId(tenantSlug, i.external_account_id) &&
|
||||
!isForeignSiblingBrandText(tenantSlug, i.label)
|
||||
);
|
||||
|
||||
if (!tokens.length) {
|
||||
metrics.warning =
|
||||
'Sin token Meta: configura META_ACCESS_TOKEN (4V2030) o pégalo en Agencia.';
|
||||
const cached = readSocialCache(integrations);
|
||||
const cached = readSocialCache(integrations, tenantSlug);
|
||||
if (cached.facebook) metrics.facebook = cached.facebook;
|
||||
if (cached.instagram) metrics.instagram = cached.instagram;
|
||||
if (cached.facebook || cached.instagram) {
|
||||
@@ -896,7 +929,7 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
} catch (e) {
|
||||
metrics.warning = e instanceof Error ? e.message : 'metrics error';
|
||||
const cached = readSocialCache(integrations);
|
||||
const cached = readSocialCache(integrations, tenantSlug);
|
||||
if (cached.facebook && !metrics.facebook) metrics.facebook = cached.facebook;
|
||||
if (cached.instagram && !metrics.instagram)
|
||||
metrics.instagram = cached.instagram;
|
||||
|
||||
@@ -3,6 +3,11 @@ import { htmlTemplate } from './template';
|
||||
import { createClient } from '@/lib/supabase/server';
|
||||
import { resolveActiveTenant } from '@/lib/tenant-context';
|
||||
import { createClient as createSupabaseClient } from '@supabase/supabase-js';
|
||||
import {
|
||||
filterRowsForTenantBrand,
|
||||
isForeignSiblingAccountId,
|
||||
isForeignSiblingBrandText,
|
||||
} from '@/lib/tenant-brand-aliases';
|
||||
|
||||
export async function GET() {
|
||||
const supabase = await createClient();
|
||||
@@ -23,11 +28,19 @@ export async function GET() {
|
||||
|
||||
const adminSupabase = createSupabaseClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
|
||||
|
||||
const { data: adDaily } = await adminSupabase
|
||||
const { data: tenantRow } = await adminSupabase
|
||||
.from('tenants')
|
||||
.select('slug')
|
||||
.eq('id', tenant_id)
|
||||
.maybeSingle();
|
||||
const tenantSlug = (tenantRow as { slug?: string } | null)?.slug || null;
|
||||
|
||||
const { data: adDailyRaw } = await adminSupabase
|
||||
.from('ad_daily')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenant_id)
|
||||
.order('date', { ascending: true });
|
||||
const adDaily = filterRowsForTenantBrand(tenantSlug, adDailyRaw || []);
|
||||
|
||||
const { data: orgDaily } = await adminSupabase
|
||||
.from('organic_daily')
|
||||
@@ -45,9 +58,36 @@ export async function GET() {
|
||||
.from('integrations')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenant_id);
|
||||
|
||||
const igIntegration = integrations?.find((i: any) => i.provider === 'instagram');
|
||||
const fbIntegration = integrations?.find((i: any) => i.provider === 'facebook');
|
||||
const igIntegration = integrations?.find((i: any) => {
|
||||
if (i.provider !== 'instagram' || i.status !== 'connected') return false;
|
||||
if (isForeignSiblingAccountId(tenantSlug, i.external_account_id)) return false;
|
||||
if (
|
||||
isForeignSiblingBrandText(
|
||||
tenantSlug,
|
||||
i.label,
|
||||
i.meta?.account_name,
|
||||
i.meta?.social_cache?.username,
|
||||
i.meta?.social_cache?.name
|
||||
)
|
||||
)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
const fbIntegration = integrations?.find((i: any) => {
|
||||
if (i.provider !== 'facebook' || i.status !== 'connected') return false;
|
||||
if (isForeignSiblingAccountId(tenantSlug, i.external_account_id)) return false;
|
||||
if (
|
||||
isForeignSiblingBrandText(
|
||||
tenantSlug,
|
||||
i.label,
|
||||
i.meta?.account_name,
|
||||
i.meta?.social_cache?.username,
|
||||
i.meta?.social_cache?.name
|
||||
)
|
||||
)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
const igCache = igIntegration?.meta?.social_cache;
|
||||
const fbCache = fbIntegration?.meta?.social_cache;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user