From eb00256bea40da3d7334163c7e3dc3ec207af921 Mon Sep 17 00:00:00 2001
From: 4Ventures <4ventures@MacBook-Air-de-4Ventures.local>
Date: Tue, 22 Sep 2026 18:58:29 +0200
Subject: [PATCH] feat: add Instagram, Facebook, TikTok and LinkedIn slots
Organic social channels per client in the agency hub, separate from
paid Meta Ads.
Co-authored-by: Cursor
---
sql/social_integrations.sql | 36 ++++++++++++++++++++++
src/app/api/admin/create-client/route.ts | 4 +++
src/components/AgencyPortfolioBoard.tsx | 16 +++++-----
src/components/ClientIntegrationsPanel.tsx | 14 +++++++++
src/lib/agency.ts | 4 +++
5 files changed, 66 insertions(+), 8 deletions(-)
create mode 100644 sql/social_integrations.sql
diff --git a/sql/social_integrations.sql b/sql/social_integrations.sql
new file mode 100644
index 0000000..8d2f740
--- /dev/null
+++ b/sql/social_integrations.sql
@@ -0,0 +1,36 @@
+-- Organic social connection slots per tenant
+-- Idempotent. (Distinct from meta_ads / paid.)
+
+alter table public.integrations drop constraint if exists integrations_provider_check;
+alter table public.integrations
+ add constraint integrations_provider_check
+ check (provider in (
+ 'meta_ads',
+ 'google_ads',
+ 'ga4',
+ 'search_console',
+ 'tag_manager',
+ 'clientify',
+ 'klaviyo',
+ 'mailchimp',
+ 'google_drive',
+ 'wordpress',
+ 'prestashop',
+ 'shopify',
+ 'instagram',
+ 'facebook',
+ 'tiktok',
+ 'linkedin'
+ ));
+
+insert into public.integrations (tenant_id, provider, status, label)
+select t.id, p.provider, 'disconnected', p.label
+from public.tenants t
+cross join (
+ values
+ ('instagram', 'Instagram'),
+ ('facebook', 'Facebook Page'),
+ ('tiktok', 'TikTok'),
+ ('linkedin', 'LinkedIn')
+) as p(provider, label)
+on conflict (tenant_id, provider) do nothing;
diff --git a/src/app/api/admin/create-client/route.ts b/src/app/api/admin/create-client/route.ts
index 5338681..110e5ff 100644
--- a/src/app/api/admin/create-client/route.ts
+++ b/src/app/api/admin/create-client/route.ts
@@ -58,6 +58,10 @@ export async function POST(req: NextRequest) {
['wordpress', 'WordPress'],
['prestashop', 'PrestaShop'],
['shopify', 'Shopify'],
+ ['instagram', 'Instagram'],
+ ['facebook', 'Facebook Page'],
+ ['tiktok', 'TikTok'],
+ ['linkedin', 'LinkedIn'],
];
await fetch(`${url}/rest/v1/integrations`, {
method: 'POST',
diff --git a/src/components/AgencyPortfolioBoard.tsx b/src/components/AgencyPortfolioBoard.tsx
index 17850a9..20baeb3 100644
--- a/src/components/AgencyPortfolioBoard.tsx
+++ b/src/components/AgencyPortfolioBoard.tsx
@@ -54,18 +54,18 @@ type AlertRow = {
const MATRIX_PROVIDERS = [
'meta_ads',
- 'google_ads',
- 'clientify',
+ 'instagram',
+ 'tiktok',
+ 'linkedin',
'google_drive',
- 'wordpress',
] as const;
const SHORT: Record = {
meta_ads: 'Meta',
- google_ads: 'GAds',
- clientify: 'CRM',
+ instagram: 'IG',
+ tiktok: 'TT',
+ linkedin: 'LI',
google_drive: 'Drive',
- wordpress: 'WP',
};
function healthBadge(h: string) {
@@ -241,8 +241,8 @@ export default function AgencyPortfolioBoard() {
Panel de control · clientes y conexiones
- Cada cliente con su Meta, Google Ads, Clientify, Drive y web
- (WordPress…). Activa/apaga canales en Conexiones.
+ Cada cliente con ads, redes (IG/TikTok/LinkedIn), Drive y web.
+ Activa/apaga canales en Conexiones.
= {
wordpress: 'https://sitio.com + Application Password',
prestashop: 'https://tienda.com + API key',
shopify: 'mitienda.myshopify.com + Admin API token',
+ instagram: '@handle o Instagram Business Account ID',
+ facebook: 'Page ID / facebook.com/…',
+ tiktok: '@handle o Advertiser / Business ID',
+ linkedin: 'Company Page ID / linkedin.com/company/…',
};
export default function ClientIntegrationsPanel({
@@ -106,6 +110,7 @@ export default function ClientIntegrationsPanel({
const groups = [
{ id: 'paid', title: 'Paid media' },
+ { id: 'social', title: 'Redes sociales (orgánico)' },
{ id: 'analytics', title: 'Analítica y medición' },
{ id: 'crm', title: 'CRM' },
{ id: 'email', title: 'Email / lifecycle' },
@@ -291,6 +296,15 @@ export default function ClientIntegrationsPanel({
fase.
)}
+ {(p.id === 'instagram' ||
+ p.id === 'facebook' ||
+ p.id === 'tiktok' ||
+ p.id === 'linkedin') && (
+
+ Orgánico (páginas/perfiles). Distinto de Meta Ads /
+ TikTok Ads. OAuth live y publicación: siguiente fase.
+
+ )}
);
})}
diff --git a/src/lib/agency.ts b/src/lib/agency.ts
index d32a833..f9589ff 100644
--- a/src/lib/agency.ts
+++ b/src/lib/agency.ts
@@ -13,6 +13,10 @@ export const INTEGRATION_PROVIDERS = [
{ id: 'wordpress', label: 'WordPress', group: 'cms' },
{ id: 'prestashop', label: 'PrestaShop', group: 'cms' },
{ id: 'shopify', label: 'Shopify', group: 'cms' },
+ { id: 'instagram', label: 'Instagram', group: 'social' },
+ { id: 'facebook', label: 'Facebook Page', group: 'social' },
+ { id: 'tiktok', label: 'TikTok', group: 'social' },
+ { id: 'linkedin', label: 'LinkedIn', group: 'social' },
] as const;
export type IntegrationProviderId =