From dc4aa782c2b6bac77fcad5edf70059fb11cfa413 Mon Sep 17 00:00:00 2001 From: Isaac_Aracena Date: Sun, 2 Aug 2026 04:00:48 +0000 Subject: [PATCH] Update backup: Tablero CDC - Refrescar Banco Fulgencio Diario --- ...-cdc-refrescar-banco-fulgencio-diario.json | 260 ++++++++++++++---- 1 file changed, 201 insertions(+), 59 deletions(-) diff --git a/tablero-cdc-refrescar-banco-fulgencio-diario.json b/tablero-cdc-refrescar-banco-fulgencio-diario.json index 172bd06..e3e790c 100644 --- a/tablero-cdc-refrescar-banco-fulgencio-diario.json +++ b/tablero-cdc-refrescar-banco-fulgencio-diario.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-07-25T17:10:33.663Z", + "updatedAt": "2026-07-27T14:23:33.135Z", "createdAt": "2026-06-16T17:22:11.395Z", "id": "g2JtKXNz5GDbkmQH", "name": "Tablero CDC - Refrescar Banco Fulgencio Diario", @@ -68,7 +68,7 @@ }, { "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;" + "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\n if (match?.[1]) {\n return match[1];\n }\n }\n\n return '';\n}\n\nconst rows = $input.all();\nconst result = [];\n\nfor (let index = 0; index < rows.length; index++) {\n const item = rows[index];\n const row = item.json || {};\n\n const origen = clean(row.tablero_origen).toLowerCase();\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\n const existingFileId = clean(row.file_id);\n const extractedFileId = extractGoogleFileId(proposalUrl);\n const fileId = existingFileId || extractedFileId;\n\n // Solo procesar filas válidas provenientes de Tablero CDC.\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\n // Dejamos ambos campos consistentes.\n file_id: fileId,\n file_id_refresco: fileId,\n\n proposal_url_refresco: proposalUrl,\n refresh_attempts_actual: Number(row.refresh_attempts || 0),\n },\n\n // Conserva la vinculación entre el elemento original y el resultado.\n pairedItem: {\n item: index,\n },\n });\n}\n\nif (result.length === 0) {\n throw new Error(\n 'No se encontraron filas válidas de Tablero CDC con tablero_sync_key, tablero_project_id y enlace.'\n );\n}\n\nreturn result;" }, "type": "n8n-nodes-base.code", "typeVersion": 2, @@ -104,7 +104,7 @@ "conditions": [ { "id": "8c79f0ce-b901-43c8-b863-ad490d267c37", - "leftValue": "={{ $json[\"file_id\"] }}", + "leftValue": "={{ String($json.file_id_refresco || $json.file_id || '').trim() }}", "rightValue": "", "operator": { "type": "string", @@ -152,7 +152,7 @@ }, { "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];" + "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\n if (!date || Number.isNaN(date.getTime())) {\n return null;\n }\n\n return date;\n}\n\nconst driveModifiedTime = clean(\n metadata.modifiedTime || metadata.modified_time\n);\n\nconst lastRefresh = clean(original.ultimo_refresco_ia);\nconst existingDriveModified = clean(original.drive_modified_time);\nconst previousStatus = clean(original.refresh_status).toUpperCase();\n\nconst driveDate = toDate(driveModifiedTime);\nconst lastRefreshDate = toDate(lastRefresh);\nconst existingDriveDate = toDate(existingDriveModified);\n\nconst driveSizeBytes = Number(metadata.size || 0);\n\nconst driveSizeMb = driveSizeBytes\n ? Number((driveSizeBytes / 1024 / 1024).toFixed(2))\n : 0;\n\n// Estados que deben volver a intentarse aunque una ejecución anterior\n// haya escrito incorrectamente ultimo_refresco_ia.\nconst retryableStatuses = new Set([\n 'PENDIENTE',\n 'SIN_FILE_ID',\n 'ERROR_METADATA_DRIVE',\n 'ERROR_EXPORT_SLIDES',\n 'ERROR_CONVERT_PPTX',\n 'SIN_CONTENIDO',\n 'PPTX_DEMASIADO_PESADO',\n]);\n\nlet shouldRefresh = false;\nlet reason = '';\n\nif (retryableStatuses.has(previousStatus)) {\n shouldRefresh = true;\n reason = `reintento_estado_${previousStatus.toLowerCase()}`;\n} else if (!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\n drive_file_id:\n metadata.id ||\n original.file_id_refresco ||\n original.file_id ||\n '',\n\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\n should_refresh: shouldRefresh,\n refresh_reason: reason,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, @@ -371,7 +371,7 @@ "columns": { "mappingMode": "defineBelow", "value": { - "tablero_sync_key": "={{ $json[\"tablero_sync_key\"]}}", + "tablero_sync_key": "={{ String($json.tablero_sync_key || '').trim() }}", "NOMBRE": "={{ $json[\"NOMBRE\"]}}", "TIPO DE ACCION": "={{ $json[\"TIPO DE ACCION\"]}}", "CLIENTE": "={{ $json[\"CLIENTE\"]}}", @@ -714,6 +714,8 @@ ], "id": "f6487012-88de-4be3-b62e-fa9bdfdda270", "name": "Google Sheets - Upsert Banco Fulgencio", + "retryOnFail": true, + "maxTries": 5, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", @@ -723,7 +725,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -749,7 +751,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -762,7 +764,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -849,7 +851,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -928,7 +930,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -1002,6 +1004,54 @@ ], "id": "fc72547c-317e-4ac7-9428-825e8504f143", "name": "Sticky Note3" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "cb44c645-00ba-43c1-a8f0-2fe556ebc382", + "leftValue": "={{ String($json.tablero_sync_key || '').trim() }}", + "rightValue": "", + "operator": { + "type": "string", + "operation": "notEmpty", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 5024, + 112 + ], + "id": "1b56b546-c4af-412d-bef8-8105eb830ae1", + "name": "IF - Tiene sync key antes de Upsert?" + }, + { + "parameters": { + "amount": 4 + }, + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + 2768, + 432 + ], + "id": "be090d5c-6fb6-4087-b25b-83fb8e3a3392", + "name": "Wait - Respetar cuota Google Sheets", + "webhookId": "8b997d7c-097c-4c8a-8793-6bce3db5e142" } ], "connections": { @@ -1188,7 +1238,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -1199,7 +1249,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -1210,7 +1260,7 @@ "main": [ [ { - "node": "Loop - Filas Banco Fulgencio", + "node": "Wait - Respetar cuota Google Sheets", "type": "main", "index": 0 } @@ -1221,7 +1271,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -1232,7 +1282,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -1243,7 +1293,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -1301,7 +1351,7 @@ ], [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -1327,6 +1377,17 @@ ] }, "Code - Marcar PPTX demasiado pesado": { + "main": [ + [ + { + "node": "IF - Tiene sync key antes de Upsert?", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF - Tiene sync key antes de Upsert?": { "main": [ [ { @@ -1334,6 +1395,24 @@ "type": "main", "index": 0 } + ], + [ + { + "node": "Loop - Filas Banco Fulgencio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Wait - Respetar cuota Google Sheets": { + "main": [ + [ + { + "node": "Loop - Filas Banco Fulgencio", + "type": "main", + "index": 0 + } ] ] } @@ -1355,9 +1434,9 @@ "meta": { "templateCredsSetupCompleted": true }, - "versionId": "a92fd2c1-b300-4def-8cd7-fdb0bde1bdfe", - "activeVersionId": "a92fd2c1-b300-4def-8cd7-fdb0bde1bdfe", - "versionCounter": 435, + "versionId": "db8caedf-99e0-4c41-b43a-4b115621fb4a", + "activeVersionId": "db8caedf-99e0-4c41-b43a-4b115621fb4a", + "versionCounter": 499, "triggerCount": 1, "shared": [ { @@ -1380,9 +1459,9 @@ ], "tags": [], "activeVersion": { - "updatedAt": "2026-07-22T14:43:58.000Z", - "createdAt": "2026-07-22T14:43:55.809Z", - "versionId": "a92fd2c1-b300-4def-8cd7-fdb0bde1bdfe", + "updatedAt": "2026-07-27T14:23:46.000Z", + "createdAt": "2026-07-27T14:23:33.139Z", + "versionId": "db8caedf-99e0-4c41-b43a-4b115621fb4a", "workflowId": "g2JtKXNz5GDbkmQH", "nodes": [ { @@ -1446,7 +1525,7 @@ }, { "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;" + "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\n if (match?.[1]) {\n return match[1];\n }\n }\n\n return '';\n}\n\nconst rows = $input.all();\nconst result = [];\n\nfor (let index = 0; index < rows.length; index++) {\n const item = rows[index];\n const row = item.json || {};\n\n const origen = clean(row.tablero_origen).toLowerCase();\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\n const existingFileId = clean(row.file_id);\n const extractedFileId = extractGoogleFileId(proposalUrl);\n const fileId = existingFileId || extractedFileId;\n\n // Solo procesar filas válidas provenientes de Tablero CDC.\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\n // Dejamos ambos campos consistentes.\n file_id: fileId,\n file_id_refresco: fileId,\n\n proposal_url_refresco: proposalUrl,\n refresh_attempts_actual: Number(row.refresh_attempts || 0),\n },\n\n // Conserva la vinculación entre el elemento original y el resultado.\n pairedItem: {\n item: index,\n },\n });\n}\n\nif (result.length === 0) {\n throw new Error(\n 'No se encontraron filas válidas de Tablero CDC con tablero_sync_key, tablero_project_id y enlace.'\n );\n}\n\nreturn result;" }, "type": "n8n-nodes-base.code", "typeVersion": 2, @@ -1482,7 +1561,7 @@ "conditions": [ { "id": "8c79f0ce-b901-43c8-b863-ad490d267c37", - "leftValue": "={{ $json[\"file_id\"] }}", + "leftValue": "={{ String($json.file_id_refresco || $json.file_id || '').trim() }}", "rightValue": "", "operator": { "type": "string", @@ -1530,7 +1609,7 @@ }, { "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];" + "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\n if (!date || Number.isNaN(date.getTime())) {\n return null;\n }\n\n return date;\n}\n\nconst driveModifiedTime = clean(\n metadata.modifiedTime || metadata.modified_time\n);\n\nconst lastRefresh = clean(original.ultimo_refresco_ia);\nconst existingDriveModified = clean(original.drive_modified_time);\nconst previousStatus = clean(original.refresh_status).toUpperCase();\n\nconst driveDate = toDate(driveModifiedTime);\nconst lastRefreshDate = toDate(lastRefresh);\nconst existingDriveDate = toDate(existingDriveModified);\n\nconst driveSizeBytes = Number(metadata.size || 0);\n\nconst driveSizeMb = driveSizeBytes\n ? Number((driveSizeBytes / 1024 / 1024).toFixed(2))\n : 0;\n\n// Estados que deben volver a intentarse aunque una ejecución anterior\n// haya escrito incorrectamente ultimo_refresco_ia.\nconst retryableStatuses = new Set([\n 'PENDIENTE',\n 'SIN_FILE_ID',\n 'ERROR_METADATA_DRIVE',\n 'ERROR_EXPORT_SLIDES',\n 'ERROR_CONVERT_PPTX',\n 'SIN_CONTENIDO',\n 'PPTX_DEMASIADO_PESADO',\n]);\n\nlet shouldRefresh = false;\nlet reason = '';\n\nif (retryableStatuses.has(previousStatus)) {\n shouldRefresh = true;\n reason = `reintento_estado_${previousStatus.toLowerCase()}`;\n} else if (!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\n drive_file_id:\n metadata.id ||\n original.file_id_refresco ||\n original.file_id ||\n '',\n\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\n should_refresh: shouldRefresh,\n refresh_reason: reason,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, @@ -1749,7 +1828,7 @@ "columns": { "mappingMode": "defineBelow", "value": { - "tablero_sync_key": "={{ $json[\"tablero_sync_key\"]}}", + "tablero_sync_key": "={{ String($json.tablero_sync_key || '').trim() }}", "NOMBRE": "={{ $json[\"NOMBRE\"]}}", "TIPO DE ACCION": "={{ $json[\"TIPO DE ACCION\"]}}", "CLIENTE": "={{ $json[\"CLIENTE\"]}}", @@ -2092,6 +2171,8 @@ ], "id": "f6487012-88de-4be3-b62e-fa9bdfdda270", "name": "Google Sheets - Upsert Banco Fulgencio", + "retryOnFail": true, + "maxTries": 5, "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", @@ -2101,7 +2182,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -2127,7 +2208,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -2140,7 +2221,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -2227,7 +2308,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -2306,7 +2387,7 @@ }, { "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];" + "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: original.ultimo_refresco_ia || '',\n ultima_actualizacion: 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, @@ -2380,6 +2461,54 @@ ], "id": "fc72547c-317e-4ac7-9428-825e8504f143", "name": "Sticky Note3" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "cb44c645-00ba-43c1-a8f0-2fe556ebc382", + "leftValue": "={{ String($json.tablero_sync_key || '').trim() }}", + "rightValue": "", + "operator": { + "type": "string", + "operation": "notEmpty", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 5024, + 112 + ], + "id": "1b56b546-c4af-412d-bef8-8105eb830ae1", + "name": "IF - Tiene sync key antes de Upsert?" + }, + { + "parameters": { + "amount": 4 + }, + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + 2768, + 432 + ], + "id": "be090d5c-6fb6-4087-b25b-83fb8e3a3392", + "name": "Wait - Respetar cuota Google Sheets", + "webhookId": "8b997d7c-097c-4c8a-8793-6bce3db5e142" } ], "connections": { @@ -2566,7 +2695,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -2577,7 +2706,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -2588,7 +2717,7 @@ "main": [ [ { - "node": "Loop - Filas Banco Fulgencio", + "node": "Wait - Respetar cuota Google Sheets", "type": "main", "index": 0 } @@ -2599,7 +2728,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -2610,7 +2739,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -2621,7 +2750,7 @@ "main": [ [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -2679,7 +2808,7 @@ ], [ { - "node": "Google Sheets - Upsert Banco Fulgencio", + "node": "IF - Tiene sync key antes de Upsert?", "type": "main", "index": 0 } @@ -2705,6 +2834,17 @@ ] }, "Code - Marcar PPTX demasiado pesado": { + "main": [ + [ + { + "node": "IF - Tiene sync key antes de Upsert?", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF - Tiene sync key antes de Upsert?": { "main": [ [ { @@ -2712,38 +2852,40 @@ "type": "main", "index": 0 } + ], + [ + { + "node": "Loop - Filas Banco Fulgencio", + "type": "main", + "index": 0 + } + ] + ] + }, + "Wait - Respetar cuota Google Sheets": { + "main": [ + [ + { + "node": "Loop - Filas Banco Fulgencio", + "type": "main", + "index": 0 + } ] ] } }, "authors": "Isaac Aracena", - "name": "Version a92fd2c1", + "name": "Version db8caedf", "description": "", "autosaved": true, "workflowPublishHistory": [ { - "createdAt": "2026-07-22T14:43:58.310Z", - "id": 3376, + "createdAt": "2026-07-27T14:23:46.606Z", + "id": 3494, "workflowId": "g2JtKXNz5GDbkmQH", - "versionId": "a92fd2c1-b300-4def-8cd7-fdb0bde1bdfe", + "versionId": "db8caedf-99e0-4c41-b43a-4b115621fb4a", "event": "activated", "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" - }, - { - "createdAt": "2026-07-25T17:10:33.819Z", - "id": 3428, - "workflowId": "g2JtKXNz5GDbkmQH", - "versionId": "a92fd2c1-b300-4def-8cd7-fdb0bde1bdfe", - "event": "activated", - "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" - }, - { - "createdAt": "2026-07-25T17:10:33.748Z", - "id": 3427, - "workflowId": "g2JtKXNz5GDbkmQH", - "versionId": "a92fd2c1-b300-4def-8cd7-fdb0bde1bdfe", - "event": "deactivated", - "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" } ] }