Módulo RRSS

Backend completo OAuth Meta ✓ 34 endpoints 3 agentes n8n pendientes 2026-06-07

Gestión completa del ciclo de contenido para redes sociales: estrategia, banco de ideas, posts con copy por plataforma, revisión, aprobación, programación, publicación OAuth y métricas.

El backend está completamente implementado incluyendo el flujo OAuth para conectar Facebook e Instagram. Modo manual disponible hoy — modo automático disponible cuando Meta apruebe la app. Los agentes n8n son la siguiente fase.

🗺️

Cómo funciona el sistema — visión completa

Este diagrama explica el ciclo de vida completo de un post: desde que se configura la empresa hasta que el contenido aparece publicado en redes sociales.

Paso 1 — Configurar la empresa (se hace una sola vez)
🏢Empresa en Growth54Nombre, industria, país
→
📡Canales RRSSFacebook, IG, LinkedIn
→
🎯EstrategiaTono, pilares, CTA
→
✅Listo para generarTodo configurado
Paso 2 — Generar ideas de contenido
👆Panel: "Generar Ideas"Un clic
→
🤖Agente Content AIn8n + IA
→
💡10 ideas guardadasstatus: idea
→
👀Equipo revisaAprueba o descarta
→
✅Ideas aprobadaslistas para post
Paso 3 — Convertir idea en post con copy
💡Idea aprobadaen el banco
→
✍️Agente Content AImodo: post
→
📄Post generadostatus: borrador
→
👀Revisión equipoeditar si necesario
→
✅Aprobadolisto para publicar
Paso 4 — Publicar en redes sociales

Hay dos modos según si la empresa ya conectó sus redes:

🖐 Modo Manual — mientras no hay API conectada (disponible HOY)
📄Post aprobadocopy listo en panel
→
📋Copiar textodesde el panel
→
📱Pegar y publicaren Facebook / IG
→
✔️Marcar "Publicado"en el panel
Sin OAuth configurado el panel igual funciona. El equipo usa el copy generado por la IA y publica a mano. El historial queda guardado en Growth54.
⚡ Modo Automático — cuando Meta aprueba la app (próximamente)
📄Post aprobadocopy listo en panel
→
👆Botón "Publicar"un clic
→
🔑Token OAuthguardado y encriptado
→
📡API de MetaFacebook / Instagram
→
🎉Publicadoautomático
Paso 5 — Medir resultados
📊Agente Analyticslee métricas
→
💾Guarda en DBalcance, engagement…
→
📈Panel de métricasGrowth54
→
🔄Ajustar estrategiaStrategist AI
Resumen en una línea: Growth54 genera el contenido con IA → el equipo lo aprueba → el sistema lo publica (manual hoy, automático mañana) → los datos vuelven al panel para mejorar.
🗄️

Tablas y estados

TablaPropósito
company_social_channelsCanales configurados por empresa
social_strategiesEstrategia RRSS — 1 registro por empresa
social_ideasBanco de ideas de contenido
social_postsPosts en todas sus etapas
social_metricsMétricas de rendimiento por plataforma y período
Estados — Ideas
ValorSignificado
ideaRecién creada, pendiente de revisión
aprobadoAprobada — lista para convertirse en post
descartadoDescartada, no se usará
Estados — Posts
ValorSignificado
borradorGenerado, sin revisar
en_revisionEnviado a revisión por el equipo
aprobadoAprobado — listo para programar
programadoTiene fecha asignada
publicadoYa fue publicado
Campo generated_by — presente en todos los modelos. manual = creado desde el panel, n8n = generado por un agente.
🎯

Estrategia

Una empresa tiene exactamente una estrategia RRSS. El PUT es upsert — crea o reemplaza.

GET/api/rrss/estrategia

Devuelve la estrategia de la empresa activa. data: null si no existe.

curl -s "https://api.growth54.com/api/rrss/estrategia" -H "Authorization: Bearer <token>" | jq
PUT/api/rrss/estrategia

Crea o actualiza la estrategia. Todos los campos son opcionales.

curl -s -X PUT "https://api.growth54.com/api/rrss/estrategia" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{
    "objetivo":      "Generar visibilidad y captar leads",
    "audiencia":     "Dueños de PYME, 28-45 años, LATAM",
    "tono":          "Profesional pero cercano",
    "frecuencia":    "6 posts semanales",
    "cta_principal": "Agendar llamada de diagnóstico gratuita",
    "embudo":        "RRSS → Website → Lead Magnet → CRM",
    "plataformas":   ["Facebook","Instagram","LinkedIn"],
    "pilares":       ["Educación","Casos de éxito","Detrás de cámaras"],
    "kpis":          ["Alcance orgánico","Engagement","Leads generados"]
  }' | jq
💡

Ideas

Banco de ideas de contenido. Pueden crearse manualmente o generarse por el agente Content AI.

GET/api/rrss/ideas

Lista ideas. Filtros opcionales: ?status=aprobado  ·  ?platform=Instagram

curl -s "https://api.growth54.com/api/rrss/ideas?status=aprobado" -H "Authorization: Bearer <token>" | jq
POST/api/rrss/ideas

Crea una idea manualmente. topic es obligatorio.

curl -s -X POST "https://api.growth54.com/api/rrss/ideas" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{
    "topic":           "5 errores que cometen las PYMEs en Instagram",
    "platform":        "Instagram",
    "keyword":         "marketing PYME",
    "fecha_propuesta": "2026-05-15"
  }' | jq
PUT/api/rrss/ideas/{id}/status

Cambia el estado. Valores: idea · aprobado · descartado

curl -s -X PUT "https://api.growth54.com/api/rrss/ideas/1/status" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"status": "aprobado"}' | jq
DELETE/api/rrss/ideas/{id}

Elimina una idea. Solo puede eliminar ideas de su empresa activa.

📄

Posts

Posts en todas las etapas del ciclo de vida: borrador → revisión → aprobado → programado → publicado.

GET/api/rrss/posts

Lista posts. Filtros opcionales: ?status=borrador  ·  ?platform=LinkedIn

curl -s "https://api.growth54.com/api/rrss/posts" -H "Authorization: Bearer <token>" | jq
POST/api/rrss/posts Nuevo · fecha_programada + status

Crea un post manualmente. tema es obligatorio.
New Acepta fecha_programada y status opcionales, para crear el post ya con fecha o directamente programado (lo usa el composer del calendario). Si se omiten, el comportamiento es el de siempre: borrador sin fecha.
— status admite borrador o programado.
— Con programado la fecha es obligatoria y debe ser futura, o responde 422.
— fecha_programada se interpreta en la zona horaria de la empresa, igual que en /schedule, y se guarda en UTC.

curl -s -X POST "https://api.growth54.com/api/rrss/posts" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{
    "tema":             "5 errores que cometen las PYMEs en Instagram",
    "platform":         "Instagram",
    "copy_instagram":   "¿Tu negocio está en Instagram pero no consigue resultados?\n...",
    "copy_facebook":    null,
    "copy_linkedin":    null,
    "cta":              "Escríbenos para una auditoría gratuita",
    "hooks":            ["¿Tu negocio está en IG pero no consigue resultados?"],
    "fecha_programada": "2026-08-05T14:30:00",
    "status":           "programado"
  }' | jq
PUT/api/rrss/posts/{id} Nuevo · fecha_programada + status

Edita un post existente. Todos los campos son opcionales; solo se escribe lo que mandes.
New Ahora acepta también fecha_programada y status, con las mismas reglas que POST: así el panel guarda contenido, fecha y estado en una sola llamada.
— Mandar fecha_programada: null borra la fecha; omitir la clave la deja como estaba.
— Campos editables: tema, platform, copy_instagram, copy_facebook, copy_linkedin, hook, cta, imagen_url.

curl -s -X PUT "https://api.growth54.com/api/rrss/posts/1" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{
    "tema":             "5 errores que cometen las PYMEs en Instagram",
    "copy_instagram":   "Texto corregido a mano...",
    "fecha_programada": "2026-08-05T14:30:00",
    "status":           "programado"
  }' | jq
PUT/api/rrss/posts/{id}/status

Cambia el estado del post.
— Al aprobar → guarda approved_by_user_id automáticamente.
— Al publicar → guarda fecha_publicada con la fecha actual.

curl -s -X PUT "https://api.growth54.com/api/rrss/posts/1/status" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"status": "aprobado"}' | jq
PUT/api/rrss/posts/{id}/schedule

Programa el post para una fecha futura. Cambia status a programado. La fecha debe ser futura. New La hora se interpreta en la zona horaria de la empresa (companies.timezone): p. ej. "2026-05-20T10:00:00" con empresa en Nicaragua = 10:00 hora de Nicaragua. Internamente se guarda en UTC. Si la empresa no tiene zona configurada, se asume UTC. Verifica que la empresa tenga su zona horaria establecida en Editar datos de la empresa → País / Zona horaria; sin ella, las horas se programarán en UTC.

curl -s -X PUT "https://api.growth54.com/api/rrss/posts/1/schedule" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"fecha_programada": "2026-05-20T10:00:00"}' | jq
📊

Métricas

Consulta de métricas agrupadas por plataforma con totales y promedios.

GET/api/rrss/metricas

Resumen por plataforma. Filtros: ?platform=Instagram · ?from=2026-05-01 · ?to=2026-05-31

curl -s "https://api.growth54.com/api/rrss/metricas?from=2026-05-01&to=2026-05-31" \
  -H "Authorization: Bearer <token>" | jq

// Respuesta:
{
  "data": [{
    "platform":            "Instagram",
    "alcance_total":       12430,
    "impresiones_total":   38000,
    "engagement_total":    584,
    "engagement_rate_avg": 4.7,
    "clics_total":         384,
    "leads_total":         17
  }]
}
GET/api/rrss/metricas/top-posts

Top 10 posts publicados ordenados por engagement.

curl -s "https://api.growth54.com/api/rrss/metricas/top-posts" -H "Authorization: Bearer <token>" | jq
🚀

Triggers — disparar agentes

El panel llama estos endpoints y el backend dispara el webhook del agente correspondiente. Si el webhook no está configurado en Panel › Agentes → HTTP 422.

POST/api/rrss/run-strategist

Dispara el agente Strategist AI. Key: rrss_strategist

curl -s -X POST "https://api.growth54.com/api/rrss/run-strategist" -H "Authorization: Bearer <token>" | jq
POST/api/rrss/run-content-ai

Dispara el agente Content AI. Key: rrss_content_ai. El campo mode controla si genera ideas o un post específico.

// Generar ideas:
curl -s -X POST "https://api.growth54.com/api/rrss/run-content-ai" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"mode": "ideas", "idea_id": null}' | jq

// Generar post desde una idea aprobada:
curl -s -X POST "https://api.growth54.com/api/rrss/run-content-ai" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"mode": "post", "idea_id": 5}' | jq
🔀 Dos botones en el panel de Ideas → un solo agente New

La pantalla RRSS › Ideas tiene ahora dos botones separados, y ambos disparan el mismo webhook (rrss_content_ai). Lo único que cambia es el campo mode. El agente de n8n debe ramificar según mode (un nodo Switch al inicio):

  • Botón "Generar Ideas" → envía mode: "ideas" → el agente genera ideas nuevas (sin correr el Strategist completo) y las guarda con POST /api/agent/rrss/ideas. Ver Agente 2A.
  • Botón "Generar Post" → envía mode: "post" (opcionalmente idea_id) → el agente genera el post/borrador y lo guarda con POST /api/agent/rrss/posts. Ver Agente 2B.

Payload exacto que recibe tu webhook (lo arma el backend en RrssAgentTriggerController):

{
  "company_id": 24,
  "trigger": { "source": "ui", "user_id": 1 },
  "mode": "ideas",      // "ideas" (Generar Ideas) o "post" (Generar Post)
  "idea_id": null       // sólo relevante en mode "post": la idea aprobada a desarrollar
}

Importante: si tu agente ignora mode y siempre genera posts, el botón "Generar Ideas" saldrá mal (era el bug anterior). El branch por mode es obligatorio.

POST/api/rrss/run-analytics

Dispara el agente Analytics AI. Key: rrss_analytics. No requiere body.

curl -s -X POST "https://api.growth54.com/api/rrss/run-analytics" -H "Authorization: Bearer <token>" | jq
🔑

Tokens de Acceso Social

Credenciales OAuth por plataforma (Facebook, Instagram, LinkedIn). El agente Publisher las usa para publicar. El token de acceso se almacena encriptado.

GET/api/rrss/tokens

Lista los tokens configurados para la empresa activa. El campo access_token no se devuelve por seguridad.

curl -s "https://api.growth54.com/api/rrss/tokens" -H "Authorization: Bearer <token>" | jq
PUT/api/rrss/tokens

Crea o actualiza el token de una plataforma (upsert por platform). Si access_token se omite en una actualización, el token existente no cambia.

curl -s -X PUT "https://api.growth54.com/api/rrss/tokens" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{
    "platform":               "Instagram",
    "access_token":           "EAAxxxx...",
    "page_id":                "123456789",
    "instagram_account_id":   "17841400000000000",
    "label":                  "System User — Cliente XYZ",
    "expires_at":             "2027-01-01"
  }' | jq
DELETE/api/rrss/tokens/{id}

Elimina un token. Solo puede eliminar tokens de su empresa activa.

🔐

Conectar Facebook + Instagram — OAuth

El flujo OAuth conecta la cuenta de Facebook de la empresa con Growth54. Después de conectar, el token queda encriptado en company_social_tokens y el sistema puede publicar en nombre de la empresa.

Requisito previo: La app de Meta debe tener registrada la URL de callback exacta. En local se necesita ngrok (Meta requiere HTTPS). En producción usar https://apigrowth.mdarthurdigital.com/api/oauth/meta/callback.
Flujo completo de conexión
👆Panel: "Conectar Facebook"botón en Canales
→
🔀GET /api/oauth/meta/redirect?company_id=X
→
📘Login de Facebookel usuario autoriza
→
🔙GET /api/oauth/meta/callback?code=...&state=...
→
🔑Token guardado60 días, encriptado
→
↩️Redirige al panel/rrss/canales
Endpoints
GET /api/oauth/meta/redirect auth:sanctum

El panel llama este endpoint con el company_id. Laravel construye la URL de autorización de Meta y redirige al usuario al login de Facebook. El estado OAuth incluye el company_id encriptado en Base64 para recuperarlo en el callback.

// El frontend abre esta URL en el mismo tab o popup:
window.location.href = "https://api.growth54.com/api/oauth/meta/redirect?company_id=3"

// Parámetros enviados a Meta:
scope = pages_manage_posts, pages_read_engagement,
        instagram_basic, instagram_content_publish
response_type = code
state = base64({ company_id: 3, csrf: "aleatorio" })
GET /api/oauth/meta/callback público

Meta redirige aquí con el code de autorización. Laravel intercambia el code por un token de corta duración, lo convierte a token de 60 días, obtiene las páginas de Facebook y la cuenta de Instagram vinculada, y guarda todo en company_social_tokens. Al terminar redirige al panel.

Qué guardaDónde
Token de página de Facebook (60 días)company_social_tokens platform=facebook
Token de Instagram Business (si está vinculado)company_social_tokens platform=instagram
Page ID, Instagram Account IDmisma tabla, campos page_id y instagram_account_id
Si Meta rechaza o el usuario cancela: el callback recibe ?error=... y redirige al panel con ?oauth=error. El panel debe mostrar un toast de error.
POST /api/oauth/meta/disconnect auth:sanctum

Elimina los tokens de Facebook e Instagram de la empresa. El botón "Desconectar" del panel llama este endpoint.

curl -s -X POST "https://api.growth54.com/api/oauth/meta/disconnect" \
  -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"company_id": 3}' | jq

// Respuesta:
{ "ok": true, "deleted": 2 }
Variables .env requeridas
META_APP_ID=          ← App ID de Meta for Developers
META_APP_SECRET=      ← App Secret de Meta for Developers
META_REDIRECT_URI=    ← debe coincidir EXACTAMENTE con la URL registrada en Meta

# Local (con ngrok):
META_REDIRECT_URI=https://TU-NGROK.ngrok-free.app/api/oauth/meta/callback

# Producción:
META_REDIRECT_URI=https://apigrowth.mdarthurdigital.com/api/oauth/meta/callback

FRONTEND_URL=         ← a donde redirige el callback (ej: http://localhost:5173)
Qué construir en el frontend
1
En SocialAccountsPage.jsx — tarjeta por plataforma con el estado de conexión
2
Botón "Conectar con Facebook" → window.location.href = /api/oauth/meta/redirect?company_id=X
3
Cuando el callback redirige con ?oauth=success → mostrar toast verde "¡Facebook e Instagram conectados!"
4
Si llega ?oauth=error → mostrar toast rojo con el mensaje
5
Botón "Desconectar" → llama POST /api/oauth/meta/disconnect → recarga estado
Sin App Review de Meta: solo las cuentas listadas como "Testers" en el panel de Meta Developers pueden conectarse. Para usar con clientes reales hay que solicitar los permisos pages_manage_posts e instagram_content_publish a Meta (proceso de revisión de 1–3 semanas con screencast y Privacy Policy).
💼

Conectar LinkedIn — OAuth

LinkedIn permite publicar en páginas de empresa. El token dura 60 días y el refresh token 1 año, lo que hace la reconexión prácticamente automática. A diferencia de Meta, LinkedIn tiene un proceso de aprobación más simple — los permisos básicos se aprueban solos.

Estado actual: La estructura de base de datos ya soporta LinkedIn (company_social_tokens con platform=linkedin). Falta crear la app en LinkedIn Developer Portal y construir el LinkedInOAuthService.
Paso 1 — Crear la app en LinkedIn Developer Portal

Solo se hace una vez. Growth54 tendrá UNA app y todos los clientes se conectan a través de ella.

1
Ir a developer.linkedin.com → iniciar sesión con la cuenta de LinkedIn de Growth54 → hacer clic en "Create app"
2
Completar el formulario de creación:
App name:     Growth54
LinkedIn Page: [seleccionar la página de empresa de Growth54]
App Logo:     subir logo de Growth54
Privacy policy URL: https://growth54.com/privacidad
3
Hacer clic en "Create app". LinkedIn lleva a la pantalla de configuración de la app.
4
Ir a la pestaña "Auth" y agregar las Redirect URLs:
# Producción:
https://apigrowth.mdarthurdigital.com/api/oauth/linkedin/callback

# Local (con ngrok, agregar temporalmente para pruebas):
https://TU-NGROK.ngrok-free.app/api/oauth/linkedin/callback
5
En la misma pestaña "Auth" → copiar Client ID y Client Secret → guardarlos en el .env:
LINKEDIN_CLIENT_ID=86xxxxxxxxxxxxxxxx
LINKEDIN_CLIENT_SECRET=xxxxxxxxxxxxxxxx
LINKEDIN_REDIRECT_URI=https://apigrowth.mdarthurdigital.com/api/oauth/linkedin/callback
Paso 2 — Solicitar los productos (permisos)

En la pestaña "Products" de la app, solicitar estos tres productos en orden:

ProductoPara qué sirveAprobación
Sign In with LinkedIn using OpenID Connect Obtener perfil básico del usuario (nombre, email). Base del OAuth. Automática ✓
Share on LinkedIn Publicar posts en el perfil personal del usuario. Automática ✓
Marketing Developer Platform Publicar en páginas de empresa (w_organization_social). El más importante para Growth54. Requiere solicitud — LinkedIn la aprueba en 1–7 días
Truco: Mientras esperas la aprobación del Marketing Developer Platform, podés trabajar con w_member_social (publicar en perfil personal). Sirve para probar el flujo OAuth completo sin esperar.
Scopes que pedirá Growth54
ScopeQué permiteRequiere
openidIdentificar al usuarioSign In with LinkedIn
profileNombre, foto del usuarioSign In with LinkedIn
emailEmail del usuarioSign In with LinkedIn
w_member_socialPublicar en perfil personalShare on LinkedIn
w_organization_socialPublicar en página de empresaMarketing Developer Platform
r_organization_socialLeer métricas de página de empresaMarketing Developer Platform
Flujo completo de conexión
👆Panel: "Conectar LinkedIn"botón en Canales
→
🔀GET /api/oauth/linkedin/redirect?company_id=X
→
💼Login de LinkedInel usuario autoriza
→
🔙GET /api/oauth/linkedin/callback?code=...&state=...
→
🔑Token guardado60 días + refresh 1 año
→
↩️Redirige al panel/rrss/canales
Diferencias clave vs Meta
Meta (Facebook/Instagram)LinkedIn
Duración del token60 días (no renovable automático)60 días + refresh token de 1 año
Renovar tokenEl usuario debe reconectarSe renueva automático con el refresh token
Publicar en empresaToken de página (independiente)Scope w_organization_social + URN de la organización
App ReviewProceso largo (1–3 semanas)Marketing Developer Platform (1–7 días)
Límite de postsSin límite específico150 posts por día por organización
Qué hay que construir en el backend
Lo que ya existe: la tabla company_social_tokens ya soporta LinkedIn (platform=linkedin). Solo falta el service y las rutas.
1
Crear app/Services/LinkedIn/LinkedInOAuthService.php con:
  • getAuthorizationUrl(company_id) — URL de login LinkedIn con state
  • handleCallback(code, state) — intercambia code → token → guarda refresh token → obtiene URN de organización
  • refreshToken(company_id) — usa el refresh token para renovar antes de que expire
2
Agregar métodos en OAuthController.php:
  • linkedinRedirect()
  • linkedinCallback()
  • linkedinDisconnect()
3
Agregar rutas en routes/api.php:
GET  /api/oauth/linkedin/redirect   (auth:sanctum)
GET  /api/oauth/linkedin/callback   (público)
POST /api/oauth/linkedin/disconnect (auth:sanctum)
4
Guardar en company_social_tokens:
platform:      'linkedin'
access_token:  (encriptado)
page_id:       URN de la organización → "urn:li:organization:XXXXXXX"
label:         nombre de la página de empresa
expires_at:    now + 60 días
El refresh token se guarda por separado (columna a agregar: refresh_token).
Cómo publicar en LinkedIn (cuando el agente Publisher lo necesite)
// Endpoint de LinkedIn para publicar en página de empresa:
POST https://api.linkedin.com/v2/ugcPosts

// Body:
{
  "author": "urn:li:organization:XXXXXXX",   ← el page_id guardado en company_social_tokens
  "lifecycleState": "PUBLISHED",
  "specificContent": {
    "com.linkedin.ugc.ShareContent": {
      "shareCommentary": { "text": "el copy generado por el agente" },
      "shareMediaCategory": "NONE"
    }
  },
  "visibility": { "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC" }
}

// Header:
Authorization: Bearer <access_token de company_social_tokens>
Para incluir imagen: primero hay que subir la imagen a LinkedIn con POST /v2/assets?action=registerUpload y luego incluir el asset URN en el shareMediaCategory: "IMAGE". El agente Publisher lo maneja.
Checklist para activar LinkedIn
#TareaDóndeEstado
1Crear app en developer.linkedin.comdeveloper.linkedin.com⬜ Pendiente
2Solicitar "Sign In with LinkedIn" y "Share on LinkedIn"Tab Products de la app⬜ Pendiente
3Solicitar "Marketing Developer Platform"Tab Products de la app⬜ Pendiente (1–7 días)
4Agregar Redirect URI de producciónTab Auth de la app⬜ Pendiente
5Llenar LINKEDIN_CLIENT_ID y LINKEDIN_CLIENT_SECRET en .env.env backend⬜ Pendiente
6Crear LinkedInOAuthService + métodos en OAuthControllerBackend Laravel✅ Implementado 2026-06-07
7Agregar rutas /api/oauth/linkedin/*routes/api.php✅ Implementado 2026-06-07
8Botón "Conectar LinkedIn" en CanalesPage.jsxFrontend⬜ Pendiente
9Agregar columna refresh_token en company_social_tokensNueva migración✅ Migración aplicada 2026-06-07
⚡

Endpoints n8n — lectura

Solo lectura para los workflows de n8n. Header requerido: Authorization: Bearer <N8N_TOKEN>

Base: /api/n8n/companies/{id}/rrss/

GET/api/n8n/companies/{id}/rrss/canales Nuevo · image_config

Canales activos con handle, objetivo, frecuencia, formatos, rol en el embudo y la configuración de imagen que eligió el cliente para esa red (image_config).

{ "data": [{
  "id": 12, "platform": "Facebook", "handle": "...", "objective": "...",
  "content_formats": ["post","carrusel"],
  "image_config": {
    "img_estilo":     "realista",           // realista | humanos | 3d | animado | minimalista | artistico
    "img_formato":    "horizontal",         // cuadrada (1:1) | vertical (4:5) | horizontal (16:9)
    "img_texto_modo": "titulo_subtitulo"    // sin_texto | solo_titulo | titulo_subtitulo | titulo_sub_detalles | libre
  }
}]}
📷 Cómo queremos las imágenes (para el agente que las genera)
  • Lee image_config de cada plataforma por separado — no es igual para Facebook que para Instagram. Cada red trae su propia config.
  • img_formato define la proporción: cuadrada = 1024×1024 (feed Instagram) · vertical = 1024×1536 (stories/reels) · horizontal = 1536×1024 (Facebook / LinkedIn / banner).
  • img_estilo = el look de la imagen (fotográfico, animado, 3D, etc.).
  • img_texto_modo = si la imagen lleva título/subtítulo encima. Si lleva texto, debe ir en el mismo idioma del post y bien escrito.
  • Nunca inventar logos ni marcas/logotipos de terceros.
  • Si image_config viene null, usar por defecto: Facebook/LinkedIn → horizontal, Instagram → cuadrada, estilo realista, sin texto.
GET/api/n8n/companies/{id}/rrss/estrategia

Estrategia vigente. Devuelve null si no existe.

GET/api/n8n/companies/{id}/rrss/ideas

Ideas con status aprobado, listas para convertirse en posts.

GET/api/n8n/companies/{id}/rrss/posts

Posts con status aprobado o programado.

GET/api/n8n/companies/{id}/rrss/posts/to-publish

Posts programado cuya fecha_programada ya pasó. Cada post incluye ya el token de Meta de la empresa (page_id, instagram_account_id, access_token) — el Distribution AI publica con eso, no hay que pedirlo aparte.

New Aquí también aparecen los posts creados a mano desde el calendario del panel, no solo los del Content AI. Salen con la misma forma (status: programado, fecha en UTC, platform, copy de esa red e imagen_url), así que el Distribution AI no necesita ningún cambio: los recoge y publica igual.

{ "data": [{
  "id": 123, "platform": "instagram", "copy_instagram": "...", "imagen_url": "...",
  "page_id": "1713965015486703", "instagram_account_id": "17841...", "access_token": "EAAxxxxx..."
}]}
GET/api/n8n/pages/{page_id}

Resuelve la empresa por page_id de Meta (Community AI). Cuando Meta envía un webhook trae el page_id pero no el company_id; esto lo traduce. Devuelve 404 si el page_id no está registrado (el agente NO atiende — sin fallback a una empresa fija). Funciona para cualquier empresa con su página conectada.

{ "company_id": 24, "fb_access_token": "EAAxxxxx...", "ig_user_id": "17841402206774619" }
🤖

Endpoints agent — escritura

Para que los agentes guarden datos generados. Header: Authorization: Bearer <AGENT_TOKEN>
Los registros creados llevan generated_by = 'n8n' automáticamente.

POST/api/agent/rrss/estrategia

Guarda o actualiza la estrategia. Incluir company_id.

{ "company_id": 3, "objetivo": "...", "audiencia": "...", "tono": "...",
  "frecuencia": "...", "cta_principal": "...", "embudo": "...",
  "plataformas": ["Instagram","LinkedIn"],
  "pilares": ["Educación","Casos de éxito"],
  "kpis": ["Alcance","Engagement"] }
POST/api/agent/rrss/ideas

Guarda ideas en bulk. Respuesta: {"ok": true, "created": N}

{ "company_id": 3, "ideas": [
  { "topic": "5 errores que cometen las PYMEs", "platform": "Instagram", "keyword": "marketing PYME" },
  { "topic": "Cómo automatizar con IA", "platform": "LinkedIn" }
]}
POST/api/agent/rrss/posts

Guarda posts como borradores en bulk. La imagen generada va en imagen_url (URL pública), y opcionalmente el prompt usado en imagen_prompt.

{ "company_id": 3, "posts": [{
  "tema": "5 errores que cometen las PYMEs",
  "platform": "Instagram", "social_idea_id": 5,
  "copy_instagram": "¿Tu negocio está en Instagram pero no consigue resultados?\n...",
  "copy_facebook": "Si tu página de Facebook publica pero no genera conversaciones...",
  "copy_linkedin": "En 2026, producir contenido sin estrategia es el error más común...",
  "hooks": ["¿Tu negocio está en Instagram pero no consigue resultados?"],
  "cta": "Comenta o envíame un DM para una auditoría gratuita.",
  "imagen_url": "https://.../imagen-optimizada.jpg",
  "imagen_prompt": "Minimalist flat design 5 warning signs PYME"
}]}
📷 Sobre la imagen del post
  • Genera la imagen según el image_config de la plataforma del post (ver GET .../rrss/canales arriba): estilo, formato y texto que eligió el cliente.
  • Guarda su URL pública en imagen_url (el Distribution AI la usa para publicar en Meta; Instagram exige imagen).
  • Peso ligero: preferimos imágenes livianas (JPEG comprimido). Nuestro backend ofrece POST /api/agent/optimize-image (recibe PNG base64, devuelve JPEG comprimido) si quieres optimizar antes de hostearla.
POST/api/agent/rrss/posts/{post}/mark-published

Marca un post como publicado. Guarda fecha_publicada con la fecha actual. Usar por el agente Publisher tras publicar en la red social.

curl -s -X POST "https://api.growth54.com/api/agent/rrss/posts/7/mark-published" \
  -H "Authorization: Bearer <AGENT_TOKEN>" | jq
DELETE/api/agent/rrss/posts/{post}

Elimina un post. Útil para limpiar borradores duplicados generados en pruebas. Respuesta: {"ok": true}

curl -s -X DELETE "https://api.growth54.com/api/agent/rrss/posts/7" \
  -H "Authorization: Bearer <AGENT_TOKEN>" | jq
POST/api/agent/rrss/metricas

Guarda métricas. social_post_id es opcional.

{ "company_id": 3, "metricas": [{
  "platform": "Instagram", "period_start": "2026-05-01", "period_end": "2026-05-31",
  "social_post_id": 12, "alcance": 3420, "impresiones": 10500,
  "engagement_count": 212, "engagement_rate": 6.2, "clics": 89, "leads": 4
}]}
GET/api/agent/wa/empresa-by-phone/{phone_number_id}

WhatsApp Engine multi-empresa. Resuelve la empresa por el phone_number_id que manda Meta y devuelve toda la config del bot (token, prompt, catálogo, FAQs, mensajes, palabras de lead, correos del vendedor). 404 si el número no está registrado → el agente NO atiende (sin fallback). La config vive en wa_configs (1 fila por empresa); no existe ni se necesita wa_empresas. Las conversaciones se guardan luego con POST /api/agent/whatsapp/inbound.

{ "company_id": 20, "nombre": "Cratingexpress",
  "phone_number_id": "1083260611538246", "wa_access_token": "EAAxxxxx...",
  "system_prompt": "...", "catalogo": "...", "faqs": "...",
  "msg_bienvenida": "...", "msg_followup_24h": "...", "msg_followup_72h": "...",
  "palabras_lead_caliente": ["cotizar","precio"], "emails_vendedor": ["ventas@empresa.com"] }
⚙️

Agentes n8n — Configuración inicial

Todos los workflows comienzan con un nodo Set llamado Config que define las variables globales.

BASE_URL    = https://api.growth54.com     ← producción
              https://xxx.ngrok-free.app   ← local con ngrok
AGENT_TOKEN = (AGENT_API_TOKEN del .env del backend)
N8N_TOKEN   = (N8N_API_TOKEN del .env del backend)
COMPANY_ID  = {{ $json.company_id }}      ← viene del trigger del panel
Headers por tipo de request:
Lectura → Authorization: Bearer <N8N_TOKEN>
Escritura → Authorization: Bearer <AGENT_TOKEN>
Keys registradas en DB
AgenteKeyConfigurar en
Strategist AIrrss_strategistPanel › Configuración › Agentes
Content AIrrss_content_aiPanel › Configuración › Agentes
Analytics AIrrss_analyticsPanel › Configuración › Agentes
Distribution AIrrss_distribution_aiPanel › Configuración › Agentes
Community AIrrss_community_aiPanel › Configuración › Agentes
Sales AIrrss_sales_aiPanel › Configuración › Agentes
Prospección AI Newlead_prospectingPanel › Configuración › Agentes
Nurture & Outreach AI Newblog_distributionPanel › Configuración › Agentes
Esta tabla estaba incompleta desde antes de hoy — Distribution AI, Community AI y Sales AI ya existían y no estaban listadas. Se completa el inventario, pero sus secciones de "Agente — flujo de nodos" abajo todavía no existen (solo están documentadas al nivel de /docs/api). Prospección AI y Nurture & Outreach AI sí tienen su sección completa, ver abajo.
🧠

Agente 1 — Strategist AI

Lee el perfil y canales de la empresa, genera una estrategia RRSS completa y la guarda. El equipo puede editarla desde el panel.

Flujo de nodos
1
Webhook trigger — recibe company_id del panel
2
GET /api/n8n/companies/{id} — nombre, industria, país, descripción
3
GET /api/n8n/companies/{id}/rrss/canales — canales activos con handles y objetivos
4
GET /api/n8n/companies/{id}/rrss/estrategia — estrategia actual (puede ser null)
5
Nodo IA (OpenAI / Claude) — genera la estrategia con el prompt de abajo
6
POST /api/agent/rrss/estrategia — guarda resultado, generated_by queda en n8n
Prompt para nodo IA
Eres un estratega de redes sociales experto en marketing para PYMEs latinoamericanas.

Empresa: {nombre} — {industria} — {país}
Descripción: {descripción}
Canales activos: {plataforma, handle, objetivo, frecuencia, funnel_role}
Estrategia actual: {estrategia existente o "No hay estrategia aún"}

Genera una estrategia RRSS completa y accionable.
Responde SOLO en JSON:
{
  "objetivo":      "objetivo principal de RRSS",
  "audiencia":     "descripción del buyer persona",
  "tono":          "tono de comunicación recomendado",
  "frecuencia":    "frecuencia de publicación",
  "cta_principal": "llamada a la acción principal",
  "embudo":        "ruta desde RRSS hasta conversión",
  "plataformas":   ["plataforma1", "plataforma2"],
  "pilares":       ["pilar 1", "pilar 2", "pilar 3"],
  "kpis":          ["KPI 1", "KPI 2", "KPI 3"]
}
💡

Agente 2A — Content AI, modo Ideas

Disparado con "mode": "ideas". Genera 10 ideas basadas en la estrategia y los canales. Llegan con status idea — el equipo las aprueba o descarta.

Flujo de nodos
1
Webhook trigger — recibe company_id y mode = "ideas"
2
GET /api/n8n/companies/{id} — perfil empresa
3
GET /api/n8n/companies/{id}/rrss/canales — canales activos
4
GET /api/n8n/companies/{id}/rrss/estrategia — pilares, audiencia, tono
5
Nodo IA — genera 10 ideas cubriendo los pilares
6
POST /api/agent/rrss/ideas — guarda en bulk → {"ok": true, "created": 10}
Prompt para nodo IA
Eres un experto en contenido para redes sociales de PYMEs.

Empresa: {nombre} — {industria} — {país}
Objetivo: {objetivo} | Audiencia: {audiencia} | Tono: {tono}
Pilares: {pilares} | CTA: {cta_principal}
Canales: {plataformas con handle y frecuencia}

Genera 10 ideas variadas distribuidas entre canales activos.
Cubre todos los pilares. Sé específico. Fechas: próximos 30 días.

Responde SOLO en JSON:
{
  "ideas": [
    {
      "topic":           "título claro del contenido",
      "platform":        "Instagram | Facebook | LinkedIn",
      "keyword":         "palabra clave central",
      "fecha_propuesta": "YYYY-MM-DD"
    }
  ]
}
✍️

Agente 2B — Content AI, modo Post

Disparado con "mode": "post", "idea_id": N. Toma una idea aprobada y genera el post completo: copy por plataforma, hooks y CTA. Llega como borrador para revisión del equipo.

Flujo de nodos
1
Webhook trigger — recibe company_id, mode = "post", idea_id
2
GET /api/n8n/companies/{id} — perfil empresa
3
GET /api/n8n/companies/{id}/rrss/estrategia — tono y CTA
4
GET /api/n8n/companies/{id}/rrss/ideas — buscar la idea por idea_id
5
Nodo IA — genera copy para cada plataforma activa
6
POST /api/agent/rrss/posts — guarda como borrador con social_idea_id enlazado
Prompt para nodo IA
Eres un copywriter experto en RRSS para PYMEs de LATAM.

Empresa: {nombre} — {industria}
Tono: {tono} | CTA: {cta_principal}

Idea a desarrollar:
- Tema: {topic} | Plataforma: {platform} | Keyword: {keyword}

Genera copy adaptado para cada plataforma. Si no aplica, devuelve null.

Responde SOLO en JSON:
{
  "copy_instagram": "texto con emojis, saltos de línea y hashtags al final",
  "copy_facebook":  "texto conversacional más extenso, puede incluir pregunta",
  "copy_linkedin":  "tono profesional, párrafos cortos, dato de valor",
  "hooks": [
    "primera línea alternativa 1",
    "primera línea alternativa 2"
  ],
  "cta":           "llamada a la acción específica para este post",
  "imagen_prompt": "descripción en inglés para DALL-E o Midjourney"
}
📈

Agente 3 — Analytics AI

Lee métricas reales de las plataformas y las carga al backend. La fuente de datos depende de lo que cada empresa tenga disponible.

Flujo de nodos
1
Webhook trigger — recibe company_id
2
GET /api/n8n/companies/{id}/rrss/canales — canales activos (handles y plataformas)
3
GET /api/n8n/companies/{id}/rrss/posts — posts publicados (para linkear métricas)
4
Por cada canal — consultar la fuente de datos (ver opciones abajo)
5
POST /api/agent/rrss/metricas — guardar métricas del período
Opciones de fuente de datos
Opción A — Google Sheets (recomendado para arrancar)
El cliente llena una hoja con métricas del mes. n8n lee la sheet y llama /api/agent/rrss/metricas. Sin OAuth por empresa.
Opción B — APIs directas (ideal a largo plazo)
Meta Graph API para Instagram/Facebook, LinkedIn Marketing API. Cada empresa necesita tokens OAuth. Más preciso, más configuración inicial.
Opción C — CSV manual
El cliente exporta CSV de cada plataforma. n8n lo procesa con un nodo Code y envía al backend.
🔍

Agente 4 — Prospección AI New · 19-sep

Sale a buscar negocios nuevos por rubro y ciudad y los agrega al CRM como leads reales — generación de leads pasiva, el cliente no hace nada, los leads le aparecen solos en el tablero. Multiempresa desde el diseño: el rubro y la ciudad salen del perfil real de cada empresa, no de una lista fija.

Flujo de nodos
1
Trigger propio (horario semanal en n8n, o manual) — recorre GET /api/n8n/companies?activas=1
2
Por cada empresa: usa industry/base_city/geography ya incluidos en esa misma respuesta como criterio de búsqueda — no hace falta pedirlos aparte
3
Busca negocios (API de búsqueda de lugares) y descubre el email por cascada (búsqueda de email por dominio → scraping de /contact → scraping de /about)
4
POST /api/agent/crm/leads con origen:"prospeccion" — no a una hoja de cálculo externa. Ese origen es justamente el que distingue "salimos a buscarlo" de agente (llegó solo)
No usar origen:"manual" como parche. Antes de que prospeccion estuviera disponible en el ENUM, algunos leads se guardaban así con la fuente real escondida en notas — ya no hace falta, el valor real está soportado y probado (tests/Feature/Api/Integrations/AgentCrmOrigenTest.php).
New · 29-sep El "trigger manual" del panel ya existe de verdad. Hasta ahora no había ninguna forma de disparar este agente desde el panel — el usuario tenía que esperar el horario semanal. Ahora POST /api/rrss/run-prospeccion (sin parámetros) llama al webhook registrado en la key lead_prospecting, igual que ya hacían Strategist/Content/ Analytics AI. El botón "Ejecutar" ya está habilitado en el panel (paso 7 del Dashboard de Agentes).
📧

Agente 5 — Nurture & Outreach AI New · 19-sep

Dos funciones separadas, activables por el cliente de forma independiente (por el tema legal pendiente del correo en frío a listas raspadas): nutrir leads calificados (cada artículo nuevo del blog arma un correo para cada lead calificado del CRM que no lo haya recibido todavía) y contactar leads en frío (primer correo de presentación a los leads que trajo Prospección AI, nunca contactados). Ninguna manda nada sola — todo queda como borrador esperando aprobación humana.

Flujo de nodos
1
Trigger propio (horario en n8n, o manual) — recorre GET /api/n8n/companies?activas=1&con_cms=wordpress (modo calificado) o simplemente las empresas activas (modo frío)
2
Modo calificado: GET /api/n8n/companies/{id}/rrss/posts o el WordPress real de la empresa para el último artículo publicado
3
Trae el board del CRM y filtra leads calificados (modo calificado) o recién prospectados y nunca contactados (modo frío)
4
Arma el asunto y el cuerpo con IA
5
POST /api/agent/email/drafts — arma el borrador, no lo manda. Ver /docs/api — Correo, bandeja de borradores para el contrato completo, los campos y las respuestas
Este es el único cambio real si tu workflow ya usaba POST /api/agent/email/send: cambiar ese nodo por POST /api/agent/email/drafts. El resto (leer empresa, CMS, board del CRM) queda igual. El envío real solo ocurre cuando un humano aprueba desde el panel.
New · 29-sep Trigger manual del panel — con un matiz sobre el modo. POST /api/rrss/run-nurture requiere modo: "blog" (nutrir calificados) | "outreach" (contactar en frío). El botón "Ejecutar" del paso 8 del Dashboard de Agentes dispara siempre con modo:"blog" — para el modo "outreach" no hay botón propio todavía en el panel, solo la ruta.
🗺️

Orden de construcción recomendado

#AgentePor qué primero
1Content AI — IdeasValor inmediato. El cliente ve ideas de contenido en segundos.
2Content AI — PostAmplía el anterior con la misma key. Posts listos para revisar.
3Strategist AIMás elaborado. Requiere canales bien configurados primero.
4Analytics AI (Sheets)Mínimo viable sin OAuth. El cliente llena una hoja.
5Analytics AI (APIs)Iteración avanzada. Automatización total de métricas.
Errores comunes a evitar:
  • No enviar company_id en el body de escritura → 422
  • Enviar status con valor fuera del enum → 422
  • Enviar idea_id que no pertenece a la empresa → 422
  • fecha_programada en el pasado → rechazada por /schedule
  • Omitir el header Authorization → 401