{
  "name": "Generador de Carruseles con IA (Claude) - Plantilla",
  "nodes": [
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ ['idea','ideas listas'].includes(($json.property_estado || '').toLowerCase().trim()) }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            },
            {
              "id": "c2",
              "leftValue": "={{ $json.property_formato }}",
              "rightValue": "Carousel",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "c3",
              "leftValue": "={{ $json.property_link_drive }}",
              "operator": {
                "type": "string",
                "operation": "empty"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-should-process",
      "name": "¿Procesar Carrusel?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -624,
        48
      ]
    },
    {
      "parameters": {
        "resource": "block",
        "operation": "getAll",
        "blockId": {
          "__rl": true,
          "value": "={{ $json.id }}",
          "mode": "id"
        },
        "returnAll": true
      },
      "id": "get-blocks",
      "name": "Obtener Contenido de la Idea",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2.2,
      "position": [
        -80,
        0
      ],
      "credentials": {
        "notionApi": {
          "id": "8f6oSJUdJqqZ9Zkx",
          "name": "Notion account"
        }
      }
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "blocks",
        "options": {}
      },
      "id": "aggregate-blocks",
      "name": "Agrupar Bloques",
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        64,
        -16
      ]
    },
    {
      "parameters": {
        "jsCode": "const blocks = $json.blocks || [];\nconst lines = [];\n\nfor (const block of blocks) {\n  const type = block.type || '';\n  let text = (block.content || '').toString();\n\n  const bd = block[type];\n  if (bd && Array.isArray(bd.rich_text) && bd.rich_text.length > 0) {\n    text = bd.rich_text.map(rt => rt.plain_text || '').join('');\n  }\n  if (!text) {\n    const str = JSON.stringify(block);\n    const rx = /\"plain_text\":\"((?:[^\"\\\\]|\\\\.)*)\"/g;\n    let m, parts = [];\n    while ((m = rx.exec(str)) !== null) parts.push(m[1]);\n    text = parts.join('');\n  }\n  if (!text.trim()) continue;\n\n  if (type.startsWith('heading')) {\n    lines.push('\\n## ' + text.trim());\n  } else {\n    lines.push(text.trim());\n  }\n}\n\nconst fullText = lines.join('\\n').trim();\n\nreturn [{\n  json: {\n    ideaTitle:     $('¿Procesar Carrusel?').item.json.property_idea_title,\n    pageId:        $('¿Procesar Carrusel?').item.json.id,\n    fechaCreacion: $('¿Procesar Carrusel?').item.json.property_fecha_de_creacion,\n    contenido:     fullText,\n    blockCount:    blocks.length\n  }\n}];"
      },
      "id": "parse-script",
      "name": "Parsear Guion",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        -16
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $json.output || '';\nconst meta = $('Preparar Prompt').first().json;\n\nlet slides;\ntry {\n  const cleaned = raw.replace(/```json\\s*/g,'').replace(/```\\s*/g,'').trim();\n  const match = cleaned.match(/\\[[\\s\\S]*\\]/);\n  if (!match) throw new Error('No se encontró array JSON en el output');\n  slides = JSON.parse(match[0]);\n} catch(e) {\n  return [{ json: { error: 'Error parseando JSON: ' + e.message, raw: raw.slice(0,300) } }];\n}\n\nconst total = slides.length;\nconst VERDE = '#b0d12a';\nconst FONTS = \"@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;700;800;900&display=swap');\";\n\nfunction hl(t){ return (t||'').toString().replace(/\\|([^|]+)\\|/g,'<span class=\"hl\">$1</span>'); }\nfunction esc(t){ return (t||'').toString().replace(/</g,'&lt;').replace(/>/g,'&gt;'); }\n\nconst DARK_TYPES = ['cover','cta','headline','stat','visual'];\n\nfunction buildSlide(slide, idx) {\n  const type = slide.type || 'content';\n  const num = String(idx+1).padStart(2,'0');\n  const tot = String(total).padStart(2,'0');\n  const pct = ((idx+1)/total*100).toFixed(1);\n\n  const isDark = DARK_TYPES.includes(type) ? true : idx % 2 === 0;\n  const bg = isDark ? '#030301' : '#f7f7f2';\n  const fg = isDark ? '#ffffff' : '#030301';\n  const muted = isDark ? 'rgba(255,255,255,0.50)' : 'rgba(0,0,0,0.42)';\n  const body = isDark ? 'rgba(255,255,255,0.74)' : 'rgba(0,0,0,0.68)';\n  const track = isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)';\n  const divl = isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.10)';\n  const chev = isDark ? 'rgba(255,255,255,0.35)' : 'rgba(0,0,0,0.25)';\n  const fill = isDark ? '#ffffff' : VERDE;\n  const gradLight = 'linear-gradient(160deg,#f7f7f2 0%,#eeede8 100%)';\n\n  const BASE = FONTS + `\n    *{margin:0;padding:0;box-sizing:border-box;}\n    html{zoom:2.5714;}\n    html,body{width:420px;height:525px;overflow:hidden;font-family:'Plus Jakarta Sans',sans-serif;background:${bg};}\n    body{display:flex;flex-direction:column;position:relative;${isDark?'':'background:'+gradLight+';'}}\n    .hl{color:${VERDE};}\n    .bar{position:absolute;top:0;left:0;right:0;height:4px;background:${VERDE};z-index:10;}\n    .prog{position:absolute;bottom:0;left:0;right:0;padding:14px 28px 18px;z-index:10;}\n    .meta{display:flex;justify-content:space-between;align-items:center;margin-bottom:7px;}\n    .handle{font-size:9px;color:${muted};letter-spacing:2px;font-weight:700;}\n    .counter{font-size:10px;color:${muted};letter-spacing:2px;font-weight:600;font-family:'Courier New',monospace;}\n    .track{height:3px;background:${track};border-radius:2px;overflow:hidden;}\n    .fill{height:100%;width:${pct}%;background:${fill};border-radius:2px;}\n    .arr{position:absolute;right:0;top:0;height:100%;width:48px;display:flex;align-items:center;justify-content:center;z-index:10;}\n    .chev{width:24px;height:24px;stroke:${chev};stroke-width:2;fill:none;}\n    .emoji{font-family:'Segoe UI Emoji','Apple Color Emoji','Noto Color Emoji',sans-serif;}`;\n\n  const PROG = `<div class=\"prog\"><div class=\"meta\"><span class=\"handle\">@SOYJPPRIETO</span><span class=\"counter\">${num} / ${tot}</span></div><div class=\"track\"><div class=\"fill\"></div></div></div>`;\n  const ARR = `<div class=\"arr\"><svg class=\"chev\" viewBox=\"0 0 24 24\"><polyline points=\"9 18 15 12 9 6\"></polyline></svg></div>`;\n  const DOC = (css, inner, showArr=true) => `<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><style>${BASE}${css}</style></head><body><div class=\"bar\"></div>${inner}${showArr?ARR:''}${PROG}</body></html>`;\n\n  if (type === 'cover') {\n    return DOC(`\n    .wrap{flex:1;display:flex;flex-direction:column;justify-content:flex-end;padding:40px 40px 92px;}\n    .emojih{font-size:64px;line-height:1;margin-bottom:18px;}\n    .title{font-size:50px;font-weight:900;color:#fff;line-height:0.95;letter-spacing:-1.5px;text-transform:uppercase;margin-bottom:18px;}\n    .sub{font-size:15px;color:rgba(255,255,255,0.6);line-height:1.6;font-weight:500;max-width:300px;}`,\n    `<div class=\"wrap\">${slide.emoji?`<div class=\"emojih emoji\">${slide.emoji}</div>`:''}<h1 class=\"title\">${hl(slide.title)}</h1><p class=\"sub\">${esc(slide.subtitle)}</p></div>`);\n  }\n\n  if (type === 'headline') {\n    return DOC(`\n    .wrap{flex:1;display:flex;flex-direction:column;justify-content:center;padding:40px 40px 92px;gap:16px;}\n    .emojih{font-size:56px;line-height:1;margin-bottom:6px;}\n    .l1{font-size:42px;font-weight:900;color:#fff;line-height:1.0;letter-spacing:-1px;text-transform:uppercase;}\n    .rule{width:44px;height:3px;background:${VERDE};border-radius:2px;}\n    .l2{font-size:19px;font-weight:500;color:rgba(255,255,255,0.6);line-height:1.35;}`,\n    `<div class=\"wrap\">${slide.emoji?`<div class=\"emojih emoji\">${slide.emoji}</div>`:''}<h1 class=\"l1\">${hl(slide.line1)}</h1><div class=\"rule\"></div><p class=\"l2\">${hl(slide.line2)}</p></div>`);\n  }\n\n  if (type === 'stat') {\n    const duo = slide.stat2\n      ? `<div class=\"duo\"><span class=\"s\">${esc(slide.stat)}</span><span class=\"a\">${esc(slide.arrow||'→')}</span><span class=\"s hl2\">${esc(slide.stat2)}</span></div>`\n      : `<div class=\"big\">${esc(slide.stat)}</div>`;\n    return DOC(`\n    .wrap{flex:1;display:flex;flex-direction:column;justify-content:center;padding:40px 40px 92px;gap:18px;}\n    .label{font-size:10px;letter-spacing:3px;color:${VERDE};text-transform:uppercase;font-weight:700;}\n    .big{font-size:96px;font-weight:900;color:#fff;line-height:0.9;letter-spacing:-3px;}\n    .duo{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;}\n    .s{font-size:56px;font-weight:900;color:rgba(255,255,255,0.45);letter-spacing:-1px;}\n    .hl2{color:${VERDE};}\n    .a{font-size:32px;color:rgba(255,255,255,0.35);}\n    .ctx{font-size:15px;color:rgba(255,255,255,0.62);line-height:1.6;max-width:300px;}`,\n    `<div class=\"wrap\"><span class=\"label\">${esc(slide.label)}</span>${duo}<p class=\"ctx\">${esc(slide.context)}</p></div>`);\n  }\n\n  if (type === 'quote') {\n    return DOC(`\n    .wrap{flex:1;display:flex;flex-direction:column;justify-content:center;padding:44px 40px 96px;gap:12px;}\n    .mark{font-size:72px;color:${VERDE};line-height:0.5;margin-bottom:8px;font-weight:900;}\n    .q{font-size:26px;font-weight:700;color:${fg};line-height:1.35;letter-spacing:-0.3px;}\n    .who{font-size:10px;letter-spacing:2.5px;color:${muted};text-transform:uppercase;font-weight:700;margin-top:10px;}`,\n    `<div class=\"wrap\"><div class=\"mark\">&ldquo;</div><p class=\"q\">${hl(slide.quote)}</p><span class=\"who\">JP &middot; @SOYJPPRIETO</span></div>`);\n  }\n\n  if (type === 'steps') {\n    const items = (slide.steps||[]).map((s,i)=>`<div class=\"row\"><span class=\"n\">${String(i+1).padStart(2,'0')}</span><span class=\"t\">${hl(s)}</span></div>`).join('');\n    return DOC(`\n    .wrap{flex:1;display:flex;flex-direction:column;justify-content:center;padding:44px 40px 96px;}\n    .title{font-size:30px;font-weight:800;color:${fg};line-height:1.05;text-transform:uppercase;margin-bottom:22px;letter-spacing:-0.5px;}\n    .row{display:flex;gap:16px;align-items:baseline;padding:13px 0;border-top:1px solid ${divl};}\n    .n{font-size:15px;font-weight:900;color:${VERDE};flex-shrink:0;}\n    .t{font-size:15px;color:${body};line-height:1.5;font-weight:500;}`,\n    `<div class=\"wrap\"><h2 class=\"title\">${hl(slide.title)}</h2>${items}</div>`);\n  }\n\n  if (type === 'visual') {\n    return DOC(`\n    .wrap{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:40px 40px 92px;gap:24px;}\n    .e{font-size:80px;line-height:1;}\n    .t{font-size:19px;font-weight:600;color:rgba(255,255,255,0.88);line-height:1.4;max-width:290px;}`,\n    `<div class=\"wrap\"><div class=\"e emoji\">${slide.emoji||''}</div><p class=\"t\">${hl(slide.text)}</p></div>`);\n  }\n\n  if (type === 'cta') {\n    return DOC(`\n    .wrap{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:40px 36px 92px;gap:22px;}\n    .emojih{font-size:56px;line-height:1;}\n    .l1{font-size:38px;font-weight:900;color:#fff;line-height:1.0;letter-spacing:-0.8px;text-transform:uppercase;}\n    .l2{font-size:17px;color:rgba(255,255,255,0.6);font-weight:500;margin-top:-8px;}\n    .pill{background:${VERDE};color:#030301;font-size:12px;font-weight:800;text-transform:uppercase;letter-spacing:2px;padding:15px 34px;border-radius:100px;}\n    .handle2{font-size:10px;color:rgba(176,209,42,0.6);letter-spacing:2.5px;font-weight:700;}`,\n    `<div class=\"wrap\">${slide.emoji?`<div class=\"emojih emoji\">${slide.emoji}</div>`:''}<h2 class=\"l1\">${hl(slide.line1||slide.title)}</h2>${slide.line2?`<p class=\"l2\">${hl(slide.line2)}</p>`:''}<div class=\"pill\">${esc(slide.cta)||'AGENDEMOS'}</div><span class=\"handle2\">${esc(slide.handle)||'@soyjpprieto'}</span></div>`, false);\n  }\n\n  // CONTENT (default + red de seguridad para tipos desconocidos)\n  const title = slide.title || slide.line1 || slide.quote || '';\n  const bodyTxt = slide.body || slide.text || slide.context || (Array.isArray(slide.steps)?slide.steps.join(' &middot; '):'');\n  return DOC(`\n  .wrap{flex:1;display:flex;flex-direction:column;justify-content:center;padding:44px 40px 96px;}\n  .tag{display:inline-block;border:1px solid ${VERDE};color:${VERDE};text-transform:uppercase;font-size:10px;letter-spacing:2px;padding:7px 13px;margin-bottom:18px;width:fit-content;font-weight:700;}\n  .title{font-size:32px;font-weight:800;color:${fg};line-height:1.08;letter-spacing:-0.5px;text-transform:uppercase;margin-bottom:14px;}\n  .div{height:1px;background:${divl};margin-bottom:18px;}\n  .body{font-size:15px;color:${body};line-height:1.65;}`,\n  `<div class=\"wrap\">${slide.tag?`<span class=\"tag\">${esc(slide.tag)}</span>`:''}${slide.emoji?`<div style=\"font-size:40px;margin-bottom:12px;\" class=\"emoji\">${slide.emoji}</div>`:''}<h2 class=\"title\">${hl(title)}</h2><div class=\"div\"></div><p class=\"body\">${hl(bodyTxt)}</p></div>`);\n}\n\nreturn slides.map((slide, i) => ({\n  json: {\n    slideIndex: i + 1,\n    html: buildSlide(slide, i),\n    ideaTitle:     meta.ideaTitle || '',\n    pageId:        meta.pageId    || '',\n    fechaCreacion: meta.fechaCreacion || new Date().toISOString(),\n    folderWebViewLink: meta.folderWebViewLink || ''\n  }\n}));\n"
      },
      "id": "split-slides",
      "name": "Dividir Slides",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        848,
        -64
      ]
    },
    {
      "parameters": {
        "jsCode": "const meses = {1:\"01 - Enero\",2:\"02 - Febrero\",3:\"03 - Marzo\",4:\"04 - Abril\",5:\"05 - Mayo\",6:\"06 - Junio\",7:\"07 - Julio\",8:\"08 - Agosto\",9:\"09 - Septiembre\",10:\"10 - Octubre\",11:\"11 - Noviembre\",12:\"12 - Diciembre\"};\n\nlet meta;\ntry { meta = $('Preparar Prompt').all()[0].json; }\ncatch(e) { meta = $input.first().json; }\n\nlet fecha = new Date(meta.fechaCreacion || '');\nif (isNaN(fecha.getTime())) fecha = new Date();\n\nconst mesNombre = meses[fecha.getMonth() + 1];\nconst fechaStr  = fecha.toISOString().slice(0, 10);\n\nreturn [{ json: {\n  mesNombre,\n  nombreCarpeta: fechaStr + ' - ' + (meta.ideaTitle || 'Sin titulo'),\n  ideaTitle:     meta.ideaTitle || '',\n  pageId:        meta.pageId    || '',\n  fechaCreacion: fecha.toISOString()\n} }];"
      },
      "id": "resolve-folder",
      "name": "Resolver Carpeta del Mes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1056,
        -64
      ]
    },
    {
      "parameters": {
        "resource": "fileFolder",
        "filter": {
          "folderId": {
            "__rl": true,
            "value": "TU_FOLDER_ID_DE_DRIVE",
            "mode": "id"
          },
          "whatToSearch": "folders"
        },
        "options": {}
      },
      "id": "search-month-folder",
      "name": "Buscar Carpeta del Mes",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1264,
        -48
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "LDGGg1KhJIDPFVvZ",
          "name": "Google Drive account"
        }
      }
    },
    {
      "parameters": {
        "resource": "fileFolder",
        "filter": {
          "folderId": {
            "__rl": true,
            "value": "={{ $json.id }}",
            "mode": "id"
          },
          "whatToSearch": "folders"
        },
        "options": {}
      },
      "id": "search-carruseles-folder",
      "name": "Buscar Carpeta Carruseles",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1632,
        -32
      ],
      "alwaysOutputData": true,
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "LDGGg1KhJIDPFVvZ",
          "name": "Google Drive account"
        }
      }
    },
    {
      "parameters": {
        "resource": "folder",
        "name": "={{ $('Resolver Carpeta del Mes').first().json.nombreCarpeta }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "={{ $json.id }}",
          "mode": "id"
        },
        "options": {}
      },
      "id": "create-idea-folder",
      "name": "Crear Carpeta de la Idea",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        1952,
        -32
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "LDGGg1KhJIDPFVvZ",
          "name": "Google Drive account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const slides = $('Dividir Slides').all().map(item => item.json);\nconst folderId = $json.id;\nconst folderWebViewLink = $json.webViewLink || `https://drive.google.com/drive/folders/${folderId}`;\nreturn slides.map(s => ({ json: { ...s, folderId, folderWebViewLink } }));"
      },
      "id": "attach-folder",
      "name": "Adjuntar Carpeta a Slides",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2128,
        -16
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "loop-slides",
      "name": "Loop por Slide",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        2416,
        112
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://render-imagenes-production.up.railway.app/render",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-token",
              "value": "TU_TOKEN_AQUI"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "html",
              "value": "={{ $json.html }}"
            },
            {
              "name": "width",
              "value": "1080"
            },
            {
              "name": "height",
              "value": "1350"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "hcti-render",
      "name": "HCTI Renderizar PNG",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2656,
        128
      ]
    },
    {
      "parameters": {
        "name": "=slide_{{ $('Loop por Slide').item.json.slideIndex }}.png",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "={{ $('Loop por Slide').item.json.folderId }}",
          "mode": "id"
        },
        "options": {}
      },
      "id": "upload-drive",
      "name": "Subir Slide a Drive",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        3072,
        128
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "LDGGg1KhJIDPFVvZ",
          "name": "Google Drive account"
        }
      }
    },
    {
      "parameters": {
        "resource": "databasePage",
        "operation": "update",
        "pageId": {
          "__rl": true,
          "value": "={{ $('Preparar Prompt').first().json.pageId }}",
          "mode": "id"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Link drive|url",
              "urlValue": "={{ $('Adjuntar Carpeta a Slides').first().json.folderWebViewLink }}"
            },
            {
              "key": "Estado|status",
              "statusValue": "En Revision"
            }
          ]
        },
        "options": {}
      },
      "id": "update-notion",
      "name": "Actualizar Idea en Notion",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2.2,
      "position": [
        2672,
        -144
      ],
      "credentials": {
        "notionApi": {
          "id": "8f6oSJUdJqqZ9Zkx",
          "name": "Notion account"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.prompt }}",
        "options": {
          "systemMessage": "Eres un director de arte para Instagram. Generas HTML/CSS standalone listo para renderizarse como imagen. Tu trabajo es que cada slide se vea DISEÑADO, con carácter editorial — no generado por IA.\n\n## MARCA (soyjpprieto)\n- VERDE = #b0d12a\n- NEGRO = #030301\n- BLANCO = #ffffff\n- GRIS_CLARO = #f7f7f2\n- Secundario oscuro: rgba(255,255,255,0.5)\n- Secundario claro: rgba(3,3,1,0.4)\n- Fuente: 'Plus Jakarta Sans' via Google Fonts @import\n\n## ESCALA TIPOGRÁFICA — escala fija, no inventar tamaños\n- ULTRA:   96px / 900 / line-height 0.85  ← solo para stat hero o headline dominante\n- DISPLAY: 56px / 900 / line-height 0.9\n- HEAD:    36px / 800 / line-height 1.0\n- BODY:    15px / 400 / line-height 1.65\n- LABEL:   11px / 700 / uppercase / letter-spacing 2.5px\n- CAPTION: 10px / 500 / letter-spacing 0.5px\n- EMOJI_HERO: 72px  ← para emojis como elementos visuales grandes\n\n## EFECTOS DE FONDO — aplica siempre uno de estos\nGrano editorial (fondo oscuro): añade este SVG filter + overlay en todas las slides oscuras:\n<filter id=\"grain\"><feTurbulence type=\"fractalNoise\" baseFrequency=\"0.75\" numOctaves=\"4\" stitchTiles=\"stitch\"/><feColorMatrix type=\"saturate\" values=\"0\"/><feBlend in=\"SourceGraphic\" mode=\"overlay\" result=\"blend\"/></filter>\nAplícalo con: <rect width=\"100%\" height=\"100%\" filter=\"url(#grain)\" opacity=\"0.04\" style=\"position:absolute;inset:0;pointer-events:none\"/>\nFondo claro: sin grano, pero añade un gradiente sutil: background: linear-gradient(160deg, #f7f7f2 0%, #eeede8 100%)\n\n## EMOJIS — reglas de uso\n- Si el JSON trae \"emoji\", renderízalo como elemento visual independiente, nunca inline en el texto\n- Tamaño EMOJI_HERO (72px) con line-height 1, display block, no float\n- Posición: antes del título con margin-bottom 16px, o como elemento centrado en slides \"visual\"\n- Font-family: 'Segoe UI Emoji', 'Apple Color Emoji', sans-serif (para que el emoji se vea bien)\n- NUNCA más de 1 emoji por slide\n\n## ELEMENTOS COMUNES (todas las slides)\n- Barra verde 3px top: position absolute top 0, width 100%, background VERDE\n- Barra progreso: position absolute bottom 0, width 100%, padding 14px 28px 18px. Flex row: \"@soyjpprieto\" CAPTION secundario a la izquierda | track 3px flex-grow centrado | \"N/TOTAL\" CAPTION secundario a la derecha. Track fill: blanco en oscuro, VERDE en claro.\n- Flecha swipe: todas menos la última. Position absolute right 0, height 100%, width 44px, flex center. SVG chevron 20x32px, color secundario.\n- Padding contenido: 40px lateral, 28px top (tras barra verde), 72px bottom (sobre barra progreso)\n- Viewport: exactamente 420×525px\n\n## TIPO: cover\n- Fondo: NEGRO + grano\n- Layout: flex column, justify-center, altura completa\n- Emoji (si existe): EMOJI_HERO, VERDE, margin-bottom 20px\n- Título: DISPLAY (56px/900), MAYÚSCULAS, blanco. Una |PALABRA| → en VERDE, resto blanco. Letter-spacing -1px.\n- Línea acento: 3px × 40px VERDE, margin 20px 0\n- Subtítulo: BODY, secundario, minúsculas, max 10 palabras\n- Elemento decorativo: rectángulo VERDE 4px × 80px, position absolute left 0, centrado verticalmente\n\n## TIPO: headline\n- Fondo: alterna NEGRO y GRIS_CLARO\n- Layout: flex column, justify-center, todo centrado\n- Emoji (si existe): EMOJI_HERO centrado, margin-bottom 24px\n- line1: DISPLAY (56px/900). Si fondo oscuro → blanco. Si claro → NEGRO. MAYÚSCULAS. Letter-spacing -1.5px.\n- line2: HEAD (36px/700). Color VERDE. Minúsculas. Line-height 1.0. Margin-top 8px.\n- Sin body text, sin tag, sin divisores. Solo tipografía + emoji.\n- El slide ENTERO comunica una sola idea con fuerza.\n\n## TIPO: stat\n- Fondo: NEGRO + grano\n- Layout: flex column, justify-center\n- Label: LABEL, VERDE, margin-bottom 24px\n- Stat row: flex row, align-center, gap 16px\n  - stat: ULTRA (96px/900), blanco\n  - arrow (si existe): HEAD (36px/400), secundario\n  - stat2 (si existe): ULTRA (96px/900), VERDE\n- Línea: 2px × 48px VERDE, margin 28px 0\n- Context: BODY, secundario, max 10 palabras\n\n## TIPO: content\n- Fondo: alterna NEGRO y GRIS_CLARO\n- Emoji (si existe): EMOJI_HERO al inicio, arriba del tag, margin-bottom 20px\n- Tag: LABEL, VERDE, borde 1px VERDE, padding 6px 14px, inline-block, margin-bottom 20px\n- Título: HEAD (36px/800), una palabra en VERDE (la más impactante), resto en color base\n- Divisor: 1px, opacity 0.08, margin 16px 0\n- Body: BODY (15px/400/1.65), color base, máximo 3 oraciones\n\n## TIPO: quote\n- Fondo: NEGRO + grano\n- Layout: flex column, justify-center\n- Comilla decorativa: \" en 140px/900, VERDE, opacity 0.12, position absolute top 24px left 28px, z-index 0\n- Quote: HEAD (36px/700/1.15), blanco, position relative z-index 1. La palabra \"highlight\" va en VERDE.\n- Línea: 2px × 32px VERDE, margin 24px 0\n- Attribution: CAPTION, secundario\n\n## TIPO: steps\n- Fondo: GRIS_CLARO (gradiente)\n- Título: HEAD (36px/800), NEGRO, minúsculas, margin-bottom 28px\n- 3 filas, cada una flex row, align-center, gap 20px, padding 14px 0, borde-bottom 1px rgba(0,0,0,0.06)\n  - Número: 40px/900, VERDE, width 32px, flex-shrink 0\n  - Texto: BODY (15px/400), NEGRO\n- Sin emoji hero en este tipo\n\n## TIPO: visual\n- Fondo: NEGRO + grano\n- Layout: flex column, justify-center, align-center, texto centrado\n- Emoji: EMOJI_HERO (72px), margin-bottom 32px\n- Text: DISPLAY (56px/900), blanco, mayúsculas, max 4 palabras. Letter-spacing -1px.\n- Sin body, sin tag, máxima respiración\n\n## TIPO: cta\n- Fondo: NEGRO + grano\n- Sin flecha swipe\n- Progreso al 100%\n- line1: DISPLAY (56px/900), blanco, MAYÚSCULAS\n- line2: HEAD (36px/700), VERDE, minúsculas\n- Pill CTA: background VERDE, color NEGRO, LABEL, padding 16px 36px, border-radius 100px, margin-top 28px, display inline-block\n- Handle: CAPTION, secundario, margin-top 12px\n\nResponde con los N bloques HTML separados por <!-- SLIDE_N -->, nada más."
        }
      },
      "id": "claude-agent",
      "name": "Claude Diseña el Carrusel",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [
        560,
        -64
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "claude-opus-4-7",
          "mode": "list",
          "cachedResultName": "Claude Opus 4.7"
        },
        "options": {}
      },
      "id": "anthropic-chat-model",
      "name": "Anthropic Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.3,
      "position": [
        400,
        240
      ],
      "credentials": {
        "anthropicApi": {
          "id": "IiD0E74aZCdZqw89",
          "name": "Anthropic account"
        }
      }
    },
    {
      "parameters": {
        "chatId": "TU_CHAT_ID_DE_TELEGRAM",
        "text": "=🎨 Carrusel listo: {{ $('Preparar Prompt').first().json.ideaTitle }}\n\n📁 Revisa los slides aquí:\n{{ $('Adjuntar Carpeta a Slides').first().json.folderWebViewLink }}\n\nEstado movido a: En Revision",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML"
        }
      },
      "id": "telegram-notify",
      "name": "Notificar en Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2960,
        -128
      ],
      "webhookId": "0d52408b-720b-442a-a4de-a4ab22a5fb79",
      "executeOnce": true,
      "credentials": {
        "telegramApi": {
          "id": "cFKFhPh6bt9gSoaR",
          "name": "Sara CM Bot - Telegram"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const d = $json;\n\nconst prompt = `Eres un director creativo de contenido para Instagram con gusto editorial. Crea un carrusel de 6 a 8 slides para esta idea. El objetivo es que se vea DISEÑADO, con carácter — no generado por IA.\n\nTÍTULO: ${d.ideaTitle}\nCONTENIDO: ${d.contenido || '(usa el título para inferir el contenido)'}\n\nTIPOS DE SLIDE DISPONIBLES:\n- \"cover\"    → portada. Impacto máximo. Una idea, ejecutada con confianza.\n- \"headline\" → tipografía pura. Una frase corta y golpeadora, sin cuerpo de texto. Puede llevar emoji grande.\n- \"stat\"     → un número o dato que para el scroll. El dato ES el slide.\n- \"content\"  → slide estándar con contexto y desarrollo. Máximo 3 oraciones.\n- \"quote\"    → frase memorable de JP, en primera persona, como si la dijera en voz alta.\n- \"steps\"    → 3 pasos concretos en una sola slide.\n- \"visual\"   → un emoji o símbolo como elemento héroe (enorme), con una sola línea de texto debajo.\n- \"cta\"      → cierre. Invitación directa, no genérica.\n\nINSTRUCCIONES DE COPY:\n- Escribe como habla JP, no como un blog post. Directo, confiado, sin florituras.\n- Usa emojis CON INTENCIÓN — solo donde refuercen el mensaje (⚡ 🎯 📉 💡 🔑 ✅ etc). Máximo 1 por slide.\n- Alterna mayúsculas y minúsculas para ritmo: un título en MAYÚSCULAS, el siguiente en minúsculas.\n- Frases cortas. Si puedes decirlo en 5 palabras, no uses 10.\n- El tipo \"headline\" debe tener impacto inmediato — algo que alguien screenshot-earía.\n\nResponde ÚNICAMENTE con un array JSON válido. Sin texto antes ni después. Sin markdown.\n\nEstructura por tipo:\n\ncover:\n{ \"type\": \"cover\", \"emoji\": \"⚡\", \"title\": \"TITULO IMPACTANTE\", \"subtitle\": \"una frase que engancha, en minúsculas\" }\n\nheadline:\n{ \"type\": \"headline\", \"emoji\": \"🎯\", \"line1\": \"PRIMERA LÍNEA\", \"line2\": \"segunda línea en minúsculas\" }\n\nstat:\n{ \"type\": \"stat\", \"label\": \"DE ESTO\", \"stat\": \"6h\", \"arrow\": \"→\", \"stat2\": \"45min\", \"context\": \"eso es lo que tardas ahora.\" }\n\ncontent:\n{ \"type\": \"content\", \"tag\": \"LA REALIDAD\", \"title\": \"Una verdad incómoda\", \"body\": \"2-3 oraciones. Directas. Sin relleno.\", \"emoji\": \"💡\" }\n\nquote:\n{ \"type\": \"quote\", \"quote\": \"Una frase corta que JP diría en voz alta.\", \"highlight\": \"palabra clave a resaltar\" }\n\nsteps:\n{ \"type\": \"steps\", \"title\": \"el proceso real\", \"steps\": [\"paso uno concreto\", \"paso dos concreto\", \"paso tres concreto\"] }\n\nvisual:\n{ \"type\": \"visual\", \"emoji\": \"📉\", \"text\": \"Así se veía antes.\" }\n\ncta:\n{ \"type\": \"cta\", \"line1\": \"¿QUIERES ESTO\", \"line2\": \"para tu negocio?\", \"cta\": \"AGENDEMOS\", \"handle\": \"@soyjpprieto\" }\n\nREGLAS:\n- Mezcla los tipos — nunca más de 2 \"content\" seguidos\n- Un buen carrusel: cover → headline → stat → content → quote → steps → visual → cta\n- RESPONDE SOLO EL JSON, nada más`;\n\nreturn [{ json: { ...d, prompt } }];"
      },
      "id": "build-prompt",
      "name": "Preparar Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        400,
        -16
      ]
    },
    {
      "parameters": {
        "jsCode": "const mesNombre = $('Resolver Carpeta del Mes').first().json.mesNombre;\nconst folders = $input.all();\nconst match = folders.find(f =>\n  f.json.name === mesNombre ||\n  f.json.name.toLowerCase().includes(mesNombre.toLowerCase())\n);\nif (!match) throw new Error(`Carpeta del mes no encontrada: ${mesNombre}. Carpetas disponibles: ${folders.map(f=>f.json.name).join(', ')}`);\nreturn [match];"
      },
      "id": "filtrar-mes-correcto",
      "name": "Filtrar Mes Correcto",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        -48
      ]
    },
    {
      "parameters": {
        "jsCode": "const folders = $input.all();\nconst match = folders.find(f => f.json.name === 'Carruseles');\nif (!match) throw new Error(`Carpeta Carruseles no encontrada. Carpetas disponibles: ${folders.map(f=>f.json.name).join(', ')}`);\nreturn [match];"
      },
      "id": "filtrar-carpeta-carruseles",
      "name": "Filtrar Carpeta Carruseles",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1792,
        -32
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "id": "schedule-trigger",
      "name": "Cada 5 Minutos",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        -1024,
        48
      ]
    },
    {
      "parameters": {
        "resource": "databasePage",
        "operation": "getAll",
        "databaseId": {
          "__rl": true,
          "value": "TU_DATABASE_ID_DE_NOTION",
          "mode": "id"
        },
        "returnAll": true,
        "options": {}
      },
      "id": "notion-query-ideas",
      "name": "Buscar Ideas Carrusel",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2,
      "position": [
        -800,
        48
      ],
      "credentials": {
        "notionApi": {
          "id": "8f6oSJUdJqqZ9Zkx",
          "name": "Notion account"
        }
      }
    },
    {
      "parameters": {},
      "id": "limit-one-carousel",
      "name": "Procesar 1 a la Vez",
      "type": "n8n-nodes-base.limit",
      "typeVersion": 1,
      "position": [
        -384,
        32
      ]
    }
  ],
  "connections": {
    "¿Procesar Carrusel?": {
      "main": [
        [
          {
            "node": "Procesar 1 a la Vez",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Obtener Contenido de la Idea": {
      "main": [
        [
          {
            "node": "Agrupar Bloques",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agrupar Bloques": {
      "main": [
        [
          {
            "node": "Parsear Guion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parsear Guion": {
      "main": [
        [
          {
            "node": "Preparar Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Diseña el Carrusel": {
      "main": [
        [
          {
            "node": "Dividir Slides",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dividir Slides": {
      "main": [
        [
          {
            "node": "Resolver Carpeta del Mes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolver Carpeta del Mes": {
      "main": [
        [
          {
            "node": "Buscar Carpeta del Mes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Buscar Carpeta del Mes": {
      "main": [
        [
          {
            "node": "Filtrar Mes Correcto",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Buscar Carpeta Carruseles": {
      "main": [
        [
          {
            "node": "Filtrar Carpeta Carruseles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear Carpeta de la Idea": {
      "main": [
        [
          {
            "node": "Adjuntar Carpeta a Slides",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Adjuntar Carpeta a Slides": {
      "main": [
        [
          {
            "node": "Loop por Slide",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop por Slide": {
      "main": [
        [
          {
            "node": "Actualizar Idea en Notion",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "HCTI Renderizar PNG",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HCTI Renderizar PNG": {
      "main": [
        [
          {
            "node": "Subir Slide a Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Subir Slide a Drive": {
      "main": [
        [
          {
            "node": "Loop por Slide",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Claude Diseña el Carrusel",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Actualizar Idea en Notion": {
      "main": [
        [
          {
            "node": "Notificar en Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Prompt": {
      "main": [
        [
          {
            "node": "Claude Diseña el Carrusel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filtrar Mes Correcto": {
      "main": [
        [
          {
            "node": "Buscar Carpeta Carruseles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filtrar Carpeta Carruseles": {
      "main": [
        [
          {
            "node": "Crear Carpeta de la Idea",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Buscar Ideas Carrusel": {
      "main": [
        [
          {
            "node": "¿Procesar Carrusel?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Procesar 1 a la Vez": {
      "main": [
        [
          {
            "node": "Obtener Contenido de la Idea",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cada 5 Minutos": {
      "main": [
        [
          {
            "node": "Buscar Ideas Carrusel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}