{
  "updatedAt": "2026-04-14T16:35:29.516Z",
  "createdAt": "2026-04-09T15:50:36.014Z",
  "id": "UkFwQvEGGabIqcdN",
  "name": "MyE Facturas",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "watchFolder": true,
        "folderId": {
          "__rl": true,
          "value": "01HJAGCLUQXRUCYIQ4BZCK67ZJ2K5QPNDE",
          "mode": "id"
        },
        "options": {
          "folderChild": true
        }
      },
      "id": "822547d2-9365-4349-b604-094762d2c7f7",
      "name": "Microsoft OneDrive Trigger",
      "type": "n8n-nodes-base.microsoftOneDriveTrigger",
      "typeVersion": 1,
      "position": [
        -4000,
        272
      ],
      "credentials": {
        "microsoftOneDriveOAuth2Api": {
          "id": "KGZfdefsCF8ngR3q",
          "name": "Microsoft Drive account"
        }
      }
    },
    {
      "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\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 facturas debe tener formato Facturas/AAAA-MM/Semana X/Banco X/INBOX.');\n  }\n\n  const [sourceRoot, yearMonth, weekFolder, bankFolder] = parts;\n\n  if (sourceRoot.toLowerCase() !== 'facturas') 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    facturaInboxKey: inboxPath,\n  };\n}\n\nconst sd = $getWorkflowStaticData('global');\nconst now = Date.now();\nconst PENDING_TTL_MS = 6 * 60 * 60 * 1000;\nconst LOCK_TIMEOUT_MS = 5 * 60 * 1000;\nlet matchedCurrentItems = 0;\nconst currentBatchByKey = {};\nif (!sd.facturasPendingByKey || typeof sd.facturasPendingByKey !== 'object') sd.facturasPendingByKey = {};\nif (!sd.facturasLocks || typeof sd.facturasLocks !== 'object') sd.facturasLocks = {};\n\nfor (const [key, value] of Object.entries(sd.facturasPendingByKey)) {\n  const ts = Number(value?.lastEventTs || value?.queuedAtMs || 0);\n  if (!Number.isFinite(ts) || (now - ts) > PENDING_TTL_MS) delete sd.facturasPendingByKey[key];\n}\nfor (const [key, ts] of Object.entries(sd.facturasLocks)) {\n  if (!Number.isFinite(ts) || (now - ts) > LOCK_TIMEOUT_MS) delete sd.facturasLocks[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.facturasPendingByKey[meta.facturaInboxKey] || {};\n  sd.facturasPendingByKey[meta.facturaInboxKey] = {\n    ...existing,\n    ...meta,\n    lastEventTs: itemTs(source),\n    queuedAtMs: now,\n  };\n  currentBatchByKey[meta.facturaInboxKey] = {\n    ...sd.facturasPendingByKey[meta.facturaInboxKey],\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.facturasPendingByKey)\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.facturasLocks[lockKey];\n});\nconst selected = selectUnlocked(currentBatchCandidates) || selectUnlocked(candidates);\n\nif (!selected) return [];\n\nreturn [{\n  json: {\n    ...selected,\n  }\n}];"
      },
      "id": "13e7f3b0-c86e-470d-a87a-bdb3712442be",
      "name": "Filtrar Evento Factura",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -3760,
        272
      ]
    },
    {
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nconst now = Date.now();\nconst LOCK_TIMEOUT_MS = 5 * 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.facturasLocks || typeof sd.facturasLocks !== 'object') sd.facturasLocks = {};\nif (!sd.facturasRecent || typeof sd.facturasRecent !== 'object') sd.facturasRecent = {};\n\nfor (const [key, ts] of Object.entries(sd.facturasLocks)) {\n  if (!Number.isFinite(ts) || (now - ts) > LOCK_TIMEOUT_MS) delete sd.facturasLocks[key];\n}\nfor (const [key, ts] of Object.entries(sd.facturasRecent)) {\n  if (!Number.isFinite(ts) || (now - ts) > RECENT_TTL_MS) delete sd.facturasRecent[key];\n}\n\nif (!inboxPath) return [];\nif (eventKey !== '||' && sd.facturasRecent[eventKey]) return [];\nif (sd.facturasLocks[inboxPath]) {\n  if (eventKey !== '||') sd.facturasRecent[eventKey] = now;\n  return [];\n}\n\nsd.facturasLocks[inboxPath] = now;\nif (eventKey !== '||') sd.facturasRecent[eventKey] = now;\n\nreturn [{\n  json: {\n    ...$json,\n    lockKey: inboxPath,\n    loteStartedAtMs: now,\n    loteRetryCount: 0,\n  }\n}];"
      },
      "id": "e1f3805d-03f3-434b-b7aa-a248dd3b110e",
      "name": "Lock Facturas",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -3520,
        272
      ]
    },
    {
      "parameters": {
        "amount": 5,
        "unit": "seconds"
      },
      "id": "b38c3eb4-8753-40f1-bc13-f978201d3772",
      "webhookId": "59d56f2a-2ac9-4c30-b5fe-0d9e2f0ce2f1",
      "name": "Wait Lote Facturas",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        -3280,
        272
      ]
    },
    {
      "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"
            }
          }
        }
      },
      "id": "b9fd31f5-7a9b-4e26-bbd1-8117a812e407",
      "name": "Listar Archivos Inbox",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -3040,
        272
      ],
      "retryOnFail": true,
      "maxTries": 4,
      "waitBetweenTries": 4000,
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "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 Facturas');\nconst initialSource = readNodeJson('Wait Lote Facturas');\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 = 15 * 1000;\nconst MAX_BATCH_WAIT_MS = 30 * 1000;\nconst MAX_RETRIES = 6;\nconst retryCount = Number(source.loteRetryCount || 0);\nconst forceContinue = retryCount >= MAX_RETRIES;\nconst loteFacturasEstable = 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    loteFacturasEstable,\n    loteForceContinue: forceContinue,\n  }\n}];"
      },
      "id": "f36cffc8-b549-4f7c-8317-c5a575f40c09",
      "name": "Evaluar Lote Facturas",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2800,
        272
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "leftValue": "={{$json.loteFacturasEstable === true}}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "5db98cd2-9cec-4c7c-8172-8ce27be20008",
      "name": "IF Lote Facturas Estable",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -2560,
        272
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{\n  json: {\n    ...$json,\n    loteRetryCount: Number($json.loteRetryCount || 0) + 1,\n  }\n}];"
      },
      "id": "5f6bded9-dba1-49d7-abd3-ff63098f8aaa",
      "name": "Preparar Reintento Facturas",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2320,
        400
      ]
    },
    {
      "parameters": {
        "amount": 5,
        "unit": "seconds"
      },
      "id": "2c1ae4f4-7cfe-4ac5-a8cb-cd580df33417",
      "webhookId": "09e4d956-bf0f-42c5-8507-53e5bfcf155d",
      "name": "Wait Reintento Facturas",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        -2080,
        400
      ]
    },
    {
      "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"
            }
          }
        }
      },
      "id": "1cb43c80-d459-4982-b62f-9d88a9fc2c8c",
      "name": "Listar Carpetas Banco Facturas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -2320,
        176
      ],
      "retryOnFail": true,
      "maxTries": 4,
      "waitBetweenTries": 4000,
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "function encodePath(path) {\n  return String(path || '').split('/').filter(Boolean).map(encodeURIComponent).join('/');\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 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\nconst sd = $getWorkflowStaticData('global');\nconst now = Date.now();\nconst BATCH_REUSE_TTL_MS = 6 * 60 * 60 * 1000;\nconst EMPTY_BATCH_BANK_REUSE_TTL_MS = 30 * 60 * 1000;\nconst ACTIVE_BANK_LOCK_TTL_MS = 10 * 60 * 1000;\nconst pairIdx = resolvePairIndex(items[0] || {});\nconst lot = (($items('Evaluar Lote Facturas')[pairIdx] || {}).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\nif (!sd.facturasNomByBatch || typeof sd.facturasNomByBatch !== 'object') sd.facturasNomByBatch = {};\nif (!sd.facturasNomByBank || typeof sd.facturasNomByBank !== 'object') sd.facturasNomByBank = {};\nif (!sd.facturasNomActiveByBank || typeof sd.facturasNomActiveByBank !== 'object') sd.facturasNomActiveByBank = {};\nif (!sd.facturasLocks || typeof sd.facturasLocks !== 'object') sd.facturasLocks = {};\nfor (const [key, value] of Object.entries(sd.facturasNomByBatch)) {\n  const ts = Number(value?.atMs || 0);\n  if (!Number.isFinite(ts) || (now - ts) > BATCH_REUSE_TTL_MS) delete sd.facturasNomByBatch[key];\n}\nfor (const [key, value] of Object.entries(sd.facturasNomByBank)) {\n  const ts = Number(value?.atMs || 0);\n  if (!Number.isFinite(ts) || (now - ts) > EMPTY_BATCH_BANK_REUSE_TTL_MS) delete sd.facturasNomByBank[key];\n}\nfor (const [key, value] of Object.entries(sd.facturasNomActiveByBank)) {\n  const ts = Number(value?.atMs || 0);\n  if (!Number.isFinite(ts) || (now - ts) > ACTIVE_BANK_LOCK_TTL_MS) delete sd.facturasNomActiveByBank[key];\n}\n\nconst sourceBankPath = String(lot.sourceBankPath || '').trim();\nconst lotePdfSignature = String(lot.lotePdfSignature || '').trim();\nconst batchKey = [sourceBankPath, lotePdfSignature].join('||');\nconst cachedBatch = sd.facturasNomByBatch[batchKey];\nconst cachedNomFolderName = String(cachedBatch?.nomFolderName || '').trim().toUpperCase();\nconst nomFolderName = cachedNomFolderName || buildNomFolderName('FACT', now);\n\nsd.facturasNomByBatch[batchKey] = {\n  nomFolderName,\n  atMs: now,\n  sourceBankPath,\n  lotePdfSignature,\n};\nsd.facturasNomByBank[sourceBankPath] = {\n  nomFolderName,\n  atMs: now,\n};\nsd.facturasNomActiveByBank[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": "1094a933-115f-4672-bf03-2aa44323fc3d",
      "name": "Preparar NOM Facturas",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2080,
        176
      ]
    },
    {
      "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": {}
      },
      "id": "9203b8a8-cda3-4071-819e-a67a760cb8e4",
      "name": "Crear NOM Facturas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -1840,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$(\"Preparar NOM Facturas\").item.json.sourceNomPathEncoded}}?$select=id,name,webUrl,parentReference",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "730ef327-16e9-4297-8b68-8e8e66f590bd",
      "name": "Obtener NOM Facturas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -1600,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$(\"Preparar NOM Facturas\").item.json.nominasBankPathEncoded}}:/children",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{({\"name\": $(\"Preparar NOM Facturas\").item.json.nomFolderName, \"folder\": {}, \"@microsoft.graph.conflictBehavior\": \"fail\"})}}",
        "options": {}
      },
      "id": "89af39fb-83b7-4213-8577-b59d3317e78e",
      "name": "Crear NOM Nominas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -1360,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$(\"Preparar NOM Facturas\").item.json.nominasNomPathEncoded}}?$select=id,name,webUrl,parentReference",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "ba28aa36-9828-4e1c-8421-92304223f8ab",
      "name": "Obtener NOM Nominas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -1120,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$(\"Preparar NOM Facturas\").item.json.reportesBankPathEncoded}}:/children",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{({\"name\": $(\"Preparar NOM Facturas\").item.json.nomFolderName, \"folder\": {}, \"@microsoft.graph.conflictBehavior\": \"fail\"})}}",
        "options": {}
      },
      "id": "de4df742-edde-43ee-b059-0d12e1b3be5a",
      "name": "Crear NOM Reportes",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -880,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$(\"Preparar NOM Facturas\").item.json.reportesNomPathEncoded}}?$select=id,name,webUrl,parentReference",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "2b2aa0f6-ad1d-456f-bc24-5e6f5042c42d",
      "name": "Obtener NOM Reportes",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -640,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "function 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 0;\n}\n\nconst pairIdx = resolvePairIndex(items[0] || {});\nconst prep = (($items('Preparar NOM Facturas')[pairIdx] || {}).json || {});\nconst sourceNom = (($items('Obtener NOM Facturas')[pairIdx] || {}).json || {});\nconst nominasNom = (($items('Obtener NOM Nominas')[pairIdx] || {}).json || {});\nconst reportesNom = (($items('Obtener NOM Reportes')[pairIdx] || {}).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    pdfWebUrl: String(pdf.webUrl || ''),\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": "856eef26-f283-42dc-90ad-3f30b37272a4",
      "name": "Preparar Movimientos Facturas",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -400,
        176
      ]
    },
    {
      "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": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "a1113e7a-a7f9-4eaf-a690-b19cd237a981",
      "name": "Mover PDF a NOM Facturas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -160,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "name": "pdf_webUrl",
              "value": "={{$(\"Mover PDF a NOM Facturas\").item.json.webUrl || $(\"Preparar Movimientos Facturas\").item.json.pdfWebUrl}}",
              "type": "string"
            },
            {
              "name": "pdf_id",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.pdfId}}",
              "type": "string"
            },
            {
              "name": "pdf_name",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.pdfName}}",
              "type": "string"
            },
            {
              "name": "sourceNomPathEncoded",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.sourceNomPathEncoded}}",
              "type": "string"
            },
            {
              "name": "reportesNomPathEncoded",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.reportesNomPathEncoded}}",
              "type": "string"
            },
            {
              "name": "nominasNomWebUrl",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.nominasNomWebUrl}}",
              "type": "string"
            },
            {
              "name": "reportesNomWebUrl",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.reportesNomWebUrl}}",
              "type": "string"
            },
            {
              "name": "yearMonth",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.yearMonth}}",
              "type": "string"
            },
            {
              "name": "weekFolder",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.weekFolder}}",
              "type": "string"
            },
            {
              "name": "bankFolder",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.bankFolder}}",
              "type": "string"
            },
            {
              "name": "nomFolderName",
              "value": "={{$(\"Preparar Movimientos Facturas\").item.json.nomFolderName}}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "d35ba010-a077-4db1-83ba-03b211786bcf",
      "name": "Links",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        80,
        64
      ]
    },
    {
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$(\"Preparar Movimientos Facturas\").item.json.pdfId}}/content",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 5,
              "batchInterval": 400
            }
          },
          "response": {
            "response": {
              "responseFormat": "file"
            }
          },
          "timeout": 180000
        }
      },
      "id": "fc5bf3df-87cc-4b25-b9db-114eddc21cfa",
      "name": "Descargar Factura",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        80,
        400
      ],
      "retryOnFail": true,
      "maxTries": 4,
      "waitBetweenTries": 5000,
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "45ce5b04-50a2-472e-88ee-14eae2bcbe1e",
      "name": "Combinacion Links y PDF",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        432,
        176
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/files",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpBearerAuth",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "name": "purpose",
              "value": "user_data"
            },
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "data"
            }
          ]
        },
        "options": {
          "batching": {
            "batch": {
              "batchSize": 4,
              "batchInterval": 250
            }
          },
          "timeout": 180000
        }
      },
      "id": "2b7c9f91-692d-48f4-9f2b-4aa9ca47569f",
      "name": "Envio a OPENAI",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        608,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "credentials": {
        "httpBearerAuth": {
          "id": "s7yN4svPDjFhA59v",
          "name": "Bearer Auth account"
        }
      }
    },
    {
      "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 uploadedFileId = String($json.file_id ?? $json.id ?? '').trim();\n  return {\n    model: 'gpt-5.4-mini',\n    max_output_tokens: 1200,\n    reasoning: {\n      effort: 'low'\n    },\n    input: [\n      {\n        role: 'user',\n        content: [\n          {\n            type: 'input_file',\n            file_id: uploadedFileId\n          },\n          {\n            type: 'input_text',\n            text: [\n              'Eres un asistente experto en extracción de datos de facturas chilenas.',\n              '',\n              'Debes analizar directamente el PDF completo, incluyendo su estructura visual y su contenido.',\n              'No dependas de texto previamente extraído.',\n              '',\n              'OBJETIVO:',\n              '- Extraer SOLO los datos del EMISOR o PROVEEDOR de la factura.',\n              '- NO extraer datos del RECEPTOR, CLIENTE, COMPRADOR, SEÑOR(ES) o FACTURAR A.',\n              '',\n              'REGLAS CRÍTICAS:',\n              '- Si hay múltiples RUT o múltiples empresas, debes distinguir EMISOR vs RECEPTOR.',\n              '- El proveedor es quien EMITE tributariamente la factura.',\n              '- El emisor/proveedor suele estar en el encabezado superior del documento y cerca de textos como: R.U.T., FACTURA ELECTRÓNICA, S.I.I., GIRO, DIRECCIÓN, CASA MATRIZ.',\n              '- El receptor/cliente suele aparecer en bloques como: SEÑOR(ES), CLIENTE, RECEPTOR, FACTURAR A, RUT RECEPTOR.',\n              '- JAMÁS uses como proveedor al receptor o cliente.',\n              '- Si aparece \"Maritano y Ebensperger Ltda.\" o el RUT \"86.519.000-K\" o \"86519000-K\" en calidad de receptor, cliente, señor(es) o facturar a, NO lo uses como proveedor.',\n              '- Si no puedes identificar con suficiente certeza al emisor, usa null.',\n              '- No inventes datos.',\n              '- Si el documento no es una factura, devuelve todos los campos en null.',\n              '',\n              'NÚMERO DE FACTURA:',\n              '- Extrae el número real de la factura tributaria.',\n              '- No confundas el número de factura con códigos internos, códigos de producto, números de motor, números de pedido, números de referencia o códigos de ítems.',\n              '',\n              'MONTOS:',\n              '- monto_total: total final a pagar.',\n              '- monto_neto: monto sin IVA.',\n              '- monto_iva: IVA.',\n              '- Si existe monto_total y el documento es afecto a IVA 19%, puedes inferir:',\n              '  monto_neto = round(monto_total / 1.19)',\n              '  monto_iva = monto_total - monto_neto',\n              '- Si el documento es exento o no afecto, no inventes IVA.',\n              '',\n              'FORMATO:',\n              '- fechas en YYYY-MM-DD',\n              '- montos enteros CLP sin puntos ni comas',\n              '- rut con guión verificador',\n              '',\n              'VALIDACIÓN OBLIGATORIA ANTES DE RESPONDER:',\n              '- Verifica que rut_proveedor y nombre_proveedor pertenezcan al EMISOR y NO al RECEPTOR.',\n              '- Si detectas que rut_proveedor corresponde al receptor, corrige antes de responder.',\n              '- Si no puedes corregir con certeza, usa null en rut_proveedor y nombre_proveedor.',\n              '- Revisa que numero_factura sea efectivamente el folio o número de factura, no un código de detalle.',\n              '',\n              'SALIDA:',\n              '- Devuelve SOLO un JSON válido con estos campos exactos:',\n              'rut_proveedor, nombre_proveedor, numero_factura, fecha_emision, fecha_vencimiento, monto_neto, monto_iva, monto_total'\n            ].join('\\n')\n          }\n        ]\n      }\n    ],\n    text: {\n      format: {\n        type: 'json_schema',\n        name: 'factura_proveedor',\n        schema: {\n          type: 'object',\n          properties: {\n            rut_proveedor: { type: ['string', 'null'] },\n            nombre_proveedor: { type: ['string', 'null'] },\n            numero_factura: { type: ['string', 'null'] },\n            fecha_emision: { type: ['string', 'null'] },\n            fecha_vencimiento: { type: ['string', 'null'] },\n            monto_neto: { type: ['number', 'null'] },\n            monto_iva: { type: ['number', 'null'] },\n            monto_total: { type: ['number', 'null'] }\n          },\n          required: [\n            'rut_proveedor',\n            'nombre_proveedor',\n            'numero_factura',\n            'fecha_emision',\n            'fecha_vencimiento',\n            'monto_neto',\n            'monto_iva',\n            'monto_total'\n          ],\n          additionalProperties: false\n        },\n        strict: true\n      }\n    }\n  };\n})() }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 4,
              "batchInterval": 250
            }
          },
          "timeout": 180000
        }
      },
      "id": "63308b57-5c92-4075-af41-51cd48f074c6",
      "name": "IA ANALIZA FACTURA",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        832,
        48
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "credentials": {
        "httpBearerAuth": {
          "id": "s7yN4svPDjFhA59v",
          "name": "Bearer Auth account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const sourceItems = $items('Combinacion Links y PDF');\n\nfunction cleanText(value) {\n  return (value ?? '').toString().trim();\n}\n\nfunction normalizeSpaces(value) {\n  return cleanText(value).replace(/\\s+/g, ' ');\n}\n\nfunction normalizeName(value) {\n  return normalizeSpaces(value)\n    .toLowerCase()\n    .normalize('NFD')\n    .replace(/[\\u0300-\\u036f]/g, '')\n    .replace(/[^a-z0-9\\s]/g, '')\n    .replace(/\\s+/g, ' ');\n}\n\nfunction normalizeRut(value) {\n  const rut = cleanText(value).toUpperCase().replace(/[^0-9K]/g, '');\n  if (!rut || rut.length < 2) return '';\n  return rut;\n}\n\nfunction extractRut(text) {\n  const raw = cleanText(text);\n  if (!raw) return null;\n  const match = raw.match(/([0-9]{1,2}[.]?[0-9]{3}[.]?[0-9]{3}-?[0-9Kk])/);\n  return match ? normalizeSpaces(match[1]) : null;\n}\n\nfunction hasReceiverSignals(text) {\n  return /(cliente|receptor|señor\\(es\\)|senor\\(es\\)|facturar a|comprador|adquirente|deudor|rut receptor)/i.test(text);\n}\n\nfunction hasIssuerSignals(text) {\n  return /(factura|factura electr[oó]nica|s[.]?i[.]?i[.]?|giro|casa matriz|sucursal|emisor)/i.test(text);\n}\n\nfunction extractReceiverSignals(pdfText) {\n  const lines = cleanText(pdfText).replace(/\\r/g, '\\n').split('\\n').map(normalizeSpaces).filter(Boolean);\n  const labelRx = /(cliente|receptor|señor\\(es\\)|senor\\(es\\)|facturar a|comprador|adquirente|deudor)/i;\n  const out = { name: null, rut: null };\n\n  for (let i = 0; i < lines.length; i += 1) {\n    const line = lines[i];\n    if (!labelRx.test(line)) continue;\n\n    if (!out.rut) {\n      const sameRut = extractRut(line);\n      if (sameRut) out.rut = sameRut;\n    }\n\n    if (!out.name) {\n      const sameLineName = normalizeSpaces(line.replace(labelRx, '').replace(/[:\\-]/g, ' '));\n      if (sameLineName && !extractRut(sameLineName) && sameLineName.length > 4) out.name = sameLineName;\n    }\n\n    for (const offset of [1, 2]) {\n      const next = lines[i + offset] || '';\n      if (!next) continue;\n      if (!out.rut) {\n        const nextRut = extractRut(next);\n        if (nextRut) out.rut = nextRut;\n      }\n      if (!out.name && !extractRut(next) && next.length > 4 && next.length < 100) {\n        out.name = next;\n      }\n    }\n\n    if (out.name && out.rut) break;\n  }\n\n  return out;\n}\n\nfunction inferIssuerFromHeader(pdfText) {\n  const lines = cleanText(pdfText).replace(/\\r/g, '\\n').split('\\n').map(normalizeSpaces).filter(Boolean).slice(0, 120);\n  let best = { score: -999, name: null, rut: null };\n\n  for (let i = 0; i < lines.length; i += 1) {\n    const line = lines[i];\n    const rut = extractRut(line);\n    if (!rut) continue;\n\n    const from = Math.max(0, i - 2);\n    const to = Math.min(lines.length, i + 3);\n    const ctx = lines.slice(from, to).join(' | ');\n    let score = 0;\n    if (hasIssuerSignals(ctx)) score += 4;\n    if (hasReceiverSignals(ctx)) score -= 8;\n\n    let name = null;\n    for (const offset of [-1, 1, -2, 2]) {\n      const candidate = lines[i + offset] || '';\n      if (!candidate) continue;\n      if (extractRut(candidate)) continue;\n      if (hasReceiverSignals(candidate)) continue;\n      if (/(folio|fecha|vencimiento|total|iva|neto|factura|s[.]?i[.]?i[.]?)/i.test(candidate)) continue;\n      if (candidate.length < 4 || candidate.length > 100) continue;\n      name = candidate;\n      break;\n    }\n\n    if (name) score += 3;\n    if (score > best.score) best = { score, name, rut };\n  }\n\n  if (best.score < 1) return { name: null, rut: null };\n  return {\n    name: best.name ? normalizeSpaces(best.name) : null,\n    rut: best.rut ? normalizeSpaces(best.rut) : null,\n  };\n}\n\nfunction isInvalidProviderName(name, receiverName) {\n  const raw = cleanText(name);\n  if (!raw) return true;\n  if (/(maritano|ebensperger)/i.test(raw)) return true;\n  if (hasReceiverSignals(raw)) return true;\n  if (receiverName && normalizeName(raw) === normalizeName(receiverName)) return true;\n  return false;\n}\n\nfunction isInvalidProviderRut(rut, receiverRut) {\n  const nRut = normalizeRut(rut);\n  if (!nRut) return true;\n  if (receiverRut && nRut === normalizeRut(receiverRut)) return true;\n  return false;\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 extractFacturaObject(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 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 rawPdfText = String(src.text ?? src.pdf_text ?? '').trim();\n\n  const rawText =\n        item.json.output?.find(o => o.type === 'message')?.content?.[0]?.text\n        || item.json.output_text\n        || item.json.response?.output_text\n        || '';\n  const factura = extractFacturaObject(rawText);\n\n  const receiver = extractReceiverSignals(rawPdfText);\n  const headerIssuer = inferIssuerFromHeader(rawPdfText);\n\n  let providerName = factura.nombre_proveedor ?? null;\n  let providerRut = factura.rut_proveedor ?? null;\n\n  if (isInvalidProviderName(providerName, receiver.name) && headerIssuer.name) {\n    providerName = headerIssuer.name;\n  }\n  if (isInvalidProviderRut(providerRut, receiver.rut) && headerIssuer.rut) {\n    providerRut = headerIssuer.rut;\n  }\n  if (isInvalidProviderName(providerName, receiver.name)) {\n    providerName = null;\n  }\n  if (isInvalidProviderRut(providerRut, receiver.rut)) {\n    providerRut = null;\n  }\n\n  let neto = factura.monto_neto ?? null;\n  let iva = factura.monto_iva ?? null;\n  let total = factura.monto_total ?? null;\n\n  if (total && (!neto || !iva)) {\n    const calcNeto = Math.round(total / 1.19);\n    const calcIva = total - calcNeto;\n    neto = neto ?? calcNeto;\n    iva = iva ?? calcIva;\n  }\n  \n  return {\n    json: {\n      tipo_documento: 'FACTURA',\n      rut_proveedor: providerRut,\n      nombre_proveedor: providerName,\n      numero_factura: factura.numero_factura ?? null,\n      numero_oc: '',\n      fecha_documento: factura.fecha_emision ?? null,\n      fecha_vencimiento: factura.fecha_vencimiento ?? null,\n      monto_neto: neto,\n      monto_iva: iva,\n      monto_total: 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": "fde580dc-07b5-418f-8a47-cb7b73d158af",
      "name": "PARSEAR FACTURA",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        176
      ]
    },
    {
      "parameters": {
        "jsCode": "return items.map((item) => ({\n  json: {\n    tipo_documento: item.json.tipo_documento || 'FACTURA',\n    rut_proveedor: item.json.rut_proveedor || '',\n    nombre_proveedor: item.json.nombre_proveedor || '',\n    numero_factura: item.json.numero_factura || '',\n    numero_oc: '',\n    fecha_documento: item.json.fecha_documento || '',\n    fecha_vencimiento: item.json.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": "988e8154-4aff-483f-94e8-b592ec388b4d",
      "name": "Preparar Filas Excel Facturas",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1280,
        176
      ]
    },
    {
      "parameters": {
        "operation": "toFile",
        "fileFormat": "xlsx",
        "options": {
          "fileName": "facturas_semana.xlsx",
          "headerRow": true,
          "sheetName": "Facturas"
        }
      },
      "id": "de0ea377-46e8-4cd0-b445-5a5dc4c89621",
      "name": "Generar Excel Facturas",
      "type": "n8n-nodes-base.spreadsheetFile",
      "typeVersion": 2,
      "position": [
        1520,
        176
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "id": "3891be14-0a7b-4a18-ad56-852556508b84",
      "name": "Combinar Excel y Contexto Facturas",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        1760,
        176
      ]
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$(\"Obtener NOM Reportes\").item.json.id}}:/facturas_semana.xlsx:/content",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "sendBody": true,
        "contentType": "binaryData",
        "inputDataFieldName": "data",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "id": "c8ea1a73-ff5d-4119-883f-9fe84072e79d",
      "name": "Subir Excel Reportes Facturas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2000,
        176
      ],
      "retryOnFail": true,
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      }
    },
    {
      "parameters": {
        "amount": 5,
        "unit": "seconds"
      },
      "id": "74df3459-705e-4d6d-80a9-1e2717fc4200",
      "webhookId": "4b1cd83e-fc7c-4d7f-a276-90b7fbf4907e",
      "name": "Wait Tabla Reportes Facturas",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        2208,
        176
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$(\"Subir Excel Reportes Facturas\").item.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 Facturas\").length + 1), hasHeaders: true })}}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "id": "506149bd-ab86-461f-99e0-9d7ce466214e",
      "name": "Crear Tabla Excel Reportes Facturas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2400,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$(\"Subir Excel Reportes Facturas\").item.json.id}}/workbook/worksheets/Facturas/range(address='H2:J{{$items(\"Preparar Filas Excel Facturas\").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 Facturas\").length }, () => [\"[$$-es-CL] #,##0\", \"[$$-es-CL] #,##0\", \"[$$-es-CL] #,##0\"]) })}}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "id": "d7486a32-2f75-450d-9d19-c30269e6cd89",
      "name": "Formatear Montos Reportes Facturas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2608,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$(\"Subir Excel Reportes Facturas\").item.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
        }
      },
      "id": "78033f5f-0419-46b2-910b-87bebd310c92",
      "name": "Ajustar Columnas Reportes Facturas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        2800,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$(\"Subir Excel Reportes Facturas\").item.json.id}}/workbook/worksheets/Facturas/range(address='K2:K{{$items(\"Preparar Filas Excel Facturas\").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 Facturas\").length }, (_, idx) => { const row = ($items(\"Preparar Filas Excel Facturas\")[idx] || {}).json || {}; const rawUrl = String(row.url_documento || \"\").replace(/\"/g, '\"\"'); return [rawUrl ? `=HYPERLINK(\"${rawUrl}\",\"Abrir PDF\")` : null]; }) }) }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "id": "805ca5ba-1597-4a6b-81e5-ca79359ac4c5",
      "name": "Hipervincular URLs Reportes Facturas",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        3008,
        176
      ],
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "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.facturasPendingByKey || typeof sd.facturasPendingByKey !== 'object') sd.facturasPendingByKey = {};\n  if (!sd.facturasLocks || typeof sd.facturasLocks !== 'object') sd.facturasLocks = {};\n  if (!sd.facturasNomActiveByBank || typeof sd.facturasNomActiveByBank !== 'object') sd.facturasNomActiveByBank = {};\n  for (const [key, value] of Object.entries(sd.facturasPendingByKey)) {\n    const ts = Number(value?.lastEventTs || value?.queuedAtMs || 0);\n    if (!Number.isFinite(ts) || (now - ts) > PENDING_TTL_MS) delete sd.facturasPendingByKey[key];\n  }\n  for (const [key, ts] of Object.entries(sd.facturasLocks)) {\n    if (!Number.isFinite(ts) || (now - ts) > LOCK_TIMEOUT_MS) delete sd.facturasLocks[key];\n  }\n  const candidates = Object.entries(sd.facturasPendingByKey)\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.facturasLocks[lockKey];\n  });\n  return selected ? [{ json: { ...selected } }] : [];\n}\nconst current = $json || {};\nconst lockNode = readNodeJson('Lock Facturas');\nconst key = String(current.inboxPath || '').trim() || String(lockNode.lockKey || '').trim();\nconst pendingKey = String(current.facturaInboxKey || key).trim();\nconst sourceBankPath = String(current.sourceBankPath || '').trim();\nif (sd.facturasLocks && key && sd.facturasLocks[key]) delete sd.facturasLocks[key];\nif (sd.facturasPendingByKey && pendingKey && sd.facturasPendingByKey[pendingKey]) delete sd.facturasPendingByKey[pendingKey];\nif (sd.facturasNomActiveByBank && sourceBankPath && sd.facturasNomActiveByBank[sourceBankPath]) delete sd.facturasNomActiveByBank[sourceBankPath];\nreturn getNextPending(pendingKey);"
      },
      "id": "8dc1ee0f-db00-4b77-af96-8bda6af92f9f",
      "name": "Unlock Facturas",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3216,
        176
      ]
    },
    {
      "parameters": {
        "operation": "pdf",
        "options": {}
      },
      "id": "c3836ad6-8a79-4922-b5fc-4f70b57ad378",
      "name": "Extraer Texto PDF",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        512,
        464
      ]
    }
  ],
  "connections": {
    "Microsoft OneDrive Trigger": {
      "main": [
        [
          {
            "node": "Filtrar Evento Factura",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filtrar Evento Factura": {
      "main": [
        [
          {
            "node": "Lock Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lock Facturas": {
      "main": [
        [
          {
            "node": "Wait Lote Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Lote Facturas": {
      "main": [
        [
          {
            "node": "Listar Archivos Inbox",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Listar Archivos Inbox": {
      "main": [
        [
          {
            "node": "Evaluar Lote Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluar Lote Facturas": {
      "main": [
        [
          {
            "node": "IF Lote Facturas Estable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Lote Facturas Estable": {
      "main": [
        [
          {
            "node": "Listar Carpetas Banco Facturas",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Preparar Reintento Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Reintento Facturas": {
      "main": [
        [
          {
            "node": "Wait Reintento Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Reintento Facturas": {
      "main": [
        [
          {
            "node": "Listar Archivos Inbox",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Listar Carpetas Banco Facturas": {
      "main": [
        [
          {
            "node": "Preparar NOM Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar NOM Facturas": {
      "main": [
        [
          {
            "node": "Crear NOM Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear NOM Facturas": {
      "main": [
        [
          {
            "node": "Obtener NOM Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obtener NOM Facturas": {
      "main": [
        [
          {
            "node": "Crear NOM Nominas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear NOM Nominas": {
      "main": [
        [
          {
            "node": "Obtener NOM Nominas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obtener NOM Nominas": {
      "main": [
        [
          {
            "node": "Crear NOM Reportes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear NOM Reportes": {
      "main": [
        [
          {
            "node": "Obtener NOM Reportes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obtener NOM Reportes": {
      "main": [
        [
          {
            "node": "Preparar Movimientos Facturas",
            "type": "main",
            "index": 0
          },
          {
            "node": "Combinar Excel y Contexto Facturas",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Preparar Movimientos Facturas": {
      "main": [
        [
          {
            "node": "Mover PDF a NOM Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mover PDF a NOM Facturas": {
      "main": [
        [
          {
            "node": "Links",
            "type": "main",
            "index": 0
          },
          {
            "node": "Descargar Factura",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Descargar Factura": {
      "main": [
        [
          {
            "node": "Combinacion Links y PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Links": {
      "main": [
        [
          {
            "node": "Combinacion Links y PDF",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Combinacion Links y PDF": {
      "main": [
        [
          {
            "node": "Envio a OPENAI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IA ANALIZA FACTURA": {
      "main": [
        [
          {
            "node": "PARSEAR FACTURA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PARSEAR FACTURA": {
      "main": [
        [
          {
            "node": "Preparar Filas Excel Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Filas Excel Facturas": {
      "main": [
        [
          {
            "node": "Generar Excel Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generar Excel Facturas": {
      "main": [
        [
          {
            "node": "Combinar Excel y Contexto Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combinar Excel y Contexto Facturas": {
      "main": [
        [
          {
            "node": "Subir Excel Reportes Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Subir Excel Reportes Facturas": {
      "main": [
        [
          {
            "node": "Wait Tabla Reportes Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Tabla Reportes Facturas": {
      "main": [
        [
          {
            "node": "Crear Tabla Excel Reportes Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crear Tabla Excel Reportes Facturas": {
      "main": [
        [
          {
            "node": "Formatear Montos Reportes Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Formatear Montos Reportes Facturas": {
      "main": [
        [
          {
            "node": "Ajustar Columnas Reportes Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ajustar Columnas Reportes Facturas": {
      "main": [
        [
          {
            "node": "Hipervincular URLs Reportes Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hipervincular URLs Reportes Facturas": {
      "main": [
        [
          {
            "node": "Unlock Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unlock Facturas": {
      "main": [
        [
          {
            "node": "Lock Facturas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Envio a OPENAI": {
      "main": [
        [
          {
            "node": "IA ANALIZA FACTURA",
            "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=NDslMjM0OyUyMzE7MzsyYzQ2MzQ4ZS0wYmJjLTQ3ODAtODFhOC0zOGZiN2QyNDI1YmU7NjM5MTIzNzk1MDk3MjAwMDAwOzE4ODkzODE4MTc7JTIzOyUyMzslMjMwOyUyMw",
      "lastTimeChecked": "2026-04-21T14:45:10.002Z"
    },
    "global": {
      "facturasPendingByKey": {},
      "facturasLocks": {},
      "facturasRecent": {
        "01HJAGCLW5NWJMTMZ7RVH3LDC4ZZV4OCZG|867405003 f1736695.pdf|Facturas/2026-04/Semana 4/Banco de Chile/INBOX": 1776782711159
      },
      "facturasNomByBatch": {
        "Facturas/2026-04/Semana 4/Banco de Chile||01HJAGCLRBD4ZPOYKSXZAYUSOA6TQGLJVT:867405003 f1736600.pdf|01HJAGCLRKUAWPIJAJPFB2LKUJAMP4BSBR:867405003 f1736602.pdf|01HJAGCLXGEZ7WTKSCABHK5EBIGP6QCCUT:867405003 f1736613.pdf|01HJAGCLRB4UGZOUPA2ZGLS6ZHPDDCHYMB:867405003 f1736614.pdf|01HJAGCLS2LHRKDD3NFBE3MJ76DUGPIGWE:867405003 f1736680.pdf|01HJAGCLW5NWJMTMZ7RVH3LDC4ZZV4OCZG:867405003 f1736695.pdf": {
          "nomFolderName": "NOM-FACT-2026-04-21_10-45-17",
          "atMs": 1776782717388,
          "sourceBankPath": "Facturas/2026-04/Semana 4/Banco de Chile",
          "lotePdfSignature": "01HJAGCLRBD4ZPOYKSXZAYUSOA6TQGLJVT:867405003 f1736600.pdf|01HJAGCLRKUAWPIJAJPFB2LKUJAMP4BSBR:867405003 f1736602.pdf|01HJAGCLXGEZ7WTKSCABHK5EBIGP6QCCUT:867405003 f1736613.pdf|01HJAGCLRB4UGZOUPA2ZGLS6ZHPDDCHYMB:867405003 f1736614.pdf|01HJAGCLS2LHRKDD3NFBE3MJ76DUGPIGWE:867405003 f1736680.pdf|01HJAGCLW5NWJMTMZ7RVH3LDC4ZZV4OCZG:867405003 f1736695.pdf"
        }
      },
      "facturasNomByBank": {
        "Facturas/2026-04/Semana 4/Banco de Chile": {
          "nomFolderName": "NOM-FACT-2026-04-21_10-45-17",
          "atMs": 1776782717388
        }
      },
      "facturasNomActiveByBank": {
        "Facturas/2026-04/Semana 4/Banco de Chile": {
          "nomFolderName": "NOM-FACT-2026-04-21_10-45-17",
          "atMs": 1776782717388,
          "lotePdfSignature": "01HJAGCLRBD4ZPOYKSXZAYUSOA6TQGLJVT:867405003 f1736600.pdf|01HJAGCLRKUAWPIJAJPFB2LKUJAMP4BSBR:867405003 f1736602.pdf|01HJAGCLXGEZ7WTKSCABHK5EBIGP6QCCUT:867405003 f1736613.pdf|01HJAGCLRB4UGZOUPA2ZGLS6ZHPDDCHYMB:867405003 f1736614.pdf|01HJAGCLS2LHRKDD3NFBE3MJ76DUGPIGWE:867405003 f1736680.pdf|01HJAGCLW5NWJMTMZ7RVH3LDC4ZZV4OCZG:867405003 f1736695.pdf"
        }
      }
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "pinData": {},
  "versionId": "26b520a6-a998-41b7-98e5-e4aa013db412",
  "activeVersionId": "26b520a6-a998-41b7-98e5-e4aa013db412",
  "versionCounter": 488,
  "triggerCount": 1,
  "tags": [],
  "shared": [
    {
      "updatedAt": "2026-04-09T15:50:36.016Z",
      "createdAt": "2026-04-09T15:50:36.016Z",
      "role": "workflow:owner",
      "workflowId": "UkFwQvEGGabIqcdN",
      "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": "Version 26b520a6",
    "description": ""
  }
}