# n8n Workflows — Prisa Yachts LLC ## Instancia: n8n.crewinghunters.com ## Versión: 1.0 | 2026-05-04 --- ## RESUMEN DE WORKFLOWS | Workflow | Trigger | Qué hace | |----------|---------|----------| | WF1: Content Buffer Publisher | Cron 9AM / 12PM / 5PM ET diario | Lee Google Sheet → publica en Instagram y Facebook automáticamente | | WF2: LinkedIn Reminder | Cron Lu/Mié/Vie 12PM ET | Envía email con el texto del día listo para copiar en LinkedIn | | WF3: Weekly Analytics Report | Cron Domingo 7PM ET | Jala métricas de Meta API → guarda en Sheet → envía resumen por email | | WF4: Content Prep Reminder | Cron Domingo 8AM ET | Detecta posts sin imagen para la semana y alerta por email | --- ## VARIABLES DE ENTORNO (configurar en n8n antes de todo) ```env PRISA_META_PAGE_ACCESS_TOKEN=EAA... # Long-lived Page Access Token (60 días) PRISA_IG_USER_ID=17841400000000000 # Instagram Business Account ID (numérico) PRISA_FB_PAGE_ID=100000000000000 # Facebook Page ID (numérico) PRISA_CONTENT_SHEET_ID=1BxiMVs... # ID del Google Sheet (del URL) PRISA_CONTENT_SHEET_NAME=ContentCalendar PRISA_NOTIFY_EMAIL=alro65@gmail.com ``` --- ## GOOGLE SHEETS — ESTRUCTURA ### Tab 1: ContentCalendar | Col | Header | Tipo | Valores permitidos | |-----|--------|------|-------------------| | A | `post_id` | Texto | `PY-2026-001` | | B | `scheduled_date` | Fecha ISO | `2026-05-06` | | C | `scheduled_time` | Hora 24h | `11:00` | | D | `platform` | Texto fijo | `instagram` / `facebook` / `both` | | E | `caption_EN` | Texto largo | Caption en inglés (max 2200 chars) | | F | `caption_ES` | Texto largo | Caption en español | | G | `hashtags` | Texto | `#tag1 #tag2 #tag3` | | H | `image_url` | URL | URL directa pública (ver nota crítica abajo) | | I | `pillar` | Texto | `electrical` / `battery` / `education` / `safety` / `promo` | | J | `status` | Texto fijo | `draft` / `scheduled` / `publishing` / `published` / `failed` / `skipped` | | K | `published_at` | Datetime | Lo escribe WF1 automáticamente | | L | `ig_media_id` | Texto | Lo escribe WF1 automáticamente | | M | `fb_post_id` | Texto | Lo escribe WF1 automáticamente | | N | `error_message` | Texto | Lo escribe WF1 si hay error | | O | `linkedin_copy` | Texto largo | Caption adaptado para LinkedIn (lo usa WF2) | ### Tab 2: Analytics Columnas A-R: `report_date`, `post_id`, `platform`, `published_date`, `pillar`, métricas IG (impressions, reach, likes, comments, saved, shares), métricas FB, engagement rates. WF3 appenda filas aquí cada domingo. --- ## WF1: CONTENT BUFFER PUBLISHER ### Flujo completo ``` [Schedule Trigger: 9AM / 12PM / 5PM ET] → [Google Sheets: Read ALL rows de ContentCalendar] → [Code: Filter — status=scheduled AND fecha=hoy AND hora±30min] → ¿Hay filas? NO → [NoOp Exit] FIN → ¿Hay filas? SÍ → [Split In Batches: 1 por vez] → [Google Sheets: Update status="publishing"] ← lock anti-duplicado → [Switch por platform:] instagram → [Code: Build caption] → [HTTP: Create IG Container] → [Wait 10s] → [HTTP: Publish IG Container] facebook → [Code: Build caption] → [HTTP: POST /photos a Facebook Page] both → ambas ramas secuencial (IG primero, luego FB) → [SUCCESS] → [Google Sheets: Update status="published", published_at, ig_media_id, fb_post_id] → [FAILURE] → [Google Sheets: Update status="failed", error_message] → [Gmail: Notify alro65@gmail.com] ``` ### API Calls exactas **Instagram — Crear container:** ``` POST https://graph.facebook.com/v21.0/{PRISA_IG_USER_ID}/media ?image_url={image_url} &caption={caption_EN}\n\n{caption_ES}\n\n{hashtags} &access_token={PRISA_META_PAGE_ACCESS_TOKEN} ``` Respuesta: `{ "id": "container_id" }` → esperar 10s → publicar **Instagram — Publicar container:** ``` POST https://graph.facebook.com/v21.0/{PRISA_IG_USER_ID}/media_publish ?creation_id={container_id} &access_token={PRISA_META_PAGE_ACCESS_TOKEN} ``` Respuesta: `{ "id": "published_media_id" }` → guardar en columna L **Facebook — Post con imagen:** ``` POST https://graph.facebook.com/v21.0/{PRISA_FB_PAGE_ID}/photos ?url={image_url} &message={caption_EN}\n\n{caption_ES}\n\n{hashtags} &access_token={PRISA_META_PAGE_ACCESS_TOKEN} ``` Respuesta: `{ "post_id": "...", "id": "..." }` → guardar en columna M ### Manejo de errores | Error | Acción | |-------|--------| | Token expirado (code 190) | Halt todo, email crítico a alro65@gmail.com | | imagen_url inválida (code 100) | Marcar fila `failed`, email, continuar con siguiente | | Container no listo (code 9007) | Retry 4x con delay 15s, luego `failed` | | Rate limit (429) | Retry 3x con delay 60s, luego `failed` | | Sheet no accesible | Halt todo, email crítico | ### Estados del row (ciclo completo) ``` draft → (operador promueve) → scheduled scheduled → (WF1 bloquea) → publishing publishing → (éxito) → published publishing → (error) → failed failed → (operador resetea) → scheduled ``` --- ## WF2: LINKEDIN REMINDER ``` [Schedule Trigger: Lun/Mié/Vie 12PM ET] → [Google Sheets: Read ContentCalendar] → [Code: Filter — fecha=hoy AND status=scheduled/published] → ¿Hay post? NO → [Gmail: "No hay post de LinkedIn hoy"] → ¿Hay post? SÍ → [Code: Build email — usar linkedin_copy si existe, sino caption_EN+ES] → [Gmail: Enviar a alro65@gmail.com con texto listo para copiar/pegar] ``` **Formato del email:** ``` Asunto: [Prisa Yachts] LinkedIn post listo — 2026-05-06 LinkedIn post de hoy: --- [texto del post] --- Instrucciones: 1. Ir a LinkedIn 2. Crear nuevo post 3. Copiar texto arriba 4. Subir imagen desde Google Drive 5. Publicar ``` --- ## WF3: WEEKLY ANALYTICS REPORT ``` [Schedule Trigger: Domingo 7PM ET] → [Google Sheets: Read ContentCalendar] → [Code: Filter — status=published AND published_at últimos 7 días] → ¿Posts? NO → [Gmail: "Sin publicaciones esta semana"] → ¿Posts? SÍ → [Split In Batches: 1 por vez] → [IF ig_media_id existe] → [HTTP GET /{ig_media_id}/insights?metric=impressions,reach,likes,comments,saved,shares] → [IF fb_post_id existe] → [HTTP GET /{fb_post_id}/insights?metric=post_impressions,post_impressions_unique,...] → [Code: Calcular engagement rate = (likes+comments+saves+shares)/reach*100] → [Google Sheets: Append row a Analytics tab] → [Merge: Esperar todos los items] → [Code: Build reporte — ordenar por engagement desc, top post, tabla completa] → [Gmail: Enviar reporte a alro65@gmail.com] ``` **Instagram insights endpoint:** ``` GET https://graph.facebook.com/v21.0/{ig_media_id}/insights ?metric=impressions,reach,likes,comments,saved,shares &access_token={token} ``` --- ## WF4: CONTENT PREP REMINDER ``` [Schedule Trigger: Domingo 8AM ET] → [Google Sheets: Read ContentCalendar] → [Code: Filter — scheduled_date próximos 7 días AND status=scheduled/draft] → ¿Hay posts? NO → [Gmail: "Sin posts programados para la semana"] → ¿Hay posts? SÍ → [Code: Separar los que tienen image_url vs los que no] → [Code: Build email] SI hay faltantes → "ACTION REQUIRED: X posts sin imagen" SI todos tienen → "Todo listo para la semana" → [Gmail: Enviar a alro65@gmail.com] ``` --- ## GUÍA DE IMPLEMENTACIÓN — ORDEN DE PASOS ### Paso 1: Credenciales en n8n 1. **Google Sheets OAuth2** — autorizar con la cuenta que tiene el Sheet 2. **Gmail OAuth2** — autorizar con alro65@gmail.com 3. **Meta API** — Header Auth: `Bearer {PRISA_META_PAGE_ACCESS_TOKEN}` ### Paso 2: Obtener los IDs de Meta **Page Access Token (long-lived):** ``` 1. developers.facebook.com → Graph API Explorer 2. Seleccionar App + Facebook Page 3. Pedir permisos: instagram_content_publish, pages_manage_posts, pages_read_engagement, instagram_manage_insights 4. GET /me/accounts → copiar access_token de tu página 5. Convertir a long-lived: GET https://graph.facebook.com/v21.0/oauth/access_token ?grant_type=fb_exchange_token &client_id={APP_ID} &client_secret={APP_SECRET} &fb_exchange_token={SHORT_TOKEN} ``` **Instagram Business Account ID:** ``` GET https://graph.facebook.com/v21.0/{PAGE_ID} ?fields=instagram_business_account &access_token={LONG_TOKEN} → respuesta: { "instagram_business_account": { "id": "ESTE_ES_EL_IG_USER_ID" } } ``` ### Paso 3: Crear el Google Sheet 1. Crear spreadsheet con 2 tabs: `ContentCalendar` y `Analytics` 2. Agregar headers exactos según tabla arriba 3. Agregar fila de prueba con status=`scheduled` y fecha=mañana ### Paso 4: Orden de construcción (de menor a mayor riesgo) 1. **WF4 primero** — solo lectura, envía email → probar sin riesgo 2. **WF2 segundo** — solo lectura + email → probar sin riesgo 3. **WF1 tercero** — publicación real → probar con 1 fila de prueba primero 4. **WF3 último** — requiere posts publicados con IDs reales para probar --- ## ⚠️ NOTA CRÍTICA: URLs DE IMÁGENES Meta Graph API NO acepta links de Google Drive del tipo: ``` ❌ https://drive.google.com/file/d/{ID}/view ``` Formato correcto (compartir como "Cualquiera con el link"): ``` ✅ https://drive.google.com/uc?export=download&id={FILE_ID} ``` Para obtener el FILE_ID: en el link de compartir de Drive, el ID es el string alfanumérico largo. Alternativa recomendada para producción: subir fotos a **Google Cloud Storage** con acceso público — URLs más estables y sin límites de descarga. --- ## TOKEN REFRESH (importante) El Page Access Token expira en **60 días**. Antes de que expire: - Opción A: Agregar WF5 "Token Expiry Reminder" que envía email cada 30 días - Opción B (recomendada): Usar **System User Token** en Meta Business Manager — no expira nunca --- ## PREGUNTAS A RESOLVER ANTES DEL PRIMER DEPLOY 1. ¿Tienes Meta Developer App creada para Prisa Yachts? 2. ¿Instagram está como cuenta Business vinculada a tu Facebook Page? 3. ¿Las fotos irán a Google Drive o a otro storage? 4. ¿Los posts serán bilingües (EN+ES en el mismo post) o separados?