{
  "updatedAt": "2026-04-13T21:32:55.751Z",
  "createdAt": "2026-04-09T15:53:13.294Z",
  "id": "j5j1B9yc9seV1ZNt",
  "name": "MyE OrdenesCompra",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "id": "2a4eeb8c-e249-4698-bfd7-cdc797bd2bba",
      "name": "Microsoft OneDrive Trigger",
      "type": "n8n-nodes-base.microsoftOneDriveTrigger",
      "typeVersion": 1,
      "position": [
        -3808,
        64
      ],
      "parameters": {},
      "credentials": {
        "microsoftOneDriveOAuth2Api": {
          "id": "KGZfdefsCF8ngR3q",
          "name": "Microsoft Drive account"
        }
      }
    },
    {
      "id": "b4339b8c-df23-4245-bef9-deafe39d40f6",
      "name": "Filtrar Evento OC",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -3568,
        64
      ],
      "parameters": {
        "jsCode": "function cleanText(value) {\n  return (value ?? '').toString().trim();\n}\n\nfunction encodePath(path) {\n  return String(path || '').split('/').filter(Boolean).map(encodeURIComponent).join('/');\n}\n\nfunction itemTs(value) {\n  return Date.parse(value?.lastModifiedDateTime || value?.createdDateTime || '') || Date.now();\n}\n\nfunction buildMeta(source) {\n  const name = cleanText(source.name).toLowerCase();\n  const mime = cleanText(source.mimeType).toLowerCase();\n  const rawPath = cleanText(source.path || source.parentReference?.path);\n  const driveId = cleanText(source.parentReference?.driveId || source.driveId);\n\n  if (!name.endsWith('.pdf')) return null;\n  if (mime && mime !== 'application/pdf') return null;\n  if (!/\\/inbox$/i.test(rawPath)) return null;\n  const inboxPath = rawPath.replace(/^\\/drive\\/root:/i, '').replace(/^\\/root:/i, '').replace(/^\\/+/, '');\n  const sourceBankPath = inboxPath.replace(/\\/INBOX$/i, '');\n  const parts = sourceBankPath.split('/').filter(Boolean);\n\n  if (parts.length < 4) {\n    throw new Error('La ruta de ordenes de compra debe tener formato OrdenesCompra/AAAA-MM/Semana X/Banco X/INBOX.');\n  }\n\n  const [sourceRoot, yearMonth, weekFolder, bankFolder] = parts;\n\n  if (sourceRoot.toLowerCase() !== 'ordenescompra') return null;\n\n  return {\n    ...source,\n    driveId,\n    inboxPath,\n    inboxPathEncoded: encodePath(inboxPath),\n    sourceRoot,\n    yearMonth,\n    weekFolder,\n    bankFolder,\n    sourceBankPath,\n    sourceBankPathEncoded: encodePath(sourceBankPath),\n    ocInboxKey: inboxPath,\n  };\n}\n\nconst sd = $getWorkflowStaticData('global');\nconst now = Date.now();\nconst PENDING_TTL_MS = 6 * 60 * 60 * 1000;\nconst LOCK_TIMEOUT_MS = 10 * 60 * 1000;\nlet matchedCurrentItems = 0;\nconst currentBatchByKey = {};\nif (!sd.ocPendingByKey || typeof sd.ocPendingByKey !== 'object') sd.ocPendingByKey = {};\nif (!sd.ocLocks || typeof sd.ocLocks !== 'object') sd.ocLocks = {};\n\nfor (const [key, value] of Object.entries(sd.ocPendingByKey)) {\n  const ts = Number(value?.lastEventTs || value?.queuedAtMs || 0);\n  if (!Number.isFinite(ts) || (now - ts) > PENDING_TTL_MS) delete sd.ocPendingByKey[key];\n}\nfor (const [key, ts] of Object.entries(sd.ocLocks)) {\n  if (!Number.isFinite(ts) || (now - ts) > LOCK_TIMEOUT_MS) delete sd.ocLocks[key];\n}\n\nfor (const item of items) {\n  const source = item.json || {};\n  const meta = buildMeta(source);\n  if (!meta) continue;\n  matchedCurrentItems += 1;\n  const existing = sd.ocPendingByKey[meta.ocInboxKey] || {};\n  sd.ocPendingByKey[meta.ocInboxKey] = {\n    ...existing,\n    ...meta,\n    lastEventTs: itemTs(source),\n    queuedAtMs: now,\n  };\n  currentBatchByKey[meta.ocInboxKey] = {\n    ...sd.ocPendingByKey[meta.ocInboxKey],\n  };\n}\n\nif (matchedCurrentItems === 0) return [];\n\nconst currentBatchCandidates = Object.values(currentBatchByKey)\n  .filter((entry) => entry)\n  .sort((a, b) => Number(b.lastEventTs || 0) - Number(a.lastEventTs || 0));\nconst candidates = Object.values(sd.ocPendingByKey)\n  .filter((entry) => entry)\n  .sort((a, b) => Number(b.lastEventTs || 0) - Number(a.lastEventTs || 0));\n\nconst selectUnlocked = (entries) => entries.find((entry) => {\n  const lockKey = String(entry?.inboxPath || '').trim();\n  return lockKey && !sd.ocLocks[lockKey];\n});\nconst selected = selectUnlocked(currentBatchCandidates) || selectUnlocked(candidates);\n\nif (!selected) return [];\n\nreturn [{\n  json: {\n    ...selected,\n  }\n}];"
      }
    },
    {
      "id": "09b0113f-f53e-44a0-be06-749a74cbc23e",
      "name": "Lock OrdenesCompra",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -3328,
        64
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nconst now = Date.now();\nconst LOCK_TIMEOUT_MS = 10 * 60 * 1000;\nconst RECENT_TTL_MS = 15 * 60 * 1000;\n\nconst inboxPath = String($json.inboxPath || '').trim();\nconst eventKey = [String($json.id || ''), String($json.name || '').toLowerCase(), inboxPath].join('|');\n\nif (!sd.ocLocks || typeof sd.ocLocks !== 'object') sd.ocLocks = {};\nif (!sd.ocRecent || typeof sd.ocRecent !== 'object') sd.ocRecent = {};\n\nfor (const [key, ts] of Object.entries(sd.ocLocks)) {\n  if (!Number.isFinite(ts) || (now - ts) > LOCK_TIMEOUT_MS) delete sd.ocLocks[key];\n}\nfor (const [key, ts] of Object.entries(sd.ocRecent)) {\n  if (!Number.isFinite(ts) || (now - ts) > RECENT_TTL_MS) delete sd.ocRecent[key];\n}\n\nif (!inboxPath) return [];\nif (eventKey !== '||' && sd.ocRecent[eventKey]) return [];\nif (sd.ocLocks[inboxPath]) {\n  if (eventKey !== '||') sd.ocRecent[eventKey] = now;\n  return [];\n}\n\nsd.ocLocks[inboxPath] = now;\nif (eventKey !== '||') sd.ocRecent[eventKey] = now;\n\nreturn [{\n  json: {\n    ...$json,\n    lockKey: inboxPath,\n    loteStartedAtMs: now,\n    loteRetryCount: 0,\n  }\n}];"
      }
    },
    {
      "id": "186aedba-8217-49de-8530-61f389953f95",
      "webhookId": "f9056f26-350e-4d54-aac1-4d06e0116d06",
      "name": "Wait Lote OC",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        -3088,
        64
      ],
      "parameters": {
        "amount": 20,
        "unit": "seconds"
      }
    },
    {
      "id": "169dc460-9e4a-4092-b5d8-f483aaeca278",
      "name": "Listar Archivos Inbox OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -2848,
        64
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$json.inboxPathEncoded}}:/children?$top=999&$select=id,name,webUrl,file,folder,parentReference,lastModifiedDateTime,createdDateTime",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "retryOnFail": true,
      "maxTries": 4,
      "waitBetweenTries": 4000
    },
    {
      "id": "3eec4dc3-8092-489f-aed0-b35ce5f1b1e6",
      "name": "Evaluar Lote OC",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2608,
        64
      ],
      "parameters": {
        "jsCode": "function itemTs(item) {\n  return Date.parse(item?.lastModifiedDateTime || item?.createdDateTime || '') || 0;\n}\n\nfunction isPdf(item) {\n  const name = String(item?.name || '').toLowerCase();\n  return Boolean(item?.file) && name.endsWith('.pdf');\n}\n\nfunction pdfSignature(items) {\n  return items\n    .map((item) => [String(item?.id || '').trim(), String(item?.name || '').trim().toLowerCase()].join(':'))\n    .filter(Boolean)\n    .join('|');\n}\n\nfunction normalizePdfs(items) {\n  return (Array.isArray(items) ? items : []).filter(isPdf).sort((a, b) => itemTs(a) - itemTs(b));\n}\n\nfunction readNodeJson(nodeName) {\n  try {\n    return (($items(nodeName)[0] || {}).json || {});\n  } catch {\n    return {};\n  }\n}\n\nconst retrySource = readNodeJson('Wait Reintento OC');\nconst initialSource = readNodeJson('Wait Lote OC');\nconst source = Object.keys(retrySource).length ? retrySource : initialSource;\nconst children = Array.isArray($json.value) ? $json.value : [];\nconst currentPdfs = normalizePdfs(children);\nconst previousNonEmptyPdfs = normalizePdfs(source.loteLastNonEmptyPdfs);\nconst pdfs = currentPdfs.length ? currentPdfs : previousNonEmptyPdfs;\nconst latestPdfTs = pdfs.reduce((max, item) => Math.max(max, itemTs(item)), 0);\nconst quietMs = latestPdfTs ? Math.max(0, Date.now() - latestPdfTs) : 0;\nconst currentPdfSignature = pdfSignature(pdfs);\nconst previousPdfSignature = String(source.lotePdfSignature || '');\nconst samePdfSignatureStreak = currentPdfSignature && currentPdfSignature === previousPdfSignature\n  ? Number(source.loteSamePdfSignatureStreak || 0) + 1\n  : 0;\nconst loteStartedAtMs = Number(source.loteStartedAtMs || Date.now());\nconst elapsedSinceStartMs = Math.max(0, Date.now() - loteStartedAtMs);\nconst QUIET_WINDOW_MS = 60 * 1000;\nconst MAX_BATCH_WAIT_MS = 90 * 1000;\nconst MAX_RETRIES = 18;\nconst retryCount = Number(source.loteRetryCount || 0);\nconst forceContinue = retryCount >= MAX_RETRIES;\nconst loteOcEstable = forceContinue || (\n  pdfs.length > 0 && (\n    quietMs >= QUIET_WINDOW_MS ||\n    samePdfSignatureStreak >= 2 ||\n    elapsedSinceStartMs >= MAX_BATCH_WAIT_MS\n  )\n);\n\nreturn [{\n  json: {\n    ...source,\n    pdfs,\n    currentPdfCount: currentPdfs.length,\n    pdfCount: pdfs.length,\n    loteQuietMs: quietMs,\n    loteQuietWindowMs: QUIET_WINDOW_MS,\n    loteStartedAtMs,\n    loteElapsedSinceStartMs: elapsedSinceStartMs,\n    loteMaxBatchWaitMs: MAX_BATCH_WAIT_MS,\n    loteRetryCount: retryCount,\n    lotePdfSignature: currentPdfSignature,\n    loteLastNonEmptyPdfs: pdfs,\n    loteSamePdfSignatureStreak: samePdfSignatureStreak,\n    loteOcEstable,\n    loteForceContinue: forceContinue,\n  }\n}];"
      }
    },
    {
      "id": "420c64c0-58b1-4675-bd4c-2128361e8172",
      "name": "IF Lote OC Estable",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -2368,
        64
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "leftValue": "={{$json.loteOcEstable === true}}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "fe4a5923-7d99-4fec-9cb9-442268f0d4cc",
      "name": "Preparar Reintento OC",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2128,
        192
      ],
      "parameters": {
        "jsCode": "return [{\n  json: {\n    ...$json,\n    loteRetryCount: Number($json.loteRetryCount || 0) + 1,\n  }\n}];"
      }
    },
    {
      "id": "7590a41a-d33f-4354-8551-39837384e7b8",
      "webhookId": "dad8512a-0bc2-4fc0-86bb-c2d0c3f1f6c6",
      "name": "Wait Reintento OC",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        -1888,
        192
      ],
      "parameters": {
        "amount": 20,
        "unit": "seconds"
      }
    },
    {
      "id": "57c7a613-f3b1-4614-8f40-c6eab1b98e05",
      "name": "Listar Carpetas Banco OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -2128,
        -32
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$json.sourceBankPathEncoded}}:/children?$top=999&$select=id,name,webUrl,folder,parentReference,lastModifiedDateTime,createdDateTime",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "retryOnFail": true,
      "maxTries": 4,
      "waitBetweenTries": 4000
    },
    {
      "id": "91f25413-8dd6-45c2-bd2a-dbe294398350",
      "name": "Preparar NOM OC",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1888,
        -32
      ],
      "parameters": {
        "jsCode": "function encodePath(path) {\n  return String(path || '').split('/').filter(Boolean).map(encodeURIComponent).join('/');\n}\n\nconst sd = $getWorkflowStaticData('global');\nconst now = Date.now();\nconst BATCH_REUSE_TTL_MS = 6 * 60 * 60 * 1000;\nconst EMPTY_BATCH_BANK_REUSE_TTL_MS = 15 * 60 * 1000;\nconst ACTIVE_BANK_LOCK_TTL_MS = 10 * 60 * 1000;\nconst lot = (($items('Evaluar Lote OC')[0] || {}).json || {});\nconst children = Array.isArray($json.value) ? $json.value : [];\nconst folders = children\n  .filter((item) => item?.folder)\n  .map((item) => ({\n    name: String(item.name || '').trim().toUpperCase(),\n    ts: Date.parse(item?.createdDateTime || item?.lastModifiedDateTime || '') || 0,\n  }));\n\nfunction buildNomFolderName(prefix, timestamp = Date.now()) {\n  const parts = new Intl.DateTimeFormat('en-GB', {\n    timeZone: 'America/Santiago',\n    year: 'numeric',\n    month: '2-digit',\n    day: '2-digit',\n    hour: '2-digit',\n    minute: '2-digit',\n    second: '2-digit',\n    hour12: false,\n  })\n    .formatToParts(new Date(timestamp))\n    .filter((part) => part.type !== 'literal')\n    .reduce((acc, part) => ({ ...acc, [part.type]: part.value }), {});\n\n  return 'NOM-' + prefix + '-' + parts.year + '-' + parts.month + '-' + parts.day + '_' + parts.hour + '-' + parts.minute + '-' + parts.second;\n}\n\nif (!sd.ocNomByBatch || typeof sd.ocNomByBatch !== 'object') sd.ocNomByBatch = {};\nif (!sd.ocNomByBank || typeof sd.ocNomByBank !== 'object') sd.ocNomByBank = {};\nif (!sd.ocNomActiveByBank || typeof sd.ocNomActiveByBank !== 'object') sd.ocNomActiveByBank = {};\nif (!sd.ocLocks || typeof sd.ocLocks !== 'object') sd.ocLocks = {};\nfor (const [key, value] of Object.entries(sd.ocNomByBatch)) {\n  const ts = Number(value?.atMs || 0);\n  if (!Number.isFinite(ts) || (now - ts) > BATCH_REUSE_TTL_MS) delete sd.ocNomByBatch[key];\n}\nfor (const [key, value] of Object.entries(sd.ocNomByBank)) {\n  const ts = Number(value?.atMs || 0);\n  if (!Number.isFinite(ts) || (now - ts) > EMPTY_BATCH_BANK_REUSE_TTL_MS) delete sd.ocNomByBank[key];\n}\nfor (const [key, value] of Object.entries(sd.ocNomActiveByBank)) {\n  const ts = Number(value?.atMs || 0);\n  if (!Number.isFinite(ts) || (now - ts) > ACTIVE_BANK_LOCK_TTL_MS) delete sd.ocNomActiveByBank[key];\n}\n\nconst sourceBankPath = String(lot.sourceBankPath || '').trim();\nconst lotePdfSignature = String(lot.lotePdfSignature || '').trim();\nconst batchKey = [sourceBankPath, lotePdfSignature].join('||');\nconst cachedBatch = sd.ocNomByBatch[batchKey];\nconst cachedNomFolderName = String(cachedBatch?.nomFolderName || '').trim().toUpperCase();\nconst cachedBank = sd.ocNomByBank[sourceBankPath];\nconst cachedBankNomFolderName = String(cachedBank?.nomFolderName || '').trim().toUpperCase();\nconst activeBank = sd.ocNomActiveByBank[sourceBankPath];\nconst activeNomFolderName = String(activeBank?.nomFolderName || '').trim().toUpperCase();\nconst hasActiveBankLock = Boolean(sd.ocLocks[String(lot.inboxPath || '').trim()]);\nconst preferBankCachedNomFolderName = Number(lot.currentPdfCount || 0) === 0 ? cachedBankNomFolderName : '';\nconst nomFolderName =\n  (hasActiveBankLock ? activeNomFolderName : '') ||\n  cachedNomFolderName ||\n  preferBankCachedNomFolderName ||\n  buildNomFolderName('OC', now);\n\nsd.ocNomByBatch[batchKey] = {\n  nomFolderName,\n  atMs: now,\n  sourceBankPath,\n  lotePdfSignature,\n};\nsd.ocNomByBank[sourceBankPath] = {\n  nomFolderName,\n  atMs: now,\n};\nsd.ocNomActiveByBank[sourceBankPath] = {\n  nomFolderName,\n  atMs: now,\n  lotePdfSignature,\n};\n\nconst sourceNomPath = sourceBankPath + '/' + nomFolderName;\nconst nominasBankPath = ['Nominas', lot.yearMonth, lot.weekFolder, lot.bankFolder].filter(Boolean).join('/');\nconst nominasNomPath = nominasBankPath + '/' + nomFolderName;\nconst reportesBankPath = ['Reportes', lot.yearMonth, lot.weekFolder, lot.bankFolder].filter(Boolean).join('/');\nconst reportesNomPath = reportesBankPath + '/' + nomFolderName;\n\nreturn [{\n  json: {\n    ...lot,\n    batchKey,\n    nomFolderName,\n    sourceNomPath,\n    sourceNomPathEncoded: encodePath(sourceNomPath),\n    nominasBankPath,\n    nominasBankPathEncoded: encodePath(nominasBankPath),\n    nominasNomPath,\n    nominasNomPathEncoded: encodePath(nominasNomPath),\n    reportesBankPath,\n    reportesBankPathEncoded: encodePath(reportesBankPath),\n    reportesNomPath,\n    reportesNomPathEncoded: encodePath(reportesNomPath),\n  }\n}];"
      }
    },
    {
      "id": "50bb950e-4019-4bea-85fc-2d24dd1f90a7",
      "name": "Crear NOM OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -1648,
        -32
      ],
      "parameters": {
        "method": "POST",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$json.sourceBankPathEncoded}}:/children",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{({\"name\": $json.nomFolderName, \"folder\": {}, \"@microsoft.graph.conflictBehavior\": \"fail\"})}}",
        "options": {}
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "57fe6077-65b7-4804-ba08-c814e536587e",
      "name": "Obtener NOM OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -1408,
        -32
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$items(\"Preparar NOM OC\")[0].json.sourceNomPathEncoded}}?$select=id,name,webUrl,parentReference",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "id": "a7764028-ae83-40e9-b589-156fca65ead1",
      "name": "Crear NOM Nominas OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -1168,
        -32
      ],
      "parameters": {
        "method": "POST",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$items(\"Preparar NOM OC\")[0].json.nominasBankPathEncoded}}:/children",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{({\"name\": $items(\"Preparar NOM OC\")[0].json.nomFolderName, \"folder\": {}, \"@microsoft.graph.conflictBehavior\": \"fail\"})}}",
        "options": {}
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "ba9e63fa-4d26-4747-8576-56705de02642",
      "name": "Obtener NOM Nominas OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -928,
        -32
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$items(\"Preparar NOM OC\")[0].json.nominasNomPathEncoded}}?$select=id,name,webUrl,parentReference",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "id": "2444a33d-4386-4cb5-aed6-ec0f308550ef",
      "name": "Crear NOM Reportes OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -688,
        -32
      ],
      "parameters": {
        "method": "POST",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$items(\"Preparar NOM OC\")[0].json.reportesBankPathEncoded}}:/children",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{({\"name\": $items(\"Preparar NOM OC\")[0].json.nomFolderName, \"folder\": {}, \"@microsoft.graph.conflictBehavior\": \"fail\"})}}",
        "options": {}
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "eaa98b5b-0f61-4d25-8ee0-15ac2d8997a0",
      "name": "Obtener NOM Reportes OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -448,
        -32
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$items(\"Preparar NOM OC\")[0].json.reportesNomPathEncoded}}?$select=id,name,webUrl,parentReference",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "id": "0015dadb-0a06-4834-8202-3660c6ac575e",
      "name": "Preparar Movimientos OC",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -208,
        -32
      ],
      "parameters": {
        "jsCode": "const prep = (($items('Preparar NOM OC')[0] || {}).json || {});\nconst sourceNom = (($items('Obtener NOM OC')[0] || {}).json || {});\nconst nominasNom = (($items('Obtener NOM Nominas OC')[0] || {}).json || {});\nconst reportesNom = (($items('Obtener NOM Reportes OC')[0] || {}).json || {});\nconst pdfs = Array.isArray(prep.pdfs) ? prep.pdfs : [];\n\nreturn pdfs.map((pdf) => ({\n  json: {\n    driveId: prep.driveId,\n    pdfId: pdf.id,\n    pdfName: pdf.name,\n    sourceNomId: sourceNom.id,\n    reportesNomId: reportesNom.id,\n    sourceNomPathEncoded: prep.sourceNomPathEncoded,\n    reportesNomPathEncoded: prep.reportesNomPathEncoded,\n    nominasNomWebUrl: String(nominasNom.webUrl || ''),\n    reportesNomWebUrl: String(reportesNom.webUrl || ''),\n    yearMonth: prep.yearMonth,\n    weekFolder: prep.weekFolder,\n    bankFolder: prep.bankFolder,\n    nomFolderName: prep.nomFolderName,\n  }\n}));"
      }
    },
    {
      "id": "998270b0-b76f-4f37-aacc-ed3bc853f4c2",
      "name": "Mover PDF a NOM OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        32,
        -32
      ],
      "parameters": {
        "method": "PATCH",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$json.pdfId}}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{({\"parentReference\": {\"id\": $json.sourceNomId}, \"name\": $json.pdfName})}}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 20,
              "batchInterval": 300
            }
          },
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "retryOnFail": true,
      "maxTries": 4,
      "waitBetweenTries": 4000
    },
    {
      "id": "cc06b0eb-e632-41f3-a403-ccbe48b4f0f7",
      "name": "Links",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        272,
        -144
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "name": "pdf_webUrl",
              "value": "={{$json.webUrl}}",
              "type": "string"
            },
            {
              "name": "pdf_id",
              "value": "={{$json.id}}",
              "type": "string"
            },
            {
              "name": "pdf_name",
              "value": "={{$json.name}}",
              "type": "string"
            },
            {
              "name": "sourceNomPathEncoded",
              "value": "={{$(\"Preparar Movimientos OC\").item.json.sourceNomPathEncoded}}",
              "type": "string"
            },
            {
              "name": "reportesNomPathEncoded",
              "value": "={{$(\"Preparar Movimientos OC\").item.json.reportesNomPathEncoded}}",
              "type": "string"
            },
            {
              "name": "nominasNomWebUrl",
              "value": "={{$(\"Preparar Movimientos OC\").item.json.nominasNomWebUrl}}",
              "type": "string"
            },
            {
              "name": "reportesNomWebUrl",
              "value": "={{$(\"Preparar Movimientos OC\").item.json.reportesNomWebUrl}}",
              "type": "string"
            },
            {
              "name": "yearMonth",
              "value": "={{$(\"Preparar Movimientos OC\").item.json.yearMonth}}",
              "type": "string"
            },
            {
              "name": "weekFolder",
              "value": "={{$(\"Preparar Movimientos OC\").item.json.weekFolder}}",
              "type": "string"
            },
            {
              "name": "bankFolder",
              "value": "={{$(\"Preparar Movimientos OC\").item.json.bankFolder}}",
              "type": "string"
            },
            {
              "name": "nomFolderName",
              "value": "={{$(\"Preparar Movimientos OC\").item.json.nomFolderName}}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "68b90a24-da2f-4592-a6e4-a57eb9972894",
      "name": "Descargar OrdenCompra",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        272,
        80
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$json.id}}/content",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 5,
              "batchInterval": 400
            }
          },
          "response": {
            "response": {
              "responseFormat": "file"
            }
          },
          "timeout": 180000
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "retryOnFail": true,
      "maxTries": 4,
      "waitBetweenTries": 5000
    },
    {
      "id": "6205879f-5055-48d5-b90a-27deb383b8ba",
      "name": "Combinacion Links y PDF OC",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        512,
        -32
      ],
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      }
    },
    {
      "id": "e74af95b-2a4b-4835-8666-5befa5e3f1ee",
      "name": "Extraer Texto OC",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        752,
        -32
      ],
      "parameters": {
        "operation": "pdf",
        "binaryPropertyName": "data",
        "destinationKey": "pdf_text",
        "options": {}
      }
    },
    {
      "id": "42b56769-a38a-4bfa-a0dc-4309e536a5ca",
      "name": "IA ANALIZA ORDENCOMPRA",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        992,
        -32
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/responses",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendHeaders": true,
        "specifyHeaders": "json",
        "jsonHeaders": "{}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ (() => {\n  const pdfText = String($json.text ?? $json.pdf_text ?? '').trim();\n  const sourceItem = $('Combinacion Links y PDF OC').item || {};\n  const pdfBase64 = String(sourceItem?.binary?.data?.data ?? '').trim();\n  const usePdfFallback = pdfText.length < 200;\n  const promptLines = [\n    'Eres un asistente que lee ORDENES DE COMPRA chilenas.',\n    usePdfFallback\n      ? 'El texto extraído salió vacío o insuficiente; analiza el PDF adjunto directamente.'\n      : 'Usa solamente el texto entregado.',\n    'Si el documento NO es claramente una orden de compra, devuelve null en todos los campos.',\n    'Devuelve SOLO un objeto JSON con: numero_oc, rut_proveedor, nombre_proveedor, fecha_oc, monto_neto, monto_iva, monto_total.',\n    'No confundas numero_oc con numero de factura, guia o cotizacion.',\n    'Usa fecha YYYY-MM-DD y montos enteros CLP.',\n    'No inventes valores.',\n    '',\n    usePdfFallback ? 'PDF ADJUNTO:' : 'TEXTO EXTRAIDO:',\n    usePdfFallback ? '' : pdfText.substring(0, 12000),\n  ].join('\\n');\n\n  const content = usePdfFallback\n    ? [\n        {\n          type: 'input_file',\n          filename: String($json.pdf_name || 'orden_compra.pdf'),\n          file_data: 'data:application/pdf;base64,' + pdfBase64,\n        },\n        {\n          type: 'input_text',\n          text: promptLines,\n        },\n      ]\n    : [\n        {\n          type: 'input_text',\n          text: promptLines,\n        },\n      ];\n\n  return ({\n    model: 'gpt-5-mini',\n    max_output_tokens: 2000,\n    reasoning: { effort: 'low' },\n    input: [\n      {\n        role: 'user',\n        content,\n      }\n    ],\n    text: {\n      format: {\n        type: 'json_schema',\n        name: 'orden_compra',\n        schema: {\n          type: 'object',\n          properties: {\n            numero_oc: { type: ['string', 'null'] },\n            rut_proveedor: { type: ['string', 'null'] },\n            nombre_proveedor: { type: ['string', 'null'] },\n            fecha_oc: { type: ['string', 'null'] },\n            monto_neto: { type: ['number', 'null'] },\n            monto_iva: { type: ['number', 'null'] },\n            monto_total: { type: ['number', 'null'] }\n          },\n          required: ['numero_oc', 'rut_proveedor', 'nombre_proveedor', 'fecha_oc', 'monto_neto', 'monto_iva', 'monto_total'],\n          additionalProperties: false\n        },\n        strict: true\n      }\n    }\n  });\n})() }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 1
            }
          },
          "timeout": 180000
        }
      },
      "credentials": {
        "httpBearerAuth": {
          "id": "s7yN4svPDjFhA59v",
          "name": "Bearer Auth account"
        }
      },
      "retryOnFail": true,
      "maxTries": 5,
      "waitBetweenTries": 5000
    },
    {
      "id": "5be8bb29-8a26-4c86-a4de-4fd35743102a",
      "name": "PARSEAR ORDENCOMPRA",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1232,
        -32
      ],
      "parameters": {
        "jsCode": "const sourceItems = $items('Combinacion Links y PDF OC');\n\nfunction cleanText(value) {\n  return (value ?? '').toString().trim();\n}\n\nfunction normalizeAmount(value) {\n  if (value === null || value === undefined || value === '') return 0;\n  if (typeof value === 'number' && Number.isFinite(value)) return Math.max(0, Math.round(value));\n  const raw = cleanText(value).replace(/\\$/g, '').replace(/\\./g, '').replace(',', '.');\n  const num = Number(raw);\n  return Number.isFinite(num) ? Math.max(0, Math.round(num)) : 0;\n}\n\nfunction safeParseJson(text) {\n  if (typeof text === 'object' && text !== null) return text;\n  const t = cleanText(text);\n  if (!t) return null;\n  try {\n    return JSON.parse(t);\n  } catch {\n    return null;\n  }\n}\n\nfunction extractObject(payload) {\n  const direct = safeParseJson(payload);\n  if (direct && typeof direct === 'object' && !Array.isArray(direct)) return direct;\n  const text = cleanText(payload);\n  const match = text.match(/\\{[\\s\\S]*\\}/);\n  if (!match) return {};\n  return safeParseJson(match[0]) || {};\n}\n\nfunction extractResponseText(response) {\n  const directCandidates = [\n    response?.output_text,\n    response?.response?.output_text,\n  ];\n  for (const candidate of directCandidates) {\n    if (typeof candidate === 'string') {\n      const text = cleanText(candidate);\n      if (text) return text;\n    }\n  }\n\n  const output = Array.isArray(response?.output) ? response.output : [];\n  for (const entry of output) {\n    const content = Array.isArray(entry?.content) ? entry.content : [];\n    for (const part of content) {\n      const text = cleanText(part?.text);\n      if (text) return text;\n    }\n  }\n\n  return '';\n}\n\nfunction resolvePairIndex(item) {\n  const pair = item.pairedItem;\n  if (Array.isArray(pair) && pair.length && typeof pair[0]?.item === 'number') return pair[0].item;\n  if (pair && typeof pair.item === 'number') return pair.item;\n  return null;\n}\n\nreturn items.map((item) => {\n  const pairIdx = resolvePairIndex(item);\n  const src = pairIdx !== null && sourceItems[pairIdx] ? (sourceItems[pairIdx].json || {}) : {};\n  const rawText = extractResponseText(item.json || {});\n  const oc = extractObject(rawText);\n\n  return {\n    json: {\n      tipo_documento: 'ORDEN_COMPRA',\n      rut_proveedor: oc.rut_proveedor ?? null,\n      nombre_proveedor: oc.nombre_proveedor ?? null,\n      numero_factura: '',\n      numero_oc: oc.numero_oc ?? null,\n      fecha_documento: oc.fecha_oc ?? null,\n      fecha_vencimiento: '',\n      monto_neto: normalizeAmount(oc.monto_neto),\n      monto_iva: normalizeAmount(oc.monto_iva),\n      monto_total: normalizeAmount(oc.monto_total),\n      url_documento: src.pdf_webUrl || '',\n      sourceNomPathEncoded: src.sourceNomPathEncoded || '',\n      reportesNomPathEncoded: src.reportesNomPathEncoded || '',\n      nominasNomWebUrl: src.nominasNomWebUrl || '',\n      reportesNomWebUrl: src.reportesNomWebUrl || '',\n      yearMonth: src.yearMonth || '',\n      weekFolder: src.weekFolder || '',\n      bankFolder: src.bankFolder || '',\n      nomFolderName: src.nomFolderName || '',\n    }\n  };\n});"
      }
    },
    {
      "id": "90be0c95-b7fb-4f5d-9239-043e8224a2eb",
      "name": "Preparar Filas Excel OC",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1472,
        -32
      ],
      "parameters": {
        "jsCode": "return items.map((item) => ({\n  json: {\n    tipo_documento: item.json.tipo_documento || 'ORDEN_COMPRA',\n    rut_proveedor: item.json.rut_proveedor || '',\n    nombre_proveedor: item.json.nombre_proveedor || '',\n    numero_factura: '',\n    numero_oc: item.json.numero_oc || '',\n    fecha_documento: item.json.fecha_documento || '',\n    fecha_vencimiento: '',\n    monto_neto: Number(item.json.monto_neto || 0),\n    monto_iva: Number(item.json.monto_iva || 0),\n    monto_total: Number(item.json.monto_total || 0),\n    url_documento: item.json.url_documento || '',\n  }\n}));"
      }
    },
    {
      "id": "feef8955-e7e4-463e-bd07-666e1f0cf319",
      "name": "Generar Excel OC",
      "type": "n8n-nodes-base.spreadsheetFile",
      "typeVersion": 2,
      "position": [
        1712,
        -32
      ],
      "parameters": {
        "operation": "toFile",
        "binaryPropertyName": "data",
        "fileFormat": "xlsx",
        "options": {
          "fileName": "facturas_semana.xlsx",
          "headerRow": true,
          "sheetName": "Facturas"
        }
      }
    },
    {
      "id": "4493fd8a-e2d0-4679-8efd-40a6f53aac6d",
      "name": "Combinar Excel y Contexto OC",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        1952,
        -32
      ],
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      }
    },
    {
      "id": "4bbf577e-1bb0-4c89-9f8b-bcb052753aef",
      "name": "Subir Excel Reportes OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2192,
        -32
      ],
      "parameters": {
        "method": "PUT",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$items(\"Obtener NOM Reportes OC\")[0].json.id}}:/facturas_semana.xlsx:/content",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendBody": true,
        "contentType": "binaryData",
        "inputDataFieldName": "data",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "retryOnFail": true
    },
    {
      "id": "c351739e-51db-4efd-bdcb-dabf0152c304",
      "webhookId": "2c4d1d73-031d-4d83-859b-6f9a74ae8b02",
      "name": "Wait Tabla Reportes OC",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        2416,
        -32
      ],
      "parameters": {
        "amount": 5,
        "unit": "seconds"
      }
    },
    {
      "id": "e4fe5e83-992f-456c-8e48-17da60648b87",
      "name": "Crear Tabla Excel Reportes OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2608,
        -32
      ],
      "parameters": {
        "method": "POST",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$items(\"Subir Excel Reportes OC\")[0].json.id}}/workbook/worksheets/Facturas/tables/add",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{({ address: \"A1:K\" + String($items(\"Preparar Filas Excel OC\").length + 1), hasHeaders: true })}}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "db74f8ae-070d-414f-808a-f9f9e9e8dc33",
      "name": "Formatear Montos Reportes OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2816,
        -32
      ],
      "parameters": {
        "method": "PATCH",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$items(\"Subir Excel Reportes OC\")[0].json.id}}/workbook/worksheets/Facturas/range(address='H2:J{{$items(\"Preparar Filas Excel OC\").length + 1}}')",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{({ numberFormat: Array.from({ length: $items(\"Preparar Filas Excel OC\").length }, () => [\"[$$-es-CL] #,##0\", \"[$$-es-CL] #,##0\", \"[$$-es-CL] #,##0\"]) })}}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "a9adb89b-6084-45e3-a84f-d991817ddbd3",
      "name": "Ajustar Columnas Reportes OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        3008,
        -32
      ],
      "parameters": {
        "method": "PATCH",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$items(\"Subir Excel Reportes OC\")[0].json.id}}/workbook/worksheets/Facturas/range(address='A:K')/format",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{({ columnWidth: 160, wrapText: false })}}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "3b3be25c-98f7-4d94-ba06-35c941be49d1",
      "name": "Hipervincular URLs Reportes OC",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        3200,
        -32
      ],
      "parameters": {
        "method": "PATCH",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$items(\"Subir Excel Reportes OC\")[0].json.id}}/workbook/worksheets/Facturas/range(address='K2:K{{$items(\"Preparar Filas Excel OC\").length + 1}}')",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ ({ formulas: Array.from({ length: $items(\"Preparar Filas Excel OC\").length }, (_, idx) => { const row = ($items(\"Preparar Filas Excel OC\")[idx] || {}).json || {}; const rawUrl = String(row.url_documento || \"\").replace(/\"/g, '\"\"'); return [rawUrl ? `=HYPERLINK(\"${rawUrl}\",\"Abrir PDF\")` : null]; }) }) }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "e9ba5f5c-fafb-49e8-8c70-766631916298",
      "name": "Unlock OrdenesCompra",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3408,
        -32
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nconst now = Date.now();\nconst PENDING_TTL_MS = 6 * 60 * 60 * 1000;\nconst LOCK_TIMEOUT_MS = 10 * 60 * 1000;\nfunction readNodeJson(nodeName) {\n  try {\n    return (($items(nodeName)[0] || {}).json || {});\n  } catch {\n    return {};\n  }\n}\nfunction getNextPending(excludedPendingKey = '') {\n  if (!sd.ocPendingByKey || typeof sd.ocPendingByKey !== 'object') sd.ocPendingByKey = {};\n  if (!sd.ocLocks || typeof sd.ocLocks !== 'object') sd.ocLocks = {};\n  if (!sd.ocNomActiveByBank || typeof sd.ocNomActiveByBank !== 'object') sd.ocNomActiveByBank = {};\n  for (const [key, value] of Object.entries(sd.ocPendingByKey)) {\n    const ts = Number(value?.lastEventTs || value?.queuedAtMs || 0);\n    if (!Number.isFinite(ts) || (now - ts) > PENDING_TTL_MS) delete sd.ocPendingByKey[key];\n  }\n  for (const [key, ts] of Object.entries(sd.ocLocks)) {\n    if (!Number.isFinite(ts) || (now - ts) > LOCK_TIMEOUT_MS) delete sd.ocLocks[key];\n  }\n  const candidates = Object.entries(sd.ocPendingByKey)\n    .filter(([key, entry]) => key !== excludedPendingKey && entry)\n    .map(([, entry]) => entry)\n    .sort((a, b) => Number(b.lastEventTs || 0) - Number(a.lastEventTs || 0));\n  const selected = candidates.find((entry) => {\n    const lockKey = String(entry?.inboxPath || '').trim();\n    return lockKey && !sd.ocLocks[lockKey];\n  });\n  return selected ? [{ json: { ...selected } }] : [];\n}\nconst current = $json || {};\nconst lockNode = readNodeJson('Lock OrdenesCompra');\nconst key = String(current.inboxPath || '').trim() || String(lockNode.lockKey || '').trim();\nconst pendingKey = String(current.ocInboxKey || key).trim();\nconst sourceBankPath = String(current.sourceBankPath || '').trim();\nif (sd.ocLocks && key && sd.ocLocks[key]) delete sd.ocLocks[key];\nif (sd.ocPendingByKey && pendingKey && sd.ocPendingByKey[pendingKey]) delete sd.ocPendingByKey[pendingKey];\nif (sd.ocNomActiveByBank && sourceBankPath && sd.ocNomActiveByBank[sourceBankPath]) delete sd.ocNomActiveByBank[sourceBankPath];\nreturn getNextPending(pendingKey);"
      }
    }
  ],
  "connections": {
    "Microsoft OneDrive Trigger": {
      "main": [
        [
          {
            "node": "Filtrar Evento OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filtrar Evento OC": {
      "main": [
        [
          {
            "node": "Lock OrdenesCompra",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lock OrdenesCompra": {
      "main": [
        [
          {
            "node": "Wait Lote OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Lote OC": {
      "main": [
        [
          {
            "node": "Listar Archivos Inbox OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Listar Archivos Inbox OC": {
      "main": [
        [
          {
            "node": "Evaluar Lote OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluar Lote OC": {
      "main": [
        [
          {
            "node": "IF Lote OC Estable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Lote OC Estable": {
      "main": [
        [
          {
            "node": "Listar Carpetas Banco OC",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Preparar Reintento OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Reintento OC": {
      "main": [
        [
          {
            "node": "Wait Reintento OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Reintento OC": {
      "main": [
        [
          {
            "node": "Listar Archivos Inbox OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Listar Carpetas Banco OC": {
      "main": [
        [
          {
            "node": "Preparar NOM OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar NOM OC": {
      "main": [
        [
          {
            "node": "Crear NOM OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear NOM OC": {
      "main": [
        [
          {
            "node": "Obtener NOM OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obtener NOM OC": {
      "main": [
        [
          {
            "node": "Crear NOM Nominas OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear NOM Nominas OC": {
      "main": [
        [
          {
            "node": "Obtener NOM Nominas OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obtener NOM Nominas OC": {
      "main": [
        [
          {
            "node": "Crear NOM Reportes OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear NOM Reportes OC": {
      "main": [
        [
          {
            "node": "Obtener NOM Reportes OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obtener NOM Reportes OC": {
      "main": [
        [
          {
            "node": "Preparar Movimientos OC",
            "type": "main",
            "index": 0
          },
          {
            "node": "Combinar Excel y Contexto OC",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Preparar Movimientos OC": {
      "main": [
        [
          {
            "node": "Mover PDF a NOM OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mover PDF a NOM OC": {
      "main": [
        [
          {
            "node": "Links",
            "type": "main",
            "index": 0
          },
          {
            "node": "Descargar OrdenCompra",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Links": {
      "main": [
        [
          {
            "node": "Combinacion Links y PDF OC",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Descargar OrdenCompra": {
      "main": [
        [
          {
            "node": "Combinacion Links y PDF OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combinacion Links y PDF OC": {
      "main": [
        [
          {
            "node": "Extraer Texto OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extraer Texto OC": {
      "main": [
        [
          {
            "node": "IA ANALIZA ORDENCOMPRA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IA ANALIZA ORDENCOMPRA": {
      "main": [
        [
          {
            "node": "PARSEAR ORDENCOMPRA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PARSEAR ORDENCOMPRA": {
      "main": [
        [
          {
            "node": "Preparar Filas Excel OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Filas Excel OC": {
      "main": [
        [
          {
            "node": "Generar Excel OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generar Excel OC": {
      "main": [
        [
          {
            "node": "Combinar Excel y Contexto OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combinar Excel y Contexto OC": {
      "main": [
        [
          {
            "node": "Subir Excel Reportes OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Subir Excel Reportes OC": {
      "main": [
        [
          {
            "node": "Wait Tabla Reportes OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Tabla Reportes OC": {
      "main": [
        [
          {
            "node": "Crear Tabla Excel Reportes OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear Tabla Excel Reportes OC": {
      "main": [
        [
          {
            "node": "Formatear Montos Reportes OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Formatear Montos Reportes OC": {
      "main": [
        [
          {
            "node": "Ajustar Columnas Reportes OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ajustar Columnas Reportes OC": {
      "main": [
        [
          {
            "node": "Hipervincular URLs Reportes OC",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hipervincular URLs Reportes OC": {
      "main": [
        [
          {
            "node": "Unlock OrdenesCompra",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unlock OrdenesCompra": {
      "main": [
        [
          {
            "node": "Lock OrdenesCompra",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "timeSavedMode": "fixed",
    "timeSavedPerExecution": 1
  },
  "staticData": {
    "node:Microsoft OneDrive Trigger": {
      "LastLink": "https://graph.microsoft.com/v1.0/me/drive/root/delta?token=NDslMjM0OyUyMzE7MzsyYzQ2MzQ4ZS0wYmJjLTQ3ODAtODFhOC0zOGZiN2QyNDI1YmU7NjM5MTIzNzk2MTcxOTAwMDAwOzE4ODkzODIxMjM7JTIzOyUyMzslMjMwOyUyMw",
      "lastTimeChecked": "2026-04-21T14:46:57.002Z"
    },
    "global": {
      "ocPendingByKey": {},
      "ocLocks": {},
      "ocRecent": {
        "01HJAGCLXMMUBQB7YMYRGYUQ3NXJYPBPHV|oc 434465 bk.pdf|OrdenesCompra/2026-04/Semana 2/Banco Scotiabank/INBOX": 1775856281634
      },
      "ocNomByBatch": {
        "OrdenesCompra/2026-04/Semana 2/Banco Scotiabank||01HJAGCLTNP3Q356UDQZG3HB2P3ZOEEG5X:oc 430110 bk.pdf|01HJAGCLU2T2OGTRW2Q5A2L5IQK6H7JWLX:oc 430680 bk.pdf|01HJAGCLR3TRZU57OTNRC3FBQF5NUYUVXY:oc 434451 bk.pdf|01HJAGCLUCNHEINXT6O5HJ2PRZ6Q6XIGRJ:oc 434453 bk.pdf|01HJAGCLV7HXMLP3QVY5E266AGSYT6GKC7:oc 434463 bk.pdf|01HJAGCLVHPKGOSDNLMRFJVWZIUZKI5EVD:oc 434464 bk.pdf|01HJAGCLXMMUBQB7YMYRGYUQ3NXJYPBPHV:oc 434465 bk.pdf|01HJAGCLRQDZ6FKG6LKBBY5HF4H2IKLWM7:oc 435035 bk.pdf": {
          "nomFolderName": "NOM-001",
          "atMs": 1775856324308,
          "sourceBankPath": "OrdenesCompra/2026-04/Semana 2/Banco Scotiabank",
          "lotePdfSignature": "01HJAGCLTNP3Q356UDQZG3HB2P3ZOEEG5X:oc 430110 bk.pdf|01HJAGCLU2T2OGTRW2Q5A2L5IQK6H7JWLX:oc 430680 bk.pdf|01HJAGCLR3TRZU57OTNRC3FBQF5NUYUVXY:oc 434451 bk.pdf|01HJAGCLUCNHEINXT6O5HJ2PRZ6Q6XIGRJ:oc 434453 bk.pdf|01HJAGCLV7HXMLP3QVY5E266AGSYT6GKC7:oc 434463 bk.pdf|01HJAGCLVHPKGOSDNLMRFJVWZIUZKI5EVD:oc 434464 bk.pdf|01HJAGCLXMMUBQB7YMYRGYUQ3NXJYPBPHV:oc 434465 bk.pdf|01HJAGCLRQDZ6FKG6LKBBY5HF4H2IKLWM7:oc 435035 bk.pdf"
        }
      },
      "ocNomByBank": {
        "OrdenesCompra/2026-04/Semana 2/Banco Scotiabank": {
          "nomFolderName": "NOM-001",
          "atMs": 1775856324308
        }
      },
      "ocNomActiveByBank": {
        "OrdenesCompra/2026-04/Semana 2/Banco Scotiabank": {
          "nomFolderName": "NOM-001",
          "atMs": 1775856324308,
          "lotePdfSignature": "01HJAGCLTNP3Q356UDQZG3HB2P3ZOEEG5X:oc 430110 bk.pdf|01HJAGCLU2T2OGTRW2Q5A2L5IQK6H7JWLX:oc 430680 bk.pdf|01HJAGCLR3TRZU57OTNRC3FBQF5NUYUVXY:oc 434451 bk.pdf|01HJAGCLUCNHEINXT6O5HJ2PRZ6Q6XIGRJ:oc 434453 bk.pdf|01HJAGCLV7HXMLP3QVY5E266AGSYT6GKC7:oc 434463 bk.pdf|01HJAGCLVHPKGOSDNLMRFJVWZIUZKI5EVD:oc 434464 bk.pdf|01HJAGCLXMMUBQB7YMYRGYUQ3NXJYPBPHV:oc 434465 bk.pdf|01HJAGCLRQDZ6FKG6LKBBY5HF4H2IKLWM7:oc 435035 bk.pdf"
        }
      }
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "pinData": {},
  "versionId": "6cea6030-6284-4ea4-ab56-2c7d2428b46f",
  "activeVersionId": "6cea6030-6284-4ea4-ab56-2c7d2428b46f",
  "versionCounter": 349,
  "triggerCount": 1,
  "tags": [],
  "shared": [
    {
      "updatedAt": "2026-04-09T15:53:13.295Z",
      "createdAt": "2026-04-09T15:53:13.295Z",
      "role": "workflow:owner",
      "workflowId": "j5j1B9yc9seV1ZNt",
      "projectId": "W2XrXtMVi6WNLEGS",
      "project": {
        "updatedAt": "2026-04-09T15:46:35.950Z",
        "createdAt": "2026-04-09T15:37:33.410Z",
        "id": "W2XrXtMVi6WNLEGS",
        "name": "Alvaro  Mella <alvaro.mellaflores@gmail.com>",
        "type": "personal",
        "icon": null,
        "description": null,
        "creatorId": "e2235e6e-1014-4559-880b-d5293b8a90d8"
      }
    }
  ],
  "versionMetadata": {
    "name": null,
    "description": null
  }
}