feat: Agente-Marketing initial commit
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
# 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?
|
||||
Reference in New Issue
Block a user