Files
tablero-cdc-refrescar-banco…/tablero-cdc-refrescar-banco-fulgencio-diario.json

2729 lines
122 KiB
JSON

{
"updatedAt": "2026-06-25T03:27:49.324Z",
"createdAt": "2026-06-16T17:22:11.395Z",
"id": "g2JtKXNz5GDbkmQH",
"name": "Tablero CDC - Refrescar Banco Fulgencio Diario",
"description": null,
"active": true,
"isArchived": false,
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 23
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
-816,
-64
],
"id": "37cc170e-2f53-4e2e-915a-f6020e701c2e",
"name": "Schedule Trigger"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng",
"mode": "list",
"cachedResultName": "BANCO DE PROPUESTAS DE CDC PARA FULGENCIO FUMADO",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "propuestas",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit#gid=0"
},
"options": {
"dataLocationOnSheet": {
"values": {
"rangeDefinition": "specifyRange",
"firstDataRow": 465
}
}
}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-608,
-64
],
"id": "da0ce8a1-0efe-4f5b-909d-7f64b67e8f3b",
"name": "Sheets - Leer Banco Fulgencio",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "K0hDZh3a85MpOHCs",
"name": "Google Sheets account 2"
}
}
},
{
"parameters": {
"jsCode": "function clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction extractGoogleFileId(url) {\n const text = clean(url);\n\n if (!text) return '';\n\n const patterns = [\n /\\/d\\/([a-zA-Z0-9_-]+)/,\n /id=([a-zA-Z0-9_-]+)/,\n /\\/file\\/d\\/([a-zA-Z0-9_-]+)/,\n /\\/presentation\\/d\\/([a-zA-Z0-9_-]+)/,\n ];\n\n for (const pattern of patterns) {\n const match = text.match(pattern);\n if (match && match[1]) return match[1];\n }\n\n return '';\n}\n\nconst rows = $input.all();\n\nconst result = [];\n\nfor (const item of rows) {\n const row = item.json || {};\n\n const origen = clean(row.tablero_origen);\n const syncKey = clean(row.tablero_sync_key);\n const projectId = clean(row.tablero_project_id);\n const proposalUrl = clean(row['Enlace a la propuesta']);\n const existingFileId = clean(row.file_id);\n const fileId = existingFileId || extractGoogleFileId(proposalUrl);\n\n if (origen !== 'Tablero CDC') continue;\n if (!syncKey) continue;\n if (!projectId) continue;\n if (!proposalUrl) continue;\n\n result.push({\n json: {\n ...row,\n file_id_refresco: fileId,\n proposal_url_refresco: proposalUrl,\n refresh_attempts_actual: Number(row.refresh_attempts || 0),\n },\n });\n}\n\nreturn items;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-400,
-64
],
"id": "1422a9de-12a8-4b2d-bda9-9dc7193a5d67",
"name": "Code - Filtrar filas Tablero CDC"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
-176,
-64
],
"id": "3fb77467-0f3c-419e-8a6e-ab44d118ceef",
"name": "Loop - Filas Banco Fulgencio"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "8c79f0ce-b901-43c8-b863-ad490d267c37",
"leftValue": "={{ $json[\"file_id\"] }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-16,
-208
],
"id": "6896c55a-b17b-41bf-8f99-3fa949c29d67",
"name": "IF - Tiene file_id?"
},
{
"parameters": {
"url": "=https://www.googleapis.com/drive/v3/files/{{$json.file_id_refresco}}?fields=id,name,mimeType,modifiedTime,webViewLink,size&supportsAllDrives=true",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleDriveOAuth2Api",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
368,
-336
],
"id": "40ccb6f9-4e2d-4b21-ad1c-583151c84641",
"name": "HTTP - Obtener metadata Drive",
"retryOnFail": true,
"credentials": {
"googleDriveOAuth2Api": {
"id": "g23xdGLZRzBGqKgH",
"name": "Isaac - Google Drive"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "const original = $('IF - Tiene file_id?').item.json || {};\nconst metadata = $json || {};\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction toDate(value) {\n const date = value ? new Date(value) : null;\n if (!date || Number.isNaN(date.getTime())) return null;\n return date;\n}\n\nconst driveModifiedTime = clean(metadata.modifiedTime || metadata.modified_time);\nconst lastRefresh = clean(original.ultimo_refresco_ia);\nconst existingDriveModified = clean(original.drive_modified_time);\n\nconst driveDate = toDate(driveModifiedTime);\nconst lastRefreshDate = toDate(lastRefresh);\nconst existingDriveDate = toDate(existingDriveModified);\n\nconst driveSizeBytes = Number(metadata.size || 0);\nconst driveSizeMb = driveSizeBytes ? Number((driveSizeBytes / 1024 / 1024).toFixed(2)) : 0;\n\nlet shouldRefresh = false;\nlet reason = '';\n\nif (!driveDate) {\n shouldRefresh = false;\n reason = 'sin_modified_time';\n} else if (!lastRefreshDate) {\n shouldRefresh = true;\n reason = 'nunca_refrescado';\n} else if (driveDate > lastRefreshDate) {\n shouldRefresh = true;\n reason = 'archivo_modificado_desde_ultimo_refresco';\n} else if (existingDriveDate && driveDate > existingDriveDate) {\n shouldRefresh = true;\n reason = 'drive_modified_time_cambio';\n} else {\n shouldRefresh = false;\n reason = 'sin_cambios';\n}\n\nreturn [\n {\n json: {\n ...original,\n drive_file_id: metadata.id || original.file_id_refresco,\n drive_name: metadata.name || '',\n drive_mime_type: metadata.mimeType || '',\n drive_modified_time_actual: driveModifiedTime,\n drive_size_bytes: driveSizeBytes,\n drive_size_mb: driveSizeMb,\n should_refresh: shouldRefresh,\n refresh_reason: reason,\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
640,
-288
],
"id": "ebeb387e-0038-4166-b3a9-bdfdd863550a",
"name": "Code - Evaluar refresco"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "a6356649-3547-40be-bdc6-55730f280fa9",
"leftValue": "={{ $json.should_refresh }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
864,
-288
],
"id": "a0ac9c90-9029-41de-9338-1d076f045947",
"name": "IF - Debe refrescar?"
},
{
"parameters": {
"url": "=https://www.googleapis.com/drive/v3/files/{{ $json.drive_file_id }}/export?mimeType=text/plain",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleDriveOAuth2Api",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
2640,
-368
],
"id": "2eabdc4e-1bb5-4f8a-bacb-844e87d379c0",
"name": "HTTP - Exportar Slides TXT",
"retryOnFail": true,
"credentials": {
"googleDriveOAuth2Api": {
"id": "g23xdGLZRzBGqKgH",
"name": "Isaac - Google Drive"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "function getNodeJson(name) {\n try {\n return $(name).item.json || {};\n } catch (error) {\n return {};\n }\n}\n\nconst evaluated = getNodeJson('Code - Evaluar refresco');\nconst converted = getNodeJson('Code - Preparar ID convertido');\n\n// Si venimos de la rama PPTX convertida, usamos el JSON convertido.\n// Si venimos de Google Slides nativo, usamos el JSON evaluado.\nconst original = Object.keys(converted).length ? converted : evaluated;\n\nconst incoming = $json || {};\n\nfunction clean(value) {\n return String(value ?? '').trim().replace(/\\s+/g, ' ');\n}\n\nlet exportedText = '';\n\nif (typeof incoming === 'string') {\n exportedText = incoming;\n} else {\n exportedText =\n incoming.body ||\n incoming.data ||\n incoming.text ||\n incoming.content ||\n incoming.response ||\n '';\n}\n\nexportedText = clean(exportedText);\n\nconst hasContent = exportedText.length >= 80;\n\nreturn [\n {\n json: {\n ...original,\n contenido_presentacion: exportedText.slice(0, 18000),\n contenido_chars: exportedText.length,\n contenido_valido: hasContent,\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3536,
-432
],
"id": "fadb71e0-541f-4cc0-8e03-be43fb3fd841",
"name": "Code - Preparar texto IA Refresco"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "c0faa484-9c5d-4af3-b725-bfbc67f7f89d",
"leftValue": "={{ $json.contenido_valido }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
3744,
-432
],
"id": "cade8a8b-995a-4951-9b76-7f695a10fe7d",
"name": "IF - Tiene contenido?"
},
{
"parameters": {
"text": "=Nombre de la propuesta: {{$json.NOMBRE}}\nCliente: {{$json.CLIENTE}}\nMarca: {{$json.MARCA}}\nPaís: {{$json.PAIS}}\nLink: {{$json[\"Enlace a la propuesta\"]}}\n\nContenido exportado de la presentación:\n{{$json.contenido_presentacion}}",
"attributes": {
"attributes": [
{
"name": "nombre_propuesta",
"description": "Nombre limpio o comercial de la propuesta."
},
{
"name": "tipo_accion",
"description": "Tipo de acción o formato de propuesta. Ejemplos: campaña, evento, mueble, exhibidor, diseño gráfico, activación PDV, material POP, uniforme, promoción, stand. Si no se puede determinar, usar PENDIENTE."
},
{
"name": "canal",
"description": "Canal o lugar donde aplica la propuesta. Ejemplos: moderno, tradicional, online, PDV, no aplica. Si no se puede determinar, usar PENDIENTE."
},
{
"name": "tags",
"description": "Palabras clave separadas por comas para facilitar búsqueda. Ejemplos: campaña, corporativo, exhibidor, punto de venta, juegos, uniforme, material POP."
},
{
"name": "descripcion",
"description": "Párrafo profesional de 35 a 70 palabras. Debe iniciar preferiblemente con “La presentación muestra...”, “La pieza muestra...” o “La propuesta corresponde a...”. Debe mencionar cliente/marca, tipo de acción, concepto o tema principal y objetivo. No inventar detalles."
},
{
"name": "tactica_promocional",
"description": "Táctica promocional principal. Ejemplos: DEGUSTACIÓN, RULETA, SAMPLING - MUESTREO, PLINKO, WHATSAPP, LANDING PAGE, POP, PHOTOBOOTH, CANJE, SORTEO, PREMIOS INSTANTÁNEOS, JUEGO DIGITAL, TRIVIA, EXHIBICIÓN, IMPULSO o PENDIENTE."
}
]
},
"options": {
"systemPromptTemplate": "Analiza el contenido real exportado de una presentación de Google Slides para actualizar el banco de propuestas de Fulgencio Fumado.\n\nIMPORTANTE:\n- Usa el contenido de la presentación como fuente principal.\n- No cambies el país, cliente ni marca recibidos desde Tablero CDC.\n- No determines APROBADA. Ese campo debe quedar como PENDIENTE DE APROBACION.\n- No determines AMBIENTE DE COMPRA (RE). Ese campo debe quedar como PENDIENTE.\n- Si el contenido es insuficiente, usa PENDIENTE.\n- No inventes detalles.\n- Devuelve:\n nombre_propuesta\n tipo_accion\n canal\n tags\n descripcion\n tactica_promocional"
}
},
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"typeVersion": 1.2,
"position": [
4352,
-496
],
"id": "08c0d48c-400f-454a-832d-9e08d4724fbd",
"name": "Gemini - Reanalizar contenido Slides"
},
{
"parameters": {
"modelName": "models/gemini-3.1-pro-preview",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1.1,
"position": [
4352,
-288
],
"id": "5875c1c9-5aa3-4f60-a3ee-f94e0293ec04",
"name": "Google Gemini Chat Model",
"credentials": {
"googlePalmApi": {
"id": "jvsXYwL6IOoY2DBU",
"name": "Isaac - Gemini Api Pago"
}
}
},
{
"parameters": {
"jsCode": "const original = $('Code - Preparar texto IA Refresco').item.json || {};\nconst incoming = $input.first().json || {};\nconst ai = incoming.output || incoming;\n\nfunction clean(value) {\n if (Array.isArray(value)) return value.map(clean).filter(Boolean).join(', ');\n return String(value ?? '').trim().replace(/\\s+/g, ' ');\n}\n\nfunction removeAccents(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nfunction upper(value) {\n return removeAccents(value).toUpperCase();\n}\n\nfunction splitMulti(value) {\n return clean(value)\n .split(',')\n .map(v => upper(v))\n .map(v => v.trim())\n .filter(Boolean);\n}\n\nfunction unique(values) {\n return [...new Set(values.filter(Boolean))];\n}\n\nfunction joinMulti(values) {\n return unique(values).join(', ');\n}\n\nfunction normalizeTipoAccion(value) {\n const v = upper(value);\n\n const synonyms = {\n 'ACTIVACION PDV': 'ACTIVACION EN PDV',\n 'ACTIVACION EN PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'ACTIVACION PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'PDV': 'ACTIVACION EN PDV',\n 'DISEÑO GRAFICO': 'DISENO GRAFICO',\n 'DISEÑO GRÁFICO': 'DISENO GRAFICO',\n 'DISENO GRAFICO': 'DISENO GRAFICO',\n 'CAMPAÑA': 'CAMPAÑA',\n 'CAMPANA': 'CAMPAÑA',\n 'PROMOCION': 'PROMOCION',\n 'PROMOCIÓN': 'PROMOCION',\n 'MUEBLE': 'MUEBLE',\n 'EXHIBIDOR': 'MUEBLE',\n 'STAND': 'STAND',\n 'EVENTO': 'EVENTO',\n 'UNIFORME': 'UNIFORME',\n 'MATERIAL POP': 'DISENO GRAFICO',\n 'POP': 'DISENO GRAFICO',\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeCanal(value) {\n const v = upper(value);\n\n const synonyms = {\n 'PUNTO DE VENTA': 'PDV',\n 'PDV': 'PDV',\n 'CANAL MODERNO': 'MODERNO',\n 'MODERNO': 'MODERNO',\n 'CANAL TRADICIONAL': 'TRADICIONAL',\n 'TRADICIONAL': 'TRADICIONAL',\n 'DIGITAL': 'ONLINE',\n 'ONLINE': 'ONLINE',\n 'NO APLICA': 'NO APLICA',\n 'N/A': 'NO APLICA',\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeTactica(value) {\n const v = upper(value);\n\n const synonyms = {\n 'SAMPLING': 'SAMPLING - MUESTREO',\n 'MUESTREO': 'SAMPLING - MUESTREO',\n 'DEGUSTACION': 'DEGUSTACIÓN',\n 'DEGUSTACIÓN': 'DEGUSTACIÓN',\n 'RULETA DIGITAL': 'RULETA',\n 'RULETA': 'RULETA',\n 'PLINKO': 'PLINKO',\n 'WHATSAPP': 'WHATSAPP',\n 'LANDING': 'LANDING PAGE',\n 'LANDING PAGE': 'LANDING PAGE',\n 'MATERIAL POP': 'POP',\n 'POP': 'POP',\n 'JUEGO DIGITAL': 'JUEGO DIGITAL',\n 'JUEGOS DIGITALES': 'JUEGO DIGITAL',\n 'EXHIBICION': 'EXHIBICIÓN',\n 'EXHIBICIÓN': 'EXHIBICIÓN',\n 'IMPULSO': 'IMPULSO',\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeEtiqueta(value) {\n const v = upper(value);\n\n const synonyms = {\n 'ACTIVACION': 'ACTIVACION',\n 'ACTIVACIÓN': 'ACTIVACION',\n 'EXHIBICION': 'EXHIBICION',\n 'EXHIBICIÓN': 'EXHIBICION',\n 'EXHIBIDOR': 'EXHIBIDOR',\n 'DEGUSTACION': 'DEGUSTACION',\n 'DEGUSTACIÓN': 'DEGUSTACION',\n 'PROMOCION': 'PROMOCIÓN',\n 'PROMOCIÓN': 'PROMOCIÓN',\n 'DISENO': 'DISEÑO',\n 'DISEÑO': 'DISEÑO',\n 'CAMPANA': 'CAMPAÑA',\n 'CAMPAÑA': 'CAMPAÑA',\n 'JUEGO DIGITAL': 'JUEGOS DIGITALES',\n 'JUEGOS DIGITALES': 'JUEGOS DIGITALES',\n 'PUNTO DE VENTA': 'PUNTO DE VENTA',\n 'PDV': 'PUNTO DE VENTA',\n 'F1': 'JUEGOS',\n 'SIMULADOR': 'JUEGOS DIGITALES',\n 'EXPERIENCIA': 'ACTIVIDADES',\n 'CORPORATIVO': 'CORPORATIVO',\n 'UNIFORME': 'UNIFORMES',\n 'UNIFORMES': 'UNIFORMES',\n 'POP': 'PUNTO DE VENTA',\n 'MATERIAL POP': 'PUNTO DE VENTA',\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeOne(value, allowedList, fallback = 'PENDIENTE', normalizer = upper) {\n const raw = clean(value);\n if (!raw) return fallback;\n\n const mapped = normalizer(raw);\n return allowedList.includes(mapped) ? mapped : fallback;\n}\n\nfunction normalizeMulti(value, allowedList, fallback = 'PENDIENTE', unknownValue = 'OTRO', normalizer = upper) {\n const parts = splitMulti(value);\n if (!parts.length) return fallback;\n\n const result = [];\n\n for (const part of parts) {\n const mapped = normalizer(part);\n const expanded = String(mapped)\n .split(',')\n .map(v => v.trim())\n .filter(Boolean);\n\n for (const val of expanded) {\n if (allowedList.includes(val)) {\n result.push(val);\n } else {\n result.push(unknownValue);\n }\n }\n }\n\n const finalValues = unique(result);\n if (!finalValues.length) return fallback;\n\n return joinMulti(finalValues);\n}\n\nconst ALLOWED_TIPO_ACCION = [\n 'ACTIVACION EN PDV',\n 'IMPULSO DE VENTA',\n 'ACTIVACION FUERA DE PDV',\n 'EVENTO',\n 'KICK OFF',\n 'FERIA',\n 'STAND',\n 'MUEBLE',\n 'DISENO GRAFICO',\n 'DIGITAL',\n 'PUBLICIDAD',\n 'TUTORIAL',\n 'INFORMATIVA',\n 'LOGOTIPO',\n 'PROMOCION',\n 'UNIFORME',\n 'ACTIVACIÓN',\n 'INAUGURACIÓN',\n 'PARQUE',\n 'TROFEO',\n 'ANAQUEL',\n 'FOOD TRUCK',\n 'EMPAQUES',\n 'CAMPAÑA',\n 'QUICK COUNTER',\n 'DISEÑO',\n 'PPT',\n 'PENDIENTE',\n 'OTRO',\n];\n\nconst ALLOWED_CANAL = [\n 'MODERNO',\n 'TRADICIONAL',\n 'ONLINE',\n 'NO APLICA',\n 'PDV',\n 'PENDIENTE',\n];\n\nconst ALLOWED_TACTICA = [\n 'DEGUSTACIÓN',\n 'RULETA',\n 'SAMPLING - MUESTREO',\n 'PLINKO',\n 'WHATSAPP',\n 'LANDING PAGE',\n 'POP',\n 'PHOTOBOOTH',\n 'CANJE',\n 'SORTEO',\n 'PREMIOS INSTANTÁNEOS',\n 'JUEGO DIGITAL',\n 'TRIVIA',\n 'EXHIBICIÓN',\n 'IMPULSO',\n 'PENDIENTE',\n 'OTRO',\n];\n\nconst ALLOWED_ETIQUETAS = [\n 'ACTIVACION',\n 'ACTIVIDADES',\n 'BANDEJA',\n 'BTS',\n 'CORPORATIVO',\n 'DEGUSTACION',\n 'EXHIBICION',\n 'EXHIBIDOR',\n 'FUERZA DE VENTAS',\n 'JUEGOS',\n 'PREMIOS',\n 'PUNTO DE VENTA',\n 'REFERENCIAS',\n 'REUNIÓN',\n 'RULETA',\n 'SAMPLING',\n 'HALLOWEEN',\n 'LUCES NEON',\n 'INFORMATIVA',\n 'CARRITO DEGUSTACIÓN',\n 'RED SOCIAL/INFLUENCER',\n 'UNIFORMES',\n 'REGALIAS',\n 'STAND',\n 'CAPACITACIÓN',\n 'NAVIDAD',\n 'DÍA DE LAS MADRES',\n 'INDEPENDENCIA',\n 'LOGOTIPO',\n 'QUICK COUNTER',\n 'PPT',\n 'TEMPLATES',\n 'DISEÑO',\n 'PROMOCIÓN',\n 'MODERNO',\n 'MAYORISTA',\n 'PULPERÍAS',\n 'JUEGOS DIGITALES',\n 'WHATSAPP',\n 'CAMPAÑA',\n 'CONCURSO',\n 'REGALOS CORPORATIVOS',\n 'PENDIENTE',\n 'OTRO',\n];\n\nlet nombrePropuesta = clean(\n ai.nombre_propuesta ||\n original.NOMBRE ||\n original.nombre_limpio ||\n original.drive_name ||\n ''\n);\n\nif (!nombrePropuesta) {\n nombrePropuesta = 'PENDIENTE';\n}\n\nlet tipoAccion = normalizeMulti(\n ai.tipo_accion,\n ALLOWED_TIPO_ACCION,\n 'PENDIENTE',\n 'OTRO',\n normalizeTipoAccion\n);\n\nconst canal = normalizeOne(\n ai.canal,\n ALLOWED_CANAL,\n 'PENDIENTE',\n normalizeCanal\n);\n\nconst tacticaPromocional = normalizeMulti(\n ai.tactica_promocional,\n ALLOWED_TACTICA,\n 'PENDIENTE',\n 'OTRO',\n normalizeTactica\n);\n\nlet etiquetas = normalizeMulti(\n ai.tags,\n ALLOWED_ETIQUETAS,\n 'PENDIENTE',\n 'OTRO',\n normalizeEtiqueta\n);\n\nif (etiquetas.includes('OTRO') && etiquetas !== 'OTRO') {\n etiquetas = etiquetas\n .split(',')\n .map(v => v.trim())\n .filter(v => v !== 'OTRO')\n .join(', ');\n}\n\nconst ambienteCompra = 'PENDIENTE';\nconst aprobada = 'PENDIENTE DE APROBACION';\n\nlet descripcion = clean(ai.descripcion);\n\nif (!descripcion) {\n descripcion = clean(\n original.Descripcion ||\n original.NOMBRE ||\n original.drive_name ||\n ''\n );\n}\n\nlet requiereRevision = false;\nconst revisionReasons = [];\n\nif (tipoAccion.includes('OTRO') || tipoAccion === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('tipo_accion_revisar');\n}\n\nif (canal === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('canal_revisar');\n}\n\nif (tacticaPromocional.includes('OTRO') || tacticaPromocional === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('tactica_revisar');\n}\n\nif (etiquetas.includes('OTRO') || etiquetas === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('etiquetas_revisar');\n}\n\nreturn [\n {\n json: {\n NOMBRE: nombrePropuesta,\n 'TIPO DE ACCION': tipoAccion,\n CLIENTE: original.CLIENTE || '',\n MARCA: original.MARCA || '',\n PAIS: original.PAIS || '',\n CANAL: canal,\n 'AMBIENTE DE COMPRA (RE)': ambienteCompra,\n 'TÁCTICA PROMOCIONAL': tacticaPromocional,\n APROBADA: aprobada,\n ETIQUETAS: etiquetas,\n 'AÑO': original['AÑO'] || '',\n 'Enlace a la propuesta': original['Enlace a la propuesta'] || '',\n Descripcion: descripcion,\n\n file_id: original.file_id || original.file_id_refresco || original.drive_file_id_original || original.drive_file_id || '',\n nombre_archivo: original.nombre_archivo || original.NOMBRE || original.drive_name || '',\n mime_type: original.mime_type || original.drive_mime_type || '',\n fuente_pais: original.fuente_pais || 'Tablero CDC',\n confianza_pais: original.confianza_pais || '',\n requiere_revision: requiereRevision,\n motivos_revision: revisionReasons.join(', '),\n procesado_ia: true,\n ultima_actualizacion: new Date().toISOString(),\n 'Enlaces a propuestas ejecutadas': original['Enlaces a propuestas ejecutadas'] || '',\n\n tablero_sync_key: original.tablero_sync_key || '',\n tablero_project_id: original.tablero_project_id || '',\n tablero_origen: original.tablero_origen || 'Tablero CDC',\n\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'OK',\n refresh_attempts: original.refresh_attempts || 1,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4704,
-496
],
"id": "525e3816-a692-49e3-9885-20b8b4ddccbe",
"name": "Code - Normalizar respuesta refresco"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng",
"mode": "list",
"cachedResultName": "BANCO DE PROPUESTAS DE CDC PARA FULGENCIO FUMADO",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "propuestas",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"tablero_sync_key": "={{ $json[\"tablero_sync_key\"]}}",
"NOMBRE": "={{ $json[\"NOMBRE\"]}}",
"TIPO DE ACCION": "={{ $json[\"TIPO DE ACCION\"]}}",
"CLIENTE": "={{ $json[\"CLIENTE\"]}}",
"MARCA": "={{ $json[\"MARCA\"]}}",
"PAIS": "={{ $json[\"PAIS\"]}}",
"CANAL": "={{ $json[\"CANAL\"]}}",
"AMBIENTE DE COMPRA (RE)": "={{ $json[\"AMBIENTE DE COMPRA (RE)\"]}}",
"TÁCTICA PROMOCIONAL": "={{ $json[\"TÁCTICA PROMOCIONAL\"]}}",
"APROBADA": "={{ $json[\"APROBADA\"]}}",
"ETIQUETAS": "={{ $json[\"ETIQUETAS\"]}}",
"AÑO": "={{ $json[\"AÑO\"]}}",
"Enlace a la propuesta": "={{ $json[\"Enlace a la propuesta\"]}}",
"Descripcion": "={{ $json[\"Descripcion\"]}}",
"file_id": "={{ $json[\"file_id\"]}}",
"nombre_archivo": "={{ $json[\"nombre_archivo\"]}}",
"mime_type": "={{ $json[\"mime_type\"]}}",
"fuente_pais": "={{ $json[\"fuente_pais\"]}}",
"confianza_pais": "={{ $json[\"confianza_pais\"]}}",
"requiere_revision": "={{ $json[\"requiere_revision\"] ? \"TRUE\" : \"FALSE\"}}",
"procesado_ia": "={{ $json[\"procesado_ia\"] }}",
"ultima_actualizacion": "={{ $json[\"ultima_actualizacion\"]}}",
"motivos_revision": "={{ $json[\"motivos_revision\"]}}",
"Enlaces a propuestas ejecutadas": "={{ $json[\"Enlaces a propuestas ejecutadas\"]}}",
"tablero_project_id": "={{ $json[\"tablero_project_id\"]}}",
"tablero_origen": "={{ $json[\"tablero_origen\"]}}",
"drive_modified_time": "={{ $json[\"drive_modified_time\"]}}",
"ultimo_refresco_ia": "={{ $json[\"ultimo_refresco_ia\"]}}",
"refresh_status": "={{ $json[\"refresh_status\"]}}",
"refresh_attempts": "={{ $json[\"refresh_attempts\"]}}",
"converted_google_slides_id": "={{ $json[\"converted_google_slides_id\"]}}",
"converted_google_slides_link": "={{ $json[\"converted_google_slides_link\"]}}"
},
"matchingColumns": [
"tablero_sync_key"
],
"schema": [
{
"id": "NOMBRE",
"displayName": "NOMBRE",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "TIPO DE ACCION",
"displayName": "TIPO DE ACCION",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "CLIENTE",
"displayName": "CLIENTE",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "MARCA",
"displayName": "MARCA",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "PAIS",
"displayName": "PAIS",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "CANAL",
"displayName": "CANAL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "AMBIENTE DE COMPRA (RE)",
"displayName": "AMBIENTE DE COMPRA (RE)",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "TÁCTICA PROMOCIONAL",
"displayName": "TÁCTICA PROMOCIONAL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "APROBADA",
"displayName": "APROBADA",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ETIQUETAS",
"displayName": "ETIQUETAS",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "AÑO",
"displayName": "AÑO",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Enlace a la propuesta",
"displayName": "Enlace a la propuesta",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Descripcion",
"displayName": "Descripcion",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "file_id",
"displayName": "file_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "nombre_archivo",
"displayName": "nombre_archivo",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "mime_type",
"displayName": "mime_type",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "fuente_pais",
"displayName": "fuente_pais",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "confianza_pais",
"displayName": "confianza_pais",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "requiere_revision",
"displayName": "requiere_revision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "procesado_ia",
"displayName": "procesado_ia",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ultima_actualizacion",
"displayName": "ultima_actualizacion",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "motivos_revision",
"displayName": "motivos_revision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Enlaces a propuestas ejecutadas",
"displayName": "Enlaces a propuestas ejecutadas",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "tablero_sync_key",
"displayName": "tablero_sync_key",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "tablero_project_id",
"displayName": "tablero_project_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "tablero_origen",
"displayName": "tablero_origen",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "drive_modified_time",
"displayName": "drive_modified_time",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ultimo_refresco_ia",
"displayName": "ultimo_refresco_ia",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "refresh_status",
"displayName": "refresh_status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "refresh_attempts",
"displayName": "refresh_attempts",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "converted_google_slides_id",
"displayName": "converted_google_slides_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "converted_google_slides_link",
"displayName": "converted_google_slides_link",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
5472,
352
],
"id": "f6487012-88de-4be3-b62e-fa9bdfdda270",
"name": "Google Sheets - Upsert Banco Fulgencio",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "K0hDZh3a85MpOHCs",
"name": "Google Sheets account 2"
}
}
},
{
"parameters": {
"jsCode": "const original = $('Code - Preparar texto IA Refresco').item.json || {};\n\nreturn [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'SIN_CONTENIDO',\n refresh_attempts: original.refresh_attempts || 1,\n procesado_ia: false,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4128,
0
],
"id": "42c459da-dd49-4ed8-aa1d-ff1f39688acf",
"name": "Code - Marcar sin contenido"
},
{
"parameters": {
"jsCode": "function nowIso() {\n return new Date().toISOString();\n}\n\nfunction appendMotivo(existing, motivo) {\n const current = String(existing || \"\").trim();\n\n if (!current) return motivo;\n\n const parts = current\n .split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n\n if (!parts.includes(motivo)) {\n parts.push(motivo);\n }\n\n return parts.join(\", \");\n}\n\nfunction buildTableroSyncKey(data) {\n const tableroProjectId = String(data.tablero_project_id || \"\").trim();\n const fileId = String(data.file_id || \"\").trim();\n const existingKey = String(data.tablero_sync_key || \"\").trim();\n\n if (existingKey) return existingKey;\n if (tableroProjectId && fileId) return `${tableroProjectId}::${fileId}`;\n if (fileId) return `drive::${fileId}`;\n if (tableroProjectId) return `${tableroProjectId}::sin_file_id`;\n\n return \"\";\n}\n\nreturn items.map((item) => {\n const data = item.json;\n\n const fileId = String(data.file_id || \"\").trim();\n\n /**\n * Seguridad:\n * Si este nodo recibe por error una fila que SÍ tiene file_id,\n * no la marca como SIN_FILE_ID.\n */\n if (fileId) {\n return {\n json: {\n ...data,\n tablero_sync_key: buildTableroSyncKey(data),\n refresh_status: data.refresh_status || \"PENDIENTE\",\n ultima_actualizacion: data.ultima_actualizacion || nowIso(),\n },\n };\n }\n\n return {\n json: {\n ...data,\n file_id: \"\",\n tablero_sync_key: buildTableroSyncKey(data),\n refresh_status: \"SIN_FILE_ID\",\n refresh_attempts: Number(data.refresh_attempts || 0) + 1,\n requiere_revision: true,\n procesado_ia: false,\n ultima_actualizacion: nowIso(),\n ultimo_refresco_ia: data.ultimo_refresco_ia || \"\",\n motivos_revision: appendMotivo(data.motivos_revision, \"sin_file_id\"),\n },\n };\n});"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
736,
112
],
"id": "6287af34-de8e-49aa-b432-778c016c9f8d",
"name": "Code - Marcar sin file_id"
},
{
"parameters": {
"jsCode": "const original = $('IF - Tiene file_id?').item.json || {};\nconst errorData = $json || {};\n\nfunction stringifyError(value) {\n try {\n if (typeof value === 'string') return value;\n return JSON.stringify(value).slice(0, 500);\n } catch (error) {\n return String(value).slice(0, 500);\n }\n}\n\nreturn [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'ERROR_METADATA_DRIVE',\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n procesado_ia: false,\n motivos_revision: `error_metadata_drive: ${stringifyError(errorData)}`,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
688,
-96
],
"id": "389332ff-4d6b-4a5d-824d-28a1098030ef",
"name": "Code - Marcar error metadata Drive"
},
{
"parameters": {
"jsCode": "function getNodeJson(name) {\n try {\n return $(name).item.json || {};\n } catch (error) {\n return {};\n }\n}\n\nconst evaluated = getNodeJson('Code - Evaluar refresco');\nconst converted = getNodeJson('Code - Preparar ID convertido');\n\nconst original = Object.keys(converted).length ? converted : evaluated;\nconst errorData = $json || {};\n\nfunction stringifyError(value) {\n try {\n if (typeof value === 'string') return value;\n return JSON.stringify(value).slice(0, 500);\n } catch (error) {\n return String(value).slice(0, 500);\n }\n}\n\nreturn [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'ERROR_EXPORT_SLIDES',\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n procesado_ia: false,\n motivos_revision: `error_export_slides: ${stringifyError(errorData)}`,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3472,
-240
],
"id": "251b1833-27c9-4a63-9a97-6ad600ea5283",
"name": "Code - Marcar error export Slides"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "1dd1c68d-37b9-4bde-8ff6-7556f7fe979b",
"leftValue": "={{ $json.drive_mime_type }}",
"rightValue": "application/vnd.google-apps.presentation",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1408,
-304
],
"id": "5de289da-1c11-422e-be69-73d6d8c67db8",
"name": "IF - Es Google Slides nativo?"
},
{
"parameters": {
"method": "POST",
"url": "https://script.google.com/macros/s/AKfycbwGdQZT_tRCWmUsIw9LzwzWClHx12CKVI23dbE-gUwVZ-dorjJJuQLWHxUACnH2vlK-Jw/exec",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "token",
"value": "tablero-cdc-fulgencio-convert-2026-isaac"
},
{
"name": "file_id",
"value": "={{ $json.file_id_refresco || $json.drive_file_id }}"
},
{
"name": "previous_converted_id",
"value": "={{ $json.converted_google_slides_id }}"
},
{
"name": "folder_id",
"value": "1wUTt7ttK3EafPi-L8bIj-J_ZdN9EPg4r"
},
{
"name": "replace_existing",
"value": "true"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1936,
-192
],
"id": "150797a7-4db0-4aff-88ed-d18eb0afc202",
"name": "HTTP - Convertir PPTX con Apps Script"
},
{
"parameters": {
"jsCode": "const original = $('IF - Es Google Slides nativo?').item.json || {};\nconst response = $json || {};\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nif (!response.ok || !response.converted_google_slides_id) {\n return [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'ERROR_CONVERT_PPTX',\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n procesado_ia: false,\n motivos_revision: `error_convert_pptx: ${clean(response.error || JSON.stringify(response)).slice(0, 300)}`,\n },\n },\n ];\n}\n\nreturn [\n {\n json: {\n ...original,\n\n drive_file_id_original: original.drive_file_id || original.file_id_refresco || '',\n drive_file_id: response.converted_google_slides_id,\n drive_mime_type: 'application/vnd.google-apps.presentation',\n\n converted_google_slides_id: response.converted_google_slides_id,\n converted_google_slides_link: response.converted_google_slides_link,\n\n refresh_status: 'CONVERTIDO_PPTX',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2112,
-192
],
"id": "fcc131cb-6181-41fc-9231-129dfc7d6095",
"name": "Code - Preparar ID convertido"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "f05881a1-7e59-447e-830d-a7bb81d74c7e",
"leftValue": "={{ $json.refresh_status }}",
"rightValue": "ERROR_CONVERT_PPTX",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
2320,
-192
],
"id": "dd855b50-ab6f-449d-8c5c-6c5b5a80f339",
"name": "IF - Conversion OK?"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d8425980-f331-4f23-9cfb-118077f4437e",
"leftValue": "={{ $json.drive_size_mb }}",
"rightValue": 100,
"operator": {
"type": "number",
"operation": "lte"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1664,
-144
],
"id": "ea25df58-fe23-4f28-83d0-2ce8851aa353",
"name": "IF - PPTX pesa <= 100MB?"
},
{
"parameters": {
"jsCode": "const original = $json || {};\n\nreturn [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'PPTX_DEMASIADO_PESADO',\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n procesado_ia: false,\n motivos_revision: `pptx_demasiado_pesado_para_conversion_google_slides: ${original.drive_size_mb || 0} MB. Google Slides solo permite convertir presentaciones hasta 100 MB.`,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2320,
96
],
"id": "2390d0c3-bf35-4480-8987-6ee1509d211e",
"name": "Code - Marcar PPTX demasiado pesado"
},
{
"parameters": {
"content": "## 📅 ENTRADA DIARIA — BANCO FULGENCIO\n\nEste flujo corre automáticamente todos los días a las 23:00.\n\nLee el Banco de Propuestas de Fulgencio y procesa solo filas provenientes de Tablero CDC.\n\nCriterios mínimos:\n- tablero_origen = Tablero CDC\n- tablero_sync_key presente\n- tablero_project_id presente\n- Enlace a la propuesta presente\n\nObjetivo:\nmantener actualizado el banco que consulta FulgencioChat.",
"height": 544,
"width": 976,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-864,
-384
],
"id": "9adcd60e-20bc-4e8a-a822-daaadb405c8e",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## 🔎 EVALUACIÓN DE CAMBIOS EN DRIVE\n\nPor cada fila válida, el flujo obtiene metadata del archivo en Google Drive.\n\nCompara:\n- drive_modified_time actual\n- ultimo_refresco_ia guardado\n- drive_modified_time previo\n\nSolo refresca si:\n- nunca se refrescó\n- el archivo cambió después del último refresco\n- cambió la fecha de modificación en Drive\n\nSi no hay cambios, pasa a la siguiente fila.",
"height": 896,
"width": 704,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
320,
-640
],
"id": "fa12fd6a-ee6b-47ee-af68-5fb1f186bd9a",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "## 📄 EXTRACCIÓN DE CONTENIDO\n\nSi el archivo es Google Slides nativo:\n- se exporta directamente a texto.\n\nSi el archivo es PPTX:\n- solo se convierte si pesa 100 MB o menos.\n- se convierte con Apps Script a Google Slides.\n- luego se exporta a texto.\n\nErrores controlados:\n- SIN_FILE_ID\n- ERROR_METADATA_DRIVE\n- ERROR_EXPORT_SLIDES\n- ERROR_CONVERT_PPTX\n- PPTX_DEMASIADO_PESADO\n- SIN_CONTENIDO\n\nUna fila con error se marca en el banco y el flujo continúa.",
"height": 1152,
"width": 1456,
"color": 2
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1344,
-832
],
"id": "75a29688-9331-4fdb-928f-600f4bd21136",
"name": "Sticky Note2"
},
{
"parameters": {
"content": "## 🤖 REANÁLISIS IA Y UPSERT AL BANCO\n\nGemini analiza el texto exportado de la presentación.\n\nPuede actualizar:\n- nombre_propuesta\n- tipo_accion\n- canal\n- tags\n- descripcion\n- tactica_promocional\n\nNo debe cambiar:\n- país\n- cliente\n- marca\n- enlace\n- identificadores de Tablero CDC\n\nReglas fijas:\n- APROBADA = PENDIENTE DE APROBACION\n- AMBIENTE DE COMPRA (RE) = PENDIENTE\n\nEl banco se actualiza usando:\ntablero_sync_key\n\nTambién guarda:\n- refresh_status\n- refresh_attempts\n- ultimo_refresco_ia\n- drive_modified_time\n- requiere_revision\n- motivos_revision",
"height": 1760,
"width": 2576,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
3152,
-1136
],
"id": "fc72547c-317e-4ac7-9428-825e8504f143",
"name": "Sticky Note3"
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "Sheets - Leer Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Sheets - Leer Banco Fulgencio": {
"main": [
[
{
"node": "Code - Filtrar filas Tablero CDC",
"type": "main",
"index": 0
}
]
]
},
"Code - Filtrar filas Tablero CDC": {
"main": [
[
{
"node": "Loop - Filas Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Loop - Filas Banco Fulgencio": {
"main": [
[],
[
{
"node": "IF - Tiene file_id?",
"type": "main",
"index": 0
}
]
]
},
"IF - Tiene file_id?": {
"main": [
[
{
"node": "HTTP - Obtener metadata Drive",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar sin file_id",
"type": "main",
"index": 0
}
]
]
},
"HTTP - Obtener metadata Drive": {
"main": [
[
{
"node": "Code - Evaluar refresco",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar error metadata Drive",
"type": "main",
"index": 0
}
]
]
},
"Code - Evaluar refresco": {
"main": [
[
{
"node": "IF - Debe refrescar?",
"type": "main",
"index": 0
}
]
]
},
"IF - Debe refrescar?": {
"main": [
[
{
"node": "IF - Es Google Slides nativo?",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop - Filas Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"HTTP - Exportar Slides TXT": {
"main": [
[
{
"node": "Code - Preparar texto IA Refresco",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar error export Slides",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar texto IA Refresco": {
"main": [
[
{
"node": "IF - Tiene contenido?",
"type": "main",
"index": 0
}
]
]
},
"IF - Tiene contenido?": {
"main": [
[
{
"node": "Gemini - Reanalizar contenido Slides",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar sin contenido",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "Gemini - Reanalizar contenido Slides",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Gemini - Reanalizar contenido Slides": {
"main": [
[
{
"node": "Code - Normalizar respuesta refresco",
"type": "main",
"index": 0
}
]
]
},
"Code - Normalizar respuesta refresco": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar sin contenido": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets - Upsert Banco Fulgencio": {
"main": [
[
{
"node": "Loop - Filas Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar sin file_id": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar error metadata Drive": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar error export Slides": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"IF - Es Google Slides nativo?": {
"main": [
[
{
"node": "HTTP - Exportar Slides TXT",
"type": "main",
"index": 0
}
],
[
{
"node": "IF - PPTX pesa <= 100MB?",
"type": "main",
"index": 0
}
]
]
},
"HTTP - Convertir PPTX con Apps Script": {
"main": [
[
{
"node": "Code - Preparar ID convertido",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar ID convertido": {
"main": [
[
{
"node": "IF - Conversion OK?",
"type": "main",
"index": 0
}
]
]
},
"IF - Conversion OK?": {
"main": [
[
{
"node": "HTTP - Exportar Slides TXT",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"IF - PPTX pesa <= 100MB?": {
"main": [
[
{
"node": "HTTP - Convertir PPTX con Apps Script",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar PPTX demasiado pesado",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar PPTX demasiado pesado": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"staticData": {
"node:Schedule Trigger": {
"recurrenceRules": []
}
},
"meta": {
"templateCredsSetupCompleted": true
},
"versionId": "313b1289-adef-4382-b285-8866e343bef5",
"activeVersionId": "313b1289-adef-4382-b285-8866e343bef5",
"versionCounter": 418,
"triggerCount": 1,
"shared": [
{
"updatedAt": "2026-06-16T17:22:11.399Z",
"createdAt": "2026-06-16T17:22:11.399Z",
"role": "workflow:owner",
"workflowId": "g2JtKXNz5GDbkmQH",
"projectId": "PJpTANzTXIFibWsW",
"project": {
"updatedAt": "2026-04-22T14:25:09.686Z",
"createdAt": "2026-04-22T14:22:54.790Z",
"id": "PJpTANzTXIFibWsW",
"name": "Isaac Aracena <iaracena@gomezleemarketing.com>",
"type": "personal",
"icon": null,
"description": null,
"creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
}
}
],
"tags": [],
"activeVersion": {
"updatedAt": "2026-06-25T03:27:50.000Z",
"createdAt": "2026-06-25T03:27:49.327Z",
"versionId": "313b1289-adef-4382-b285-8866e343bef5",
"workflowId": "g2JtKXNz5GDbkmQH",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 23
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
-816,
-64
],
"id": "37cc170e-2f53-4e2e-915a-f6020e701c2e",
"name": "Schedule Trigger"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng",
"mode": "list",
"cachedResultName": "BANCO DE PROPUESTAS DE CDC PARA FULGENCIO FUMADO",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "propuestas",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit#gid=0"
},
"options": {
"dataLocationOnSheet": {
"values": {
"rangeDefinition": "specifyRange",
"firstDataRow": 465
}
}
}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-608,
-64
],
"id": "da0ce8a1-0efe-4f5b-909d-7f64b67e8f3b",
"name": "Sheets - Leer Banco Fulgencio",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "K0hDZh3a85MpOHCs",
"name": "Google Sheets account 2"
}
}
},
{
"parameters": {
"jsCode": "function clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction extractGoogleFileId(url) {\n const text = clean(url);\n\n if (!text) return '';\n\n const patterns = [\n /\\/d\\/([a-zA-Z0-9_-]+)/,\n /id=([a-zA-Z0-9_-]+)/,\n /\\/file\\/d\\/([a-zA-Z0-9_-]+)/,\n /\\/presentation\\/d\\/([a-zA-Z0-9_-]+)/,\n ];\n\n for (const pattern of patterns) {\n const match = text.match(pattern);\n if (match && match[1]) return match[1];\n }\n\n return '';\n}\n\nconst rows = $input.all();\n\nconst result = [];\n\nfor (const item of rows) {\n const row = item.json || {};\n\n const origen = clean(row.tablero_origen);\n const syncKey = clean(row.tablero_sync_key);\n const projectId = clean(row.tablero_project_id);\n const proposalUrl = clean(row['Enlace a la propuesta']);\n const existingFileId = clean(row.file_id);\n const fileId = existingFileId || extractGoogleFileId(proposalUrl);\n\n if (origen !== 'Tablero CDC') continue;\n if (!syncKey) continue;\n if (!projectId) continue;\n if (!proposalUrl) continue;\n\n result.push({\n json: {\n ...row,\n file_id_refresco: fileId,\n proposal_url_refresco: proposalUrl,\n refresh_attempts_actual: Number(row.refresh_attempts || 0),\n },\n });\n}\n\nreturn items;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-400,
-64
],
"id": "1422a9de-12a8-4b2d-bda9-9dc7193a5d67",
"name": "Code - Filtrar filas Tablero CDC"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
-176,
-64
],
"id": "3fb77467-0f3c-419e-8a6e-ab44d118ceef",
"name": "Loop - Filas Banco Fulgencio"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "8c79f0ce-b901-43c8-b863-ad490d267c37",
"leftValue": "={{ $json[\"file_id\"] }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-16,
-208
],
"id": "6896c55a-b17b-41bf-8f99-3fa949c29d67",
"name": "IF - Tiene file_id?"
},
{
"parameters": {
"url": "=https://www.googleapis.com/drive/v3/files/{{$json.file_id_refresco}}?fields=id,name,mimeType,modifiedTime,webViewLink,size&supportsAllDrives=true",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleDriveOAuth2Api",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
368,
-336
],
"id": "40ccb6f9-4e2d-4b21-ad1c-583151c84641",
"name": "HTTP - Obtener metadata Drive",
"retryOnFail": true,
"credentials": {
"googleDriveOAuth2Api": {
"id": "g23xdGLZRzBGqKgH",
"name": "Isaac - Google Drive"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "const original = $('IF - Tiene file_id?').item.json || {};\nconst metadata = $json || {};\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nfunction toDate(value) {\n const date = value ? new Date(value) : null;\n if (!date || Number.isNaN(date.getTime())) return null;\n return date;\n}\n\nconst driveModifiedTime = clean(metadata.modifiedTime || metadata.modified_time);\nconst lastRefresh = clean(original.ultimo_refresco_ia);\nconst existingDriveModified = clean(original.drive_modified_time);\n\nconst driveDate = toDate(driveModifiedTime);\nconst lastRefreshDate = toDate(lastRefresh);\nconst existingDriveDate = toDate(existingDriveModified);\n\nconst driveSizeBytes = Number(metadata.size || 0);\nconst driveSizeMb = driveSizeBytes ? Number((driveSizeBytes / 1024 / 1024).toFixed(2)) : 0;\n\nlet shouldRefresh = false;\nlet reason = '';\n\nif (!driveDate) {\n shouldRefresh = false;\n reason = 'sin_modified_time';\n} else if (!lastRefreshDate) {\n shouldRefresh = true;\n reason = 'nunca_refrescado';\n} else if (driveDate > lastRefreshDate) {\n shouldRefresh = true;\n reason = 'archivo_modificado_desde_ultimo_refresco';\n} else if (existingDriveDate && driveDate > existingDriveDate) {\n shouldRefresh = true;\n reason = 'drive_modified_time_cambio';\n} else {\n shouldRefresh = false;\n reason = 'sin_cambios';\n}\n\nreturn [\n {\n json: {\n ...original,\n drive_file_id: metadata.id || original.file_id_refresco,\n drive_name: metadata.name || '',\n drive_mime_type: metadata.mimeType || '',\n drive_modified_time_actual: driveModifiedTime,\n drive_size_bytes: driveSizeBytes,\n drive_size_mb: driveSizeMb,\n should_refresh: shouldRefresh,\n refresh_reason: reason,\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
640,
-288
],
"id": "ebeb387e-0038-4166-b3a9-bdfdd863550a",
"name": "Code - Evaluar refresco"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "a6356649-3547-40be-bdc6-55730f280fa9",
"leftValue": "={{ $json.should_refresh }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
864,
-288
],
"id": "a0ac9c90-9029-41de-9338-1d076f045947",
"name": "IF - Debe refrescar?"
},
{
"parameters": {
"url": "=https://www.googleapis.com/drive/v3/files/{{ $json.drive_file_id }}/export?mimeType=text/plain",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleDriveOAuth2Api",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
2640,
-368
],
"id": "2eabdc4e-1bb5-4f8a-bacb-844e87d379c0",
"name": "HTTP - Exportar Slides TXT",
"retryOnFail": true,
"credentials": {
"googleDriveOAuth2Api": {
"id": "g23xdGLZRzBGqKgH",
"name": "Isaac - Google Drive"
}
},
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "function getNodeJson(name) {\n try {\n return $(name).item.json || {};\n } catch (error) {\n return {};\n }\n}\n\nconst evaluated = getNodeJson('Code - Evaluar refresco');\nconst converted = getNodeJson('Code - Preparar ID convertido');\n\n// Si venimos de la rama PPTX convertida, usamos el JSON convertido.\n// Si venimos de Google Slides nativo, usamos el JSON evaluado.\nconst original = Object.keys(converted).length ? converted : evaluated;\n\nconst incoming = $json || {};\n\nfunction clean(value) {\n return String(value ?? '').trim().replace(/\\s+/g, ' ');\n}\n\nlet exportedText = '';\n\nif (typeof incoming === 'string') {\n exportedText = incoming;\n} else {\n exportedText =\n incoming.body ||\n incoming.data ||\n incoming.text ||\n incoming.content ||\n incoming.response ||\n '';\n}\n\nexportedText = clean(exportedText);\n\nconst hasContent = exportedText.length >= 80;\n\nreturn [\n {\n json: {\n ...original,\n contenido_presentacion: exportedText.slice(0, 18000),\n contenido_chars: exportedText.length,\n contenido_valido: hasContent,\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3536,
-432
],
"id": "fadb71e0-541f-4cc0-8e03-be43fb3fd841",
"name": "Code - Preparar texto IA Refresco"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "c0faa484-9c5d-4af3-b725-bfbc67f7f89d",
"leftValue": "={{ $json.contenido_valido }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
3744,
-432
],
"id": "cade8a8b-995a-4951-9b76-7f695a10fe7d",
"name": "IF - Tiene contenido?"
},
{
"parameters": {
"text": "=Nombre de la propuesta: {{$json.NOMBRE}}\nCliente: {{$json.CLIENTE}}\nMarca: {{$json.MARCA}}\nPaís: {{$json.PAIS}}\nLink: {{$json[\"Enlace a la propuesta\"]}}\n\nContenido exportado de la presentación:\n{{$json.contenido_presentacion}}",
"attributes": {
"attributes": [
{
"name": "nombre_propuesta",
"description": "Nombre limpio o comercial de la propuesta."
},
{
"name": "tipo_accion",
"description": "Tipo de acción o formato de propuesta. Ejemplos: campaña, evento, mueble, exhibidor, diseño gráfico, activación PDV, material POP, uniforme, promoción, stand. Si no se puede determinar, usar PENDIENTE."
},
{
"name": "canal",
"description": "Canal o lugar donde aplica la propuesta. Ejemplos: moderno, tradicional, online, PDV, no aplica. Si no se puede determinar, usar PENDIENTE."
},
{
"name": "tags",
"description": "Palabras clave separadas por comas para facilitar búsqueda. Ejemplos: campaña, corporativo, exhibidor, punto de venta, juegos, uniforme, material POP."
},
{
"name": "descripcion",
"description": "Párrafo profesional de 35 a 70 palabras. Debe iniciar preferiblemente con “La presentación muestra...”, “La pieza muestra...” o “La propuesta corresponde a...”. Debe mencionar cliente/marca, tipo de acción, concepto o tema principal y objetivo. No inventar detalles."
},
{
"name": "tactica_promocional",
"description": "Táctica promocional principal. Ejemplos: DEGUSTACIÓN, RULETA, SAMPLING - MUESTREO, PLINKO, WHATSAPP, LANDING PAGE, POP, PHOTOBOOTH, CANJE, SORTEO, PREMIOS INSTANTÁNEOS, JUEGO DIGITAL, TRIVIA, EXHIBICIÓN, IMPULSO o PENDIENTE."
}
]
},
"options": {
"systemPromptTemplate": "Analiza el contenido real exportado de una presentación de Google Slides para actualizar el banco de propuestas de Fulgencio Fumado.\n\nIMPORTANTE:\n- Usa el contenido de la presentación como fuente principal.\n- No cambies el país, cliente ni marca recibidos desde Tablero CDC.\n- No determines APROBADA. Ese campo debe quedar como PENDIENTE DE APROBACION.\n- No determines AMBIENTE DE COMPRA (RE). Ese campo debe quedar como PENDIENTE.\n- Si el contenido es insuficiente, usa PENDIENTE.\n- No inventes detalles.\n- Devuelve:\n nombre_propuesta\n tipo_accion\n canal\n tags\n descripcion\n tactica_promocional"
}
},
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"typeVersion": 1.2,
"position": [
4352,
-496
],
"id": "08c0d48c-400f-454a-832d-9e08d4724fbd",
"name": "Gemini - Reanalizar contenido Slides"
},
{
"parameters": {
"modelName": "models/gemini-3.1-pro-preview",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1.1,
"position": [
4352,
-288
],
"id": "5875c1c9-5aa3-4f60-a3ee-f94e0293ec04",
"name": "Google Gemini Chat Model",
"credentials": {
"googlePalmApi": {
"id": "jvsXYwL6IOoY2DBU",
"name": "Isaac - Gemini Api Pago"
}
}
},
{
"parameters": {
"jsCode": "const original = $('Code - Preparar texto IA Refresco').item.json || {};\nconst incoming = $input.first().json || {};\nconst ai = incoming.output || incoming;\n\nfunction clean(value) {\n if (Array.isArray(value)) return value.map(clean).filter(Boolean).join(', ');\n return String(value ?? '').trim().replace(/\\s+/g, ' ');\n}\n\nfunction removeAccents(value) {\n return clean(value)\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nfunction upper(value) {\n return removeAccents(value).toUpperCase();\n}\n\nfunction splitMulti(value) {\n return clean(value)\n .split(',')\n .map(v => upper(v))\n .map(v => v.trim())\n .filter(Boolean);\n}\n\nfunction unique(values) {\n return [...new Set(values.filter(Boolean))];\n}\n\nfunction joinMulti(values) {\n return unique(values).join(', ');\n}\n\nfunction normalizeTipoAccion(value) {\n const v = upper(value);\n\n const synonyms = {\n 'ACTIVACION PDV': 'ACTIVACION EN PDV',\n 'ACTIVACION EN PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'ACTIVACION PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'PUNTO DE VENTA': 'ACTIVACION EN PDV',\n 'PDV': 'ACTIVACION EN PDV',\n 'DISEÑO GRAFICO': 'DISENO GRAFICO',\n 'DISEÑO GRÁFICO': 'DISENO GRAFICO',\n 'DISENO GRAFICO': 'DISENO GRAFICO',\n 'CAMPAÑA': 'CAMPAÑA',\n 'CAMPANA': 'CAMPAÑA',\n 'PROMOCION': 'PROMOCION',\n 'PROMOCIÓN': 'PROMOCION',\n 'MUEBLE': 'MUEBLE',\n 'EXHIBIDOR': 'MUEBLE',\n 'STAND': 'STAND',\n 'EVENTO': 'EVENTO',\n 'UNIFORME': 'UNIFORME',\n 'MATERIAL POP': 'DISENO GRAFICO',\n 'POP': 'DISENO GRAFICO',\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeCanal(value) {\n const v = upper(value);\n\n const synonyms = {\n 'PUNTO DE VENTA': 'PDV',\n 'PDV': 'PDV',\n 'CANAL MODERNO': 'MODERNO',\n 'MODERNO': 'MODERNO',\n 'CANAL TRADICIONAL': 'TRADICIONAL',\n 'TRADICIONAL': 'TRADICIONAL',\n 'DIGITAL': 'ONLINE',\n 'ONLINE': 'ONLINE',\n 'NO APLICA': 'NO APLICA',\n 'N/A': 'NO APLICA',\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeTactica(value) {\n const v = upper(value);\n\n const synonyms = {\n 'SAMPLING': 'SAMPLING - MUESTREO',\n 'MUESTREO': 'SAMPLING - MUESTREO',\n 'DEGUSTACION': 'DEGUSTACIÓN',\n 'DEGUSTACIÓN': 'DEGUSTACIÓN',\n 'RULETA DIGITAL': 'RULETA',\n 'RULETA': 'RULETA',\n 'PLINKO': 'PLINKO',\n 'WHATSAPP': 'WHATSAPP',\n 'LANDING': 'LANDING PAGE',\n 'LANDING PAGE': 'LANDING PAGE',\n 'MATERIAL POP': 'POP',\n 'POP': 'POP',\n 'JUEGO DIGITAL': 'JUEGO DIGITAL',\n 'JUEGOS DIGITALES': 'JUEGO DIGITAL',\n 'EXHIBICION': 'EXHIBICIÓN',\n 'EXHIBICIÓN': 'EXHIBICIÓN',\n 'IMPULSO': 'IMPULSO',\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeEtiqueta(value) {\n const v = upper(value);\n\n const synonyms = {\n 'ACTIVACION': 'ACTIVACION',\n 'ACTIVACIÓN': 'ACTIVACION',\n 'EXHIBICION': 'EXHIBICION',\n 'EXHIBICIÓN': 'EXHIBICION',\n 'EXHIBIDOR': 'EXHIBIDOR',\n 'DEGUSTACION': 'DEGUSTACION',\n 'DEGUSTACIÓN': 'DEGUSTACION',\n 'PROMOCION': 'PROMOCIÓN',\n 'PROMOCIÓN': 'PROMOCIÓN',\n 'DISENO': 'DISEÑO',\n 'DISEÑO': 'DISEÑO',\n 'CAMPANA': 'CAMPAÑA',\n 'CAMPAÑA': 'CAMPAÑA',\n 'JUEGO DIGITAL': 'JUEGOS DIGITALES',\n 'JUEGOS DIGITALES': 'JUEGOS DIGITALES',\n 'PUNTO DE VENTA': 'PUNTO DE VENTA',\n 'PDV': 'PUNTO DE VENTA',\n 'F1': 'JUEGOS',\n 'SIMULADOR': 'JUEGOS DIGITALES',\n 'EXPERIENCIA': 'ACTIVIDADES',\n 'CORPORATIVO': 'CORPORATIVO',\n 'UNIFORME': 'UNIFORMES',\n 'UNIFORMES': 'UNIFORMES',\n 'POP': 'PUNTO DE VENTA',\n 'MATERIAL POP': 'PUNTO DE VENTA',\n };\n\n return synonyms[v] || v;\n}\n\nfunction normalizeOne(value, allowedList, fallback = 'PENDIENTE', normalizer = upper) {\n const raw = clean(value);\n if (!raw) return fallback;\n\n const mapped = normalizer(raw);\n return allowedList.includes(mapped) ? mapped : fallback;\n}\n\nfunction normalizeMulti(value, allowedList, fallback = 'PENDIENTE', unknownValue = 'OTRO', normalizer = upper) {\n const parts = splitMulti(value);\n if (!parts.length) return fallback;\n\n const result = [];\n\n for (const part of parts) {\n const mapped = normalizer(part);\n const expanded = String(mapped)\n .split(',')\n .map(v => v.trim())\n .filter(Boolean);\n\n for (const val of expanded) {\n if (allowedList.includes(val)) {\n result.push(val);\n } else {\n result.push(unknownValue);\n }\n }\n }\n\n const finalValues = unique(result);\n if (!finalValues.length) return fallback;\n\n return joinMulti(finalValues);\n}\n\nconst ALLOWED_TIPO_ACCION = [\n 'ACTIVACION EN PDV',\n 'IMPULSO DE VENTA',\n 'ACTIVACION FUERA DE PDV',\n 'EVENTO',\n 'KICK OFF',\n 'FERIA',\n 'STAND',\n 'MUEBLE',\n 'DISENO GRAFICO',\n 'DIGITAL',\n 'PUBLICIDAD',\n 'TUTORIAL',\n 'INFORMATIVA',\n 'LOGOTIPO',\n 'PROMOCION',\n 'UNIFORME',\n 'ACTIVACIÓN',\n 'INAUGURACIÓN',\n 'PARQUE',\n 'TROFEO',\n 'ANAQUEL',\n 'FOOD TRUCK',\n 'EMPAQUES',\n 'CAMPAÑA',\n 'QUICK COUNTER',\n 'DISEÑO',\n 'PPT',\n 'PENDIENTE',\n 'OTRO',\n];\n\nconst ALLOWED_CANAL = [\n 'MODERNO',\n 'TRADICIONAL',\n 'ONLINE',\n 'NO APLICA',\n 'PDV',\n 'PENDIENTE',\n];\n\nconst ALLOWED_TACTICA = [\n 'DEGUSTACIÓN',\n 'RULETA',\n 'SAMPLING - MUESTREO',\n 'PLINKO',\n 'WHATSAPP',\n 'LANDING PAGE',\n 'POP',\n 'PHOTOBOOTH',\n 'CANJE',\n 'SORTEO',\n 'PREMIOS INSTANTÁNEOS',\n 'JUEGO DIGITAL',\n 'TRIVIA',\n 'EXHIBICIÓN',\n 'IMPULSO',\n 'PENDIENTE',\n 'OTRO',\n];\n\nconst ALLOWED_ETIQUETAS = [\n 'ACTIVACION',\n 'ACTIVIDADES',\n 'BANDEJA',\n 'BTS',\n 'CORPORATIVO',\n 'DEGUSTACION',\n 'EXHIBICION',\n 'EXHIBIDOR',\n 'FUERZA DE VENTAS',\n 'JUEGOS',\n 'PREMIOS',\n 'PUNTO DE VENTA',\n 'REFERENCIAS',\n 'REUNIÓN',\n 'RULETA',\n 'SAMPLING',\n 'HALLOWEEN',\n 'LUCES NEON',\n 'INFORMATIVA',\n 'CARRITO DEGUSTACIÓN',\n 'RED SOCIAL/INFLUENCER',\n 'UNIFORMES',\n 'REGALIAS',\n 'STAND',\n 'CAPACITACIÓN',\n 'NAVIDAD',\n 'DÍA DE LAS MADRES',\n 'INDEPENDENCIA',\n 'LOGOTIPO',\n 'QUICK COUNTER',\n 'PPT',\n 'TEMPLATES',\n 'DISEÑO',\n 'PROMOCIÓN',\n 'MODERNO',\n 'MAYORISTA',\n 'PULPERÍAS',\n 'JUEGOS DIGITALES',\n 'WHATSAPP',\n 'CAMPAÑA',\n 'CONCURSO',\n 'REGALOS CORPORATIVOS',\n 'PENDIENTE',\n 'OTRO',\n];\n\nlet nombrePropuesta = clean(\n ai.nombre_propuesta ||\n original.NOMBRE ||\n original.nombre_limpio ||\n original.drive_name ||\n ''\n);\n\nif (!nombrePropuesta) {\n nombrePropuesta = 'PENDIENTE';\n}\n\nlet tipoAccion = normalizeMulti(\n ai.tipo_accion,\n ALLOWED_TIPO_ACCION,\n 'PENDIENTE',\n 'OTRO',\n normalizeTipoAccion\n);\n\nconst canal = normalizeOne(\n ai.canal,\n ALLOWED_CANAL,\n 'PENDIENTE',\n normalizeCanal\n);\n\nconst tacticaPromocional = normalizeMulti(\n ai.tactica_promocional,\n ALLOWED_TACTICA,\n 'PENDIENTE',\n 'OTRO',\n normalizeTactica\n);\n\nlet etiquetas = normalizeMulti(\n ai.tags,\n ALLOWED_ETIQUETAS,\n 'PENDIENTE',\n 'OTRO',\n normalizeEtiqueta\n);\n\nif (etiquetas.includes('OTRO') && etiquetas !== 'OTRO') {\n etiquetas = etiquetas\n .split(',')\n .map(v => v.trim())\n .filter(v => v !== 'OTRO')\n .join(', ');\n}\n\nconst ambienteCompra = 'PENDIENTE';\nconst aprobada = 'PENDIENTE DE APROBACION';\n\nlet descripcion = clean(ai.descripcion);\n\nif (!descripcion) {\n descripcion = clean(\n original.Descripcion ||\n original.NOMBRE ||\n original.drive_name ||\n ''\n );\n}\n\nlet requiereRevision = false;\nconst revisionReasons = [];\n\nif (tipoAccion.includes('OTRO') || tipoAccion === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('tipo_accion_revisar');\n}\n\nif (canal === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('canal_revisar');\n}\n\nif (tacticaPromocional.includes('OTRO') || tacticaPromocional === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('tactica_revisar');\n}\n\nif (etiquetas.includes('OTRO') || etiquetas === 'PENDIENTE') {\n requiereRevision = true;\n revisionReasons.push('etiquetas_revisar');\n}\n\nreturn [\n {\n json: {\n NOMBRE: nombrePropuesta,\n 'TIPO DE ACCION': tipoAccion,\n CLIENTE: original.CLIENTE || '',\n MARCA: original.MARCA || '',\n PAIS: original.PAIS || '',\n CANAL: canal,\n 'AMBIENTE DE COMPRA (RE)': ambienteCompra,\n 'TÁCTICA PROMOCIONAL': tacticaPromocional,\n APROBADA: aprobada,\n ETIQUETAS: etiquetas,\n 'AÑO': original['AÑO'] || '',\n 'Enlace a la propuesta': original['Enlace a la propuesta'] || '',\n Descripcion: descripcion,\n\n file_id: original.file_id || original.file_id_refresco || original.drive_file_id_original || original.drive_file_id || '',\n nombre_archivo: original.nombre_archivo || original.NOMBRE || original.drive_name || '',\n mime_type: original.mime_type || original.drive_mime_type || '',\n fuente_pais: original.fuente_pais || 'Tablero CDC',\n confianza_pais: original.confianza_pais || '',\n requiere_revision: requiereRevision,\n motivos_revision: revisionReasons.join(', '),\n procesado_ia: true,\n ultima_actualizacion: new Date().toISOString(),\n 'Enlaces a propuestas ejecutadas': original['Enlaces a propuestas ejecutadas'] || '',\n\n tablero_sync_key: original.tablero_sync_key || '',\n tablero_project_id: original.tablero_project_id || '',\n tablero_origen: original.tablero_origen || 'Tablero CDC',\n\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'OK',\n refresh_attempts: original.refresh_attempts || 1,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4704,
-496
],
"id": "525e3816-a692-49e3-9885-20b8b4ddccbe",
"name": "Code - Normalizar respuesta refresco"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng",
"mode": "list",
"cachedResultName": "BANCO DE PROPUESTAS DE CDC PARA FULGENCIO FUMADO",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "propuestas",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1iy3lC3DS3ZfShb7d26iQOrPjx9s_3B-PC07XfYew7ng/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"tablero_sync_key": "={{ $json[\"tablero_sync_key\"]}}",
"NOMBRE": "={{ $json[\"NOMBRE\"]}}",
"TIPO DE ACCION": "={{ $json[\"TIPO DE ACCION\"]}}",
"CLIENTE": "={{ $json[\"CLIENTE\"]}}",
"MARCA": "={{ $json[\"MARCA\"]}}",
"PAIS": "={{ $json[\"PAIS\"]}}",
"CANAL": "={{ $json[\"CANAL\"]}}",
"AMBIENTE DE COMPRA (RE)": "={{ $json[\"AMBIENTE DE COMPRA (RE)\"]}}",
"TÁCTICA PROMOCIONAL": "={{ $json[\"TÁCTICA PROMOCIONAL\"]}}",
"APROBADA": "={{ $json[\"APROBADA\"]}}",
"ETIQUETAS": "={{ $json[\"ETIQUETAS\"]}}",
"AÑO": "={{ $json[\"AÑO\"]}}",
"Enlace a la propuesta": "={{ $json[\"Enlace a la propuesta\"]}}",
"Descripcion": "={{ $json[\"Descripcion\"]}}",
"file_id": "={{ $json[\"file_id\"]}}",
"nombre_archivo": "={{ $json[\"nombre_archivo\"]}}",
"mime_type": "={{ $json[\"mime_type\"]}}",
"fuente_pais": "={{ $json[\"fuente_pais\"]}}",
"confianza_pais": "={{ $json[\"confianza_pais\"]}}",
"requiere_revision": "={{ $json[\"requiere_revision\"] ? \"TRUE\" : \"FALSE\"}}",
"procesado_ia": "={{ $json[\"procesado_ia\"] }}",
"ultima_actualizacion": "={{ $json[\"ultima_actualizacion\"]}}",
"motivos_revision": "={{ $json[\"motivos_revision\"]}}",
"Enlaces a propuestas ejecutadas": "={{ $json[\"Enlaces a propuestas ejecutadas\"]}}",
"tablero_project_id": "={{ $json[\"tablero_project_id\"]}}",
"tablero_origen": "={{ $json[\"tablero_origen\"]}}",
"drive_modified_time": "={{ $json[\"drive_modified_time\"]}}",
"ultimo_refresco_ia": "={{ $json[\"ultimo_refresco_ia\"]}}",
"refresh_status": "={{ $json[\"refresh_status\"]}}",
"refresh_attempts": "={{ $json[\"refresh_attempts\"]}}",
"converted_google_slides_id": "={{ $json[\"converted_google_slides_id\"]}}",
"converted_google_slides_link": "={{ $json[\"converted_google_slides_link\"]}}"
},
"matchingColumns": [
"tablero_sync_key"
],
"schema": [
{
"id": "NOMBRE",
"displayName": "NOMBRE",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "TIPO DE ACCION",
"displayName": "TIPO DE ACCION",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "CLIENTE",
"displayName": "CLIENTE",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "MARCA",
"displayName": "MARCA",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "PAIS",
"displayName": "PAIS",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "CANAL",
"displayName": "CANAL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "AMBIENTE DE COMPRA (RE)",
"displayName": "AMBIENTE DE COMPRA (RE)",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "TÁCTICA PROMOCIONAL",
"displayName": "TÁCTICA PROMOCIONAL",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "APROBADA",
"displayName": "APROBADA",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ETIQUETAS",
"displayName": "ETIQUETAS",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "AÑO",
"displayName": "AÑO",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Enlace a la propuesta",
"displayName": "Enlace a la propuesta",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Descripcion",
"displayName": "Descripcion",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "file_id",
"displayName": "file_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "nombre_archivo",
"displayName": "nombre_archivo",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "mime_type",
"displayName": "mime_type",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "fuente_pais",
"displayName": "fuente_pais",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "confianza_pais",
"displayName": "confianza_pais",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "requiere_revision",
"displayName": "requiere_revision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "procesado_ia",
"displayName": "procesado_ia",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ultima_actualizacion",
"displayName": "ultima_actualizacion",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "motivos_revision",
"displayName": "motivos_revision",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "Enlaces a propuestas ejecutadas",
"displayName": "Enlaces a propuestas ejecutadas",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "tablero_sync_key",
"displayName": "tablero_sync_key",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "tablero_project_id",
"displayName": "tablero_project_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "tablero_origen",
"displayName": "tablero_origen",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "drive_modified_time",
"displayName": "drive_modified_time",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ultimo_refresco_ia",
"displayName": "ultimo_refresco_ia",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "refresh_status",
"displayName": "refresh_status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "refresh_attempts",
"displayName": "refresh_attempts",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "converted_google_slides_id",
"displayName": "converted_google_slides_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "converted_google_slides_link",
"displayName": "converted_google_slides_link",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
5472,
352
],
"id": "f6487012-88de-4be3-b62e-fa9bdfdda270",
"name": "Google Sheets - Upsert Banco Fulgencio",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "K0hDZh3a85MpOHCs",
"name": "Google Sheets account 2"
}
}
},
{
"parameters": {
"jsCode": "const original = $('Code - Preparar texto IA Refresco').item.json || {};\n\nreturn [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'SIN_CONTENIDO',\n refresh_attempts: original.refresh_attempts || 1,\n procesado_ia: false,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4128,
0
],
"id": "42c459da-dd49-4ed8-aa1d-ff1f39688acf",
"name": "Code - Marcar sin contenido"
},
{
"parameters": {
"jsCode": "function nowIso() {\n return new Date().toISOString();\n}\n\nfunction appendMotivo(existing, motivo) {\n const current = String(existing || \"\").trim();\n\n if (!current) return motivo;\n\n const parts = current\n .split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n\n if (!parts.includes(motivo)) {\n parts.push(motivo);\n }\n\n return parts.join(\", \");\n}\n\nfunction buildTableroSyncKey(data) {\n const tableroProjectId = String(data.tablero_project_id || \"\").trim();\n const fileId = String(data.file_id || \"\").trim();\n const existingKey = String(data.tablero_sync_key || \"\").trim();\n\n if (existingKey) return existingKey;\n if (tableroProjectId && fileId) return `${tableroProjectId}::${fileId}`;\n if (fileId) return `drive::${fileId}`;\n if (tableroProjectId) return `${tableroProjectId}::sin_file_id`;\n\n return \"\";\n}\n\nreturn items.map((item) => {\n const data = item.json;\n\n const fileId = String(data.file_id || \"\").trim();\n\n /**\n * Seguridad:\n * Si este nodo recibe por error una fila que SÍ tiene file_id,\n * no la marca como SIN_FILE_ID.\n */\n if (fileId) {\n return {\n json: {\n ...data,\n tablero_sync_key: buildTableroSyncKey(data),\n refresh_status: data.refresh_status || \"PENDIENTE\",\n ultima_actualizacion: data.ultima_actualizacion || nowIso(),\n },\n };\n }\n\n return {\n json: {\n ...data,\n file_id: \"\",\n tablero_sync_key: buildTableroSyncKey(data),\n refresh_status: \"SIN_FILE_ID\",\n refresh_attempts: Number(data.refresh_attempts || 0) + 1,\n requiere_revision: true,\n procesado_ia: false,\n ultima_actualizacion: nowIso(),\n ultimo_refresco_ia: data.ultimo_refresco_ia || \"\",\n motivos_revision: appendMotivo(data.motivos_revision, \"sin_file_id\"),\n },\n };\n});"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
736,
112
],
"id": "6287af34-de8e-49aa-b432-778c016c9f8d",
"name": "Code - Marcar sin file_id"
},
{
"parameters": {
"jsCode": "const original = $('IF - Tiene file_id?').item.json || {};\nconst errorData = $json || {};\n\nfunction stringifyError(value) {\n try {\n if (typeof value === 'string') return value;\n return JSON.stringify(value).slice(0, 500);\n } catch (error) {\n return String(value).slice(0, 500);\n }\n}\n\nreturn [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'ERROR_METADATA_DRIVE',\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n procesado_ia: false,\n motivos_revision: `error_metadata_drive: ${stringifyError(errorData)}`,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
688,
-96
],
"id": "389332ff-4d6b-4a5d-824d-28a1098030ef",
"name": "Code - Marcar error metadata Drive"
},
{
"parameters": {
"jsCode": "function getNodeJson(name) {\n try {\n return $(name).item.json || {};\n } catch (error) {\n return {};\n }\n}\n\nconst evaluated = getNodeJson('Code - Evaluar refresco');\nconst converted = getNodeJson('Code - Preparar ID convertido');\n\nconst original = Object.keys(converted).length ? converted : evaluated;\nconst errorData = $json || {};\n\nfunction stringifyError(value) {\n try {\n if (typeof value === 'string') return value;\n return JSON.stringify(value).slice(0, 500);\n } catch (error) {\n return String(value).slice(0, 500);\n }\n}\n\nreturn [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'ERROR_EXPORT_SLIDES',\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n procesado_ia: false,\n motivos_revision: `error_export_slides: ${stringifyError(errorData)}`,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3472,
-240
],
"id": "251b1833-27c9-4a63-9a97-6ad600ea5283",
"name": "Code - Marcar error export Slides"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "1dd1c68d-37b9-4bde-8ff6-7556f7fe979b",
"leftValue": "={{ $json.drive_mime_type }}",
"rightValue": "application/vnd.google-apps.presentation",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1408,
-304
],
"id": "5de289da-1c11-422e-be69-73d6d8c67db8",
"name": "IF - Es Google Slides nativo?"
},
{
"parameters": {
"method": "POST",
"url": "https://script.google.com/macros/s/AKfycbwGdQZT_tRCWmUsIw9LzwzWClHx12CKVI23dbE-gUwVZ-dorjJJuQLWHxUACnH2vlK-Jw/exec",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "token",
"value": "tablero-cdc-fulgencio-convert-2026-isaac"
},
{
"name": "file_id",
"value": "={{ $json.file_id_refresco || $json.drive_file_id }}"
},
{
"name": "previous_converted_id",
"value": "={{ $json.converted_google_slides_id }}"
},
{
"name": "folder_id",
"value": "1wUTt7ttK3EafPi-L8bIj-J_ZdN9EPg4r"
},
{
"name": "replace_existing",
"value": "true"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1936,
-192
],
"id": "150797a7-4db0-4aff-88ed-d18eb0afc202",
"name": "HTTP - Convertir PPTX con Apps Script"
},
{
"parameters": {
"jsCode": "const original = $('IF - Es Google Slides nativo?').item.json || {};\nconst response = $json || {};\n\nfunction clean(value) {\n return String(value ?? '').trim();\n}\n\nif (!response.ok || !response.converted_google_slides_id) {\n return [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'ERROR_CONVERT_PPTX',\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n procesado_ia: false,\n motivos_revision: `error_convert_pptx: ${clean(response.error || JSON.stringify(response)).slice(0, 300)}`,\n },\n },\n ];\n}\n\nreturn [\n {\n json: {\n ...original,\n\n drive_file_id_original: original.drive_file_id || original.file_id_refresco || '',\n drive_file_id: response.converted_google_slides_id,\n drive_mime_type: 'application/vnd.google-apps.presentation',\n\n converted_google_slides_id: response.converted_google_slides_id,\n converted_google_slides_link: response.converted_google_slides_link,\n\n refresh_status: 'CONVERTIDO_PPTX',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2112,
-192
],
"id": "fcc131cb-6181-41fc-9231-129dfc7d6095",
"name": "Code - Preparar ID convertido"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "f05881a1-7e59-447e-830d-a7bb81d74c7e",
"leftValue": "={{ $json.refresh_status }}",
"rightValue": "ERROR_CONVERT_PPTX",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
2320,
-192
],
"id": "dd855b50-ab6f-449d-8c5c-6c5b5a80f339",
"name": "IF - Conversion OK?"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d8425980-f331-4f23-9cfb-118077f4437e",
"leftValue": "={{ $json.drive_size_mb }}",
"rightValue": 100,
"operator": {
"type": "number",
"operation": "lte"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1664,
-144
],
"id": "ea25df58-fe23-4f28-83d0-2ce8851aa353",
"name": "IF - PPTX pesa <= 100MB?"
},
{
"parameters": {
"jsCode": "const original = $json || {};\n\nreturn [\n {\n json: {\n ...original,\n drive_modified_time: original.drive_modified_time_actual || original.drive_modified_time || '',\n ultimo_refresco_ia: new Date().toISOString(),\n refresh_status: 'PPTX_DEMASIADO_PESADO',\n refresh_attempts: Number(original.refresh_attempts_actual || original.refresh_attempts || 0) + 1,\n procesado_ia: false,\n motivos_revision: `pptx_demasiado_pesado_para_conversion_google_slides: ${original.drive_size_mb || 0} MB. Google Slides solo permite convertir presentaciones hasta 100 MB.`,\n\n converted_google_slides_id: original.converted_google_slides_id || '',\n converted_google_slides_link: original.converted_google_slides_link || '',\n },\n },\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2320,
96
],
"id": "2390d0c3-bf35-4480-8987-6ee1509d211e",
"name": "Code - Marcar PPTX demasiado pesado"
},
{
"parameters": {
"content": "## 📅 ENTRADA DIARIA — BANCO FULGENCIO\n\nEste flujo corre automáticamente todos los días a las 23:00.\n\nLee el Banco de Propuestas de Fulgencio y procesa solo filas provenientes de Tablero CDC.\n\nCriterios mínimos:\n- tablero_origen = Tablero CDC\n- tablero_sync_key presente\n- tablero_project_id presente\n- Enlace a la propuesta presente\n\nObjetivo:\nmantener actualizado el banco que consulta FulgencioChat.",
"height": 544,
"width": 976,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-864,
-384
],
"id": "9adcd60e-20bc-4e8a-a822-daaadb405c8e",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## 🔎 EVALUACIÓN DE CAMBIOS EN DRIVE\n\nPor cada fila válida, el flujo obtiene metadata del archivo en Google Drive.\n\nCompara:\n- drive_modified_time actual\n- ultimo_refresco_ia guardado\n- drive_modified_time previo\n\nSolo refresca si:\n- nunca se refrescó\n- el archivo cambió después del último refresco\n- cambió la fecha de modificación en Drive\n\nSi no hay cambios, pasa a la siguiente fila.",
"height": 896,
"width": 704,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
320,
-640
],
"id": "fa12fd6a-ee6b-47ee-af68-5fb1f186bd9a",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "## 📄 EXTRACCIÓN DE CONTENIDO\n\nSi el archivo es Google Slides nativo:\n- se exporta directamente a texto.\n\nSi el archivo es PPTX:\n- solo se convierte si pesa 100 MB o menos.\n- se convierte con Apps Script a Google Slides.\n- luego se exporta a texto.\n\nErrores controlados:\n- SIN_FILE_ID\n- ERROR_METADATA_DRIVE\n- ERROR_EXPORT_SLIDES\n- ERROR_CONVERT_PPTX\n- PPTX_DEMASIADO_PESADO\n- SIN_CONTENIDO\n\nUna fila con error se marca en el banco y el flujo continúa.",
"height": 1152,
"width": 1456,
"color": 2
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1344,
-832
],
"id": "75a29688-9331-4fdb-928f-600f4bd21136",
"name": "Sticky Note2"
},
{
"parameters": {
"content": "## 🤖 REANÁLISIS IA Y UPSERT AL BANCO\n\nGemini analiza el texto exportado de la presentación.\n\nPuede actualizar:\n- nombre_propuesta\n- tipo_accion\n- canal\n- tags\n- descripcion\n- tactica_promocional\n\nNo debe cambiar:\n- país\n- cliente\n- marca\n- enlace\n- identificadores de Tablero CDC\n\nReglas fijas:\n- APROBADA = PENDIENTE DE APROBACION\n- AMBIENTE DE COMPRA (RE) = PENDIENTE\n\nEl banco se actualiza usando:\ntablero_sync_key\n\nTambién guarda:\n- refresh_status\n- refresh_attempts\n- ultimo_refresco_ia\n- drive_modified_time\n- requiere_revision\n- motivos_revision",
"height": 1760,
"width": 2576,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
3152,
-1136
],
"id": "fc72547c-317e-4ac7-9428-825e8504f143",
"name": "Sticky Note3"
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "Sheets - Leer Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Sheets - Leer Banco Fulgencio": {
"main": [
[
{
"node": "Code - Filtrar filas Tablero CDC",
"type": "main",
"index": 0
}
]
]
},
"Code - Filtrar filas Tablero CDC": {
"main": [
[
{
"node": "Loop - Filas Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Loop - Filas Banco Fulgencio": {
"main": [
[],
[
{
"node": "IF - Tiene file_id?",
"type": "main",
"index": 0
}
]
]
},
"IF - Tiene file_id?": {
"main": [
[
{
"node": "HTTP - Obtener metadata Drive",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar sin file_id",
"type": "main",
"index": 0
}
]
]
},
"HTTP - Obtener metadata Drive": {
"main": [
[
{
"node": "Code - Evaluar refresco",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar error metadata Drive",
"type": "main",
"index": 0
}
]
]
},
"Code - Evaluar refresco": {
"main": [
[
{
"node": "IF - Debe refrescar?",
"type": "main",
"index": 0
}
]
]
},
"IF - Debe refrescar?": {
"main": [
[
{
"node": "IF - Es Google Slides nativo?",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop - Filas Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"HTTP - Exportar Slides TXT": {
"main": [
[
{
"node": "Code - Preparar texto IA Refresco",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar error export Slides",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar texto IA Refresco": {
"main": [
[
{
"node": "IF - Tiene contenido?",
"type": "main",
"index": 0
}
]
]
},
"IF - Tiene contenido?": {
"main": [
[
{
"node": "Gemini - Reanalizar contenido Slides",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar sin contenido",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "Gemini - Reanalizar contenido Slides",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Gemini - Reanalizar contenido Slides": {
"main": [
[
{
"node": "Code - Normalizar respuesta refresco",
"type": "main",
"index": 0
}
]
]
},
"Code - Normalizar respuesta refresco": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar sin contenido": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets - Upsert Banco Fulgencio": {
"main": [
[
{
"node": "Loop - Filas Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar sin file_id": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar error metadata Drive": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar error export Slides": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"IF - Es Google Slides nativo?": {
"main": [
[
{
"node": "HTTP - Exportar Slides TXT",
"type": "main",
"index": 0
}
],
[
{
"node": "IF - PPTX pesa <= 100MB?",
"type": "main",
"index": 0
}
]
]
},
"HTTP - Convertir PPTX con Apps Script": {
"main": [
[
{
"node": "Code - Preparar ID convertido",
"type": "main",
"index": 0
}
]
]
},
"Code - Preparar ID convertido": {
"main": [
[
{
"node": "IF - Conversion OK?",
"type": "main",
"index": 0
}
]
]
},
"IF - Conversion OK?": {
"main": [
[
{
"node": "HTTP - Exportar Slides TXT",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
},
"IF - PPTX pesa <= 100MB?": {
"main": [
[
{
"node": "HTTP - Convertir PPTX con Apps Script",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Marcar PPTX demasiado pesado",
"type": "main",
"index": 0
}
]
]
},
"Code - Marcar PPTX demasiado pesado": {
"main": [
[
{
"node": "Google Sheets - Upsert Banco Fulgencio",
"type": "main",
"index": 0
}
]
]
}
},
"authors": "Isaac Aracena",
"name": "Version 313b1289",
"description": "",
"autosaved": false,
"workflowPublishHistory": [
{
"createdAt": "2026-06-25T03:27:50.388Z",
"id": 1859,
"workflowId": "g2JtKXNz5GDbkmQH",
"versionId": "313b1289-adef-4382-b285-8866e343bef5",
"event": "activated",
"userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
}
]
}
}