{
  "updatedAt": "2026-04-13T20:30:59.666Z",
  "createdAt": "2026-04-09T15:53:26.908Z",
  "id": "Zn5zN9pEkuMK7Doa",
  "name": "MyE Nominas",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "id": "3b7d1bbe-7454-4aed-a2f8-cc04c4e2e981",
      "name": "Microsoft OneDrive Trigger",
      "type": "n8n-nodes-base.microsoftOneDriveTrigger",
      "typeVersion": 1,
      "position": [
        -3616,
        624
      ],
      "parameters": {},
      "credentials": {
        "microsoftOneDriveOAuth2Api": {
          "id": "KGZfdefsCF8ngR3q",
          "name": "Microsoft Drive account"
        }
      }
    },
    {
      "id": "022e0b15-ef17-47fb-ba46-02cf1145b4fb",
      "name": "Filtrar Evento Nomina",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -3376,
        624
      ],
      "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 normalizeBaseWebhookUrl() {\n  const normalize = (value) => {\n    const raw = String(value ?? '').trim();\n    if (!raw) return '';\n    const withProtocol = /^[a-z]+:\\/\\//i.test(raw) || raw.startsWith('/') ? raw : 'https://' + raw;\n    return withProtocol.replace(/\\/+$/,'').replace(/\\/webhook$/,'');\n  };\n  for (const candidate of [$vars?.baseWebhookUrl, $vars?.BASE_WEBHOOK_URL, $vars?.WEBHOOK_URL]) {\n    const normalized = normalize(candidate);\n    if (normalized) return normalized;\n  }\n  return 'https://nonsuspended-worried-alyssa.ngrok-free.dev';\n}\n\nfunction buildMeta(source) {\n  const itemName = cleanText(source.name);\n  const name = itemName.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  const normalizedRawPath = rawPath.replace(/^\\/drive\\/root:/i, '').replace(/^\\/root:/i, '').replace(/^\\/+/, '');\n\n  if (!name.endsWith('.pdf')) return null;\n  if (mime && mime !== 'application/pdf') return null;\n  if (!/\\/nom-[^/]+$/i.test(rawPath)) return null;\n\n  const nominasNomPath = normalizedRawPath;\n  const parts = nominasNomPath.split('/').filter(Boolean);\n\n  if (parts.length < 5) {\n    throw new Error('La ruta de nomina debe tener formato Nominas/AAAA-MM/Semana X/Banco X/NOM-*.');\n  }\n\n  const [nominasRoot, yearMonth, weekFolder, bankFolder, nomFolderName] = parts;\n  const reportesNomPath = ['Reportes', yearMonth, weekFolder, bankFolder, nomFolderName].join('/');\n  const nominaLinkKey = [yearMonth, weekFolder, bankFolder, nomFolderName].join('::');\n\n  return {\n    ...source,\n    driveId,\n    nominasRoot,\n    yearMonth,\n    weekFolder,\n    bankFolder,\n    bankType: bankFolder,\n    nomFolderName,\n    nominasNomPath,\n    nominasNomPathEncoded: encodePath(nominasNomPath),\n    reportesNomPath,\n    reportesNomPathEncoded: encodePath(reportesNomPath),\n    nominaLinkKey,\n    loteRetryCount: 0,\n    excelRetryCount: 0,\n    fechaSolicitudTexto: new Intl.DateTimeFormat('es-CL', {\n      timeZone: 'America/Santiago',\n      year: 'numeric',\n      month: '2-digit',\n      day: '2-digit',\n    }).format(new Date()),\n    baseWebhookUrl: normalizeBaseWebhookUrl(),\n    secret: 'MyE_Fujo_2026..',\n    emailContador: 'pagosproveedores@mye.cl',\n    emailVerificador: 'xinostroza@mye.cl',\n    emailFinanzas: 'rpulgar@mye.cl',\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;\nconst RECENT_TTL_MS = 30 * 60 * 1000;\nlet matchedCurrentItems = 0;\nconst currentBatchByKey = {};\nif (!sd.nominaPendingByKey || typeof sd.nominaPendingByKey !== 'object') sd.nominaPendingByKey = {};\nif (!sd.nominaLocks || typeof sd.nominaLocks !== 'object') sd.nominaLocks = {};\nif (!sd.nominaRecent || typeof sd.nominaRecent !== 'object') sd.nominaRecent = {};\nif (!sd.nominaApprovalSentByKey || typeof sd.nominaApprovalSentByKey !== 'object') sd.nominaApprovalSentByKey = {};\n\nfor (const [key, value] of Object.entries(sd.nominaPendingByKey)) {\n  const ts = Number(value?.lastEventTs || value?.queuedAtMs || 0);\n  if (!Number.isFinite(ts) || (now - ts) > PENDING_TTL_MS) delete sd.nominaPendingByKey[key];\n}\nfor (const [key, ts] of Object.entries(sd.nominaLocks)) {\n  if (!Number.isFinite(ts) || (now - ts) > LOCK_TIMEOUT_MS) delete sd.nominaLocks[key];\n}\nfor (const [key, ts] of Object.entries(sd.nominaRecent)) {\n  if (!Number.isFinite(ts) || (now - ts) > RECENT_TTL_MS) delete sd.nominaRecent[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.nominaPendingByKey[meta.nominaLinkKey] || {};\n  sd.nominaPendingByKey[meta.nominaLinkKey] = {\n    ...existing,\n    ...meta,\n    lastEventTs: itemTs(source),\n    queuedAtMs: now,\n  };\n  currentBatchByKey[meta.nominaLinkKey] = {\n    ...sd.nominaPendingByKey[meta.nominaLinkKey],\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));\n\nconst selectUnlocked = (entries) => entries.find((entry) => {\n  const lockKey = String(entry?.nominasNomPath || '').trim();\n  return lockKey && !sd.nominaLocks[lockKey];\n});\nconst selected = selectUnlocked(currentBatchCandidates);\n\nif (!selected) return [];\n\nconst selectedPendingKey = String(selected.nominaLinkKey || '').trim();\nconst batchPendingQueue = currentBatchCandidates\n  .filter((entry) => String(entry?.nominaLinkKey || '').trim() && String(entry?.nominaLinkKey || '').trim() !== selectedPendingKey)\n  .map((entry) => ({ ...entry }));\n\nreturn [{\n  json: {\n    ...selected,\n    batchPendingQueue,\n  }\n}];"
      }
    },
    {
      "id": "3d1afdf4-b022-4777-b0a5-22ada4f2f35c",
      "name": "Lock Nomina",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -3136,
        624
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nconst now = Date.now();\nconst LOCK_TIMEOUT_MS = 10 * 60 * 1000;\nconst RECENT_TTL_MS = 30 * 60 * 1000;\n\nconst folderKey = String($json.nominasNomPath || '').trim();\nconst eventKey = [String($json.id || ''), String($json.name || '').toLowerCase(), folderKey].join('|');\nconst executionKey = String($execution.id || '').trim();\n\nif (!sd.nominaLocks || typeof sd.nominaLocks !== 'object') sd.nominaLocks = {};\nif (!sd.nominaRecent || typeof sd.nominaRecent !== 'object') sd.nominaRecent = {};\nif (!sd.nominaExecutionState || typeof sd.nominaExecutionState !== 'object') sd.nominaExecutionState = {};\n\nfor (const [key, ts] of Object.entries(sd.nominaLocks)) {\n  if (!Number.isFinite(ts) || (now - ts) > LOCK_TIMEOUT_MS) delete sd.nominaLocks[key];\n}\nfor (const [key, ts] of Object.entries(sd.nominaRecent)) {\n  if (!Number.isFinite(ts) || (now - ts) > RECENT_TTL_MS) delete sd.nominaRecent[key];\n}\n\nif (!folderKey) return [];\nif (eventKey !== '||' && sd.nominaRecent[eventKey]) return [];\nif (sd.nominaLocks[folderKey]) {\n  if (eventKey !== '||') sd.nominaRecent[eventKey] = now;\n  return [];\n}\n\nsd.nominaLocks[folderKey] = now;\nif (eventKey !== '||') sd.nominaRecent[eventKey] = now;\nif (executionKey) sd.nominaExecutionState[executionKey] = { ...$json };\n\nreturn items;"
      }
    },
    {
      "id": "7a4a6cb2-f77d-46ab-8bdb-d75f02a760ea",
      "webhookId": "3b6e2aa7-f80c-4994-b3df-42d917d91448",
      "name": "Wait Lote Nomina",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        -2896,
        624
      ],
      "parameters": {
        "amount": 20,
        "unit": "seconds"
      }
    },
    {
      "id": "575284f0-b4e8-432c-bbda-5608399a08e1",
      "name": "Obtener NOM Nomina",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -2656,
        624
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$json.nominasNomPathEncoded}}?$select=id,name,webUrl,parentReference",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "10526abc-176c-47d4-a05f-7d34fe08dd3c",
      "name": "Resolver NOM Nomina",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2416,
        624
      ],
      "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;\nconst executionKey = String($execution.id || '').trim();\n\nfunction readNodeJson(nodeName) {\n  try {\n    return (($(nodeName).item.json) || {});\n  } catch {\n    return {};\n  }\n}\n\nfunction encodePath(path) {\n  return String(path || '').split('/').filter(Boolean).map(encodeURIComponent).join('/');\n}\n\nfunction buildMetaFromFolder(folderPath, folderName, driveId, webUrl, existing = {}) {\n  const normalizedPath = String(folderPath || '')\n    .replace(/^\\/drive\\/root:/i, '')\n    .replace(/^\\/root:/i, '')\n    .replace(/^\\/+/, '');\n  const parts = normalizedPath.split('/').filter(Boolean);\n  if (parts.length < 4) return { ...existing };\n\n  const [nominasRoot, yearMonth, weekFolder, bankFolder] = parts;\n  const resolvedFolderName = String(folderName || existing.nomFolderName || '').trim();\n  if (!resolvedFolderName) return { ...existing };\n\n  const nominasNomPath = [nominasRoot, yearMonth, weekFolder, bankFolder, resolvedFolderName].join('/');\n  const reportesNomPath = ['Reportes', yearMonth, weekFolder, bankFolder, resolvedFolderName].join('/');\n\n  return {\n    ...existing,\n    driveId: String(driveId || existing.driveId || ''),\n    nominasRoot,\n    yearMonth,\n    weekFolder,\n    bankFolder,\n    bankType: bankFolder,\n    nomFolderName: resolvedFolderName,\n    nominasNomPath,\n    nominasNomPathEncoded: encodePath(nominasNomPath),\n    reportesNomPath,\n    reportesNomPathEncoded: encodePath(reportesNomPath),\n    nominaLinkKey: [yearMonth, weekFolder, bankFolder, resolvedFolderName].join('::'),\n    nomWebUrl: String(webUrl || existing.nomWebUrl || ''),\n  };\n}\n\nfunction getNextPending(excludedPendingKey = '', preferredQueue = []) {\n  if (!sd.nominaPendingByKey || typeof sd.nominaPendingByKey !== 'object') sd.nominaPendingByKey = {};\n  if (!sd.nominaLocks || typeof sd.nominaLocks !== 'object') sd.nominaLocks = {};\n  const queue = (Array.isArray(preferredQueue) ? preferredQueue : [])\n    .filter((entry) => String(entry?.nominaLinkKey || '').trim() && String(entry?.nominaLinkKey || '').trim() !== excludedPendingKey)\n    .sort((a, b) => Number(b.lastEventTs || 0) - Number(a.lastEventTs || 0));\n  const queueIndex = queue.findIndex((entry) => {\n    const lockKey = String(entry?.nominasNomPath || '').trim();\n    return lockKey && !sd.nominaLocks[lockKey];\n  });\n  if (queueIndex < 0) return [];\n  const [selected] = queue.splice(queueIndex, 1);\n  return [{ json: { ...selected, batchPendingQueue: queue } }];\n}\n\nif (!sd.nominaExecutionState || typeof sd.nominaExecutionState !== 'object') sd.nominaExecutionState = {};\nconst executionState = executionKey ? (sd.nominaExecutionState[executionKey] || {}) : {};\nconst retrySource = readNodeJson('Wait Reintento Lote Nomina');\nconst initialSource = readNodeJson('Wait Lote Nomina');\nconst source = Object.keys(executionState).length\n  ? executionState\n  : (Object.keys(retrySource).length ? retrySource : initialSource);\nconst response = $json || {};\nconst statusCode = Number(response.statusCode || 0);\nconst body = response.body && typeof response.body === 'object' ? response.body : {};\nconst pendingKey = String(source.nominaLinkKey || '').trim();\nconst lockKey = String(source.nominasNomPath || '').trim();\nconst batchPendingQueue = Array.isArray(source.batchPendingQueue) ? source.batchPendingQueue : [];\n\nif (statusCode >= 400 || !String(body.id || '').trim()) {\n  if (sd.nominaLocks && lockKey) delete sd.nominaLocks[lockKey];\n  if (sd.nominaPendingByKey && pendingKey && sd.nominaPendingByKey[pendingKey]) delete sd.nominaPendingByKey[pendingKey];\n  const nextPending = getNextPending(pendingKey, batchPendingQueue);\n  if (executionKey) {\n    if (nextPending.length) sd.nominaExecutionState[executionKey] = { ...nextPending[0].json };\n    else delete sd.nominaExecutionState[executionKey];\n  }\n  return nextPending;\n}\n\nconst derived = buildMetaFromFolder(\n  body.parentReference?.path,\n  body.name,\n  body.parentReference?.driveId || source.driveId,\n  body.webUrl,\n  source,\n);\nconst nextItem = {\n  ...source,\n  ...derived,\n  nomFolderId: String(body.id || ''),\n  nomWebUrl: String(body.webUrl || derived.nomWebUrl || ''),\n  nomFolderName: String(body.name || derived.nomFolderName || source.nomFolderName || ''),\n  obtenerNominaStatus: 'ok',\n};\nif (executionKey) sd.nominaExecutionState[executionKey] = nextItem;\n\nreturn [{\n  json: nextItem\n}];"
      }
    },
    {
      "id": "76930ce3-e008-4bb2-a9af-9a0d6c6680e0",
      "name": "IF NOM Nomina Disponible",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -2176,
        624
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "leftValue": "={{$json.obtenerNominaStatus === \"ok\"}}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "c52295a9-e5a0-45e5-b2b3-d0f4b6f7a0c1",
      "name": "Listar PDF Nomina",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -1984,
        416
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$json.nominasNomPathEncoded}}:/children?$top=999&$select=id,name,webUrl,file,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": "06d6f91b-6a4b-4064-ab1b-3173dec3965b",
      "name": "Evaluar Lote Nomina",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1792,
        256
      ],
      "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 readNodeJson(nodeName) {\n  try {\n    return (($(nodeName).item.json) || {});\n  } catch {\n    return {};\n  }\n}\n\nconst sd = $getWorkflowStaticData('global');\nconst executionKey = String($execution.id || '').trim();\nif (!sd.nominaApprovalSentByKey || typeof sd.nominaApprovalSentByKey !== 'object') sd.nominaApprovalSentByKey = {};\nif (!sd.nominaExecutionState || typeof sd.nominaExecutionState !== 'object') sd.nominaExecutionState = {};\nconst executionState = executionKey ? (sd.nominaExecutionState[executionKey] || {}) : {};\nconst retrySource = readNodeJson('Wait Reintento Lote Nomina');\nconst initialSource = readNodeJson('Wait Lote Nomina');\nconst filterSource = readNodeJson('Filtrar Evento Nomina');\nconst source = Object.keys(executionState).length\n  ? executionState\n  : (Object.keys(retrySource).length\n  ? retrySource\n  : (Object.keys(initialSource).length ? initialSource : filterSource));\nconst folder = readNodeJson('Resolver NOM Nomina');\nconst children = Array.isArray($json.value) ? $json.value : [];\nconst pdfs = children.filter(isPdf).sort((a, b) => itemTs(a) - itemTs(b));\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 sentMeta = sd.nominaApprovalSentByKey[String(source.nominaLinkKey || '').trim()] || {};\nconst alreadySentForSameSignature = Boolean(currentPdfSignature) && currentPdfSignature === String(sentMeta.pdfSignature || '');\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 loteRetryCount = Number(source.loteRetryCount || 0);\nconst loteNominaEstable = Boolean(folder.nomFolderId || source.nomFolderId || source.nominasNomPath) && (\n  !alreadySentForSameSignature && pdfs.length > 0 && (\n    quietMs >= QUIET_WINDOW_MS ||\n    samePdfSignatureStreak >= 2 ||\n    elapsedSinceStartMs >= MAX_BATCH_WAIT_MS\n  )\n);\n\nconst nextItem = {\n  ...source,\n  nomFolderId: folder.nomFolderId || source.nomFolderId || '',\n  nomWebUrl: folder.nomWebUrl || source.nomWebUrl || '',\n  nomFolderName: folder.nomFolderName || source.nomFolderName || '',\n  nominaPdfCount: pdfs.length,\n  loteQuietMs: quietMs,\n  loteQuietWindowMs: QUIET_WINDOW_MS,\n  loteStartedAtMs,\n  loteElapsedSinceStartMs: elapsedSinceStartMs,\n  loteMaxBatchWaitMs: MAX_BATCH_WAIT_MS,\n  loteRetryCount,\n  lotePdfSignature: currentPdfSignature,\n  loteSamePdfSignatureStreak: samePdfSignatureStreak,\n  loteAlreadySentForSameSignature: alreadySentForSameSignature,\n  loteLastSentPdfSignature: String(sentMeta.pdfSignature || ''),\n  loteNominaEstable,\n};\nif (executionKey) sd.nominaExecutionState[executionKey] = nextItem;\n\nreturn [{\n  json: nextItem\n}];"
      }
    },
    {
      "id": "3827e9b5-7343-41ee-8c2f-c6da4f8968ce",
      "name": "IF Lote Nomina Estable",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -1600,
        256
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "leftValue": "={{$json.loteNominaEstable === true}}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "5d14c1e1-c4c2-4c9d-a9db-f1951cced353",
      "name": "Preparar Reintento Lote Nomina",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1312,
        640
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nconst executionKey = String($execution.id || '').trim();\nfunction getNextPending(excludedPendingKey = '', preferredQueue = []) {\n  if (!sd.nominaPendingByKey || typeof sd.nominaPendingByKey !== 'object') sd.nominaPendingByKey = {};\n  if (!sd.nominaLocks || typeof sd.nominaLocks !== 'object') sd.nominaLocks = {};\n  const queue = (Array.isArray(preferredQueue) ? preferredQueue : [])\n    .filter((entry) => String(entry?.nominaLinkKey || '').trim() && String(entry?.nominaLinkKey || '').trim() !== excludedPendingKey)\n    .sort((a, b) => Number(b.lastEventTs || 0) - Number(a.lastEventTs || 0));\n  const queueIndex = queue.findIndex((entry) => {\n    const pendingLockKey = String(entry?.nominasNomPath || '').trim();\n    return pendingLockKey && !sd.nominaLocks[pendingLockKey];\n  });\n  if (queueIndex < 0) return [];\n  const [selected] = queue.splice(queueIndex, 1);\n  return [{ json: { ...selected, batchPendingQueue: queue } }];\n}\nif (!sd.nominaExecutionState || typeof sd.nominaExecutionState !== 'object') sd.nominaExecutionState = {};\nconst executionState = executionKey ? (sd.nominaExecutionState[executionKey] || {}) : {};\nconst meta = Object.keys(executionState).length ? executionState : (($('Evaluar Lote Nomina').item.json) || $json || {});\nconst nextRetry = Number(meta.loteRetryCount || 0) + 1;\nconst MAX_RETRIES = 18;\nconst lockKey = String(meta.nominasNomPath || '').trim();\nconst pendingKey = String(meta.nominaLinkKey || '').trim();\nconst batchPendingQueue = Array.isArray(meta.batchPendingQueue) ? meta.batchPendingQueue : [];\n\nif (meta.loteAlreadySentForSameSignature) {\n  if (sd.nominaLocks && lockKey) delete sd.nominaLocks[lockKey];\n  if (sd.nominaPendingByKey && pendingKey && sd.nominaPendingByKey[pendingKey]) delete sd.nominaPendingByKey[pendingKey];\n  const nextPending = getNextPending(pendingKey, batchPendingQueue);\n  if (executionKey) {\n    if (nextPending.length) sd.nominaExecutionState[executionKey] = { ...nextPending[0].json };\n    else delete sd.nominaExecutionState[executionKey];\n  }\n  return nextPending;\n}\n\nif (nextRetry > MAX_RETRIES) {\n  if (sd.nominaLocks && lockKey) delete sd.nominaLocks[lockKey];\n  const nextPending = getNextPending(pendingKey, batchPendingQueue);\n  if (executionKey) {\n    if (nextPending.length) sd.nominaExecutionState[executionKey] = { ...nextPending[0].json };\n    else delete sd.nominaExecutionState[executionKey];\n  }\n  return nextPending;\n}\n\nconst nextItem = {\n  ...meta,\n  loteRetryCount: nextRetry,\n};\nif (executionKey) sd.nominaExecutionState[executionKey] = nextItem;\n\nreturn [{\n  json: nextItem\n}];"
      }
    },
    {
      "id": "6f9c3e36-5b89-444d-9bc0-4d5825324db6",
      "webhookId": "d3ef4e83-d4f0-4e37-a860-8776d97928e0",
      "name": "Wait Reintento Lote Nomina",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        -1104,
        640
      ],
      "parameters": {
        "amount": 20,
        "unit": "seconds"
      }
    },
    {
      "id": "befe8048-86e8-4058-bb2b-5f956af845ca",
      "name": "Buscar Excel Reportes Nomina",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -1312,
        240
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/root:/{{$json.reportesNomPathEncoded}}/facturas_semana.xlsx?$select=id,name,webUrl",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "6f9ac62b-50c8-4407-a688-04679b50414c",
      "name": "IF Excel Reporte Existe",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -1104,
        240
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "leftValue": "={{String($json.id || \"\").trim() !== \"\" && !$json.error}}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "1d5f9140-cb2c-4227-95c1-5a4aad9dcffb",
      "name": "Descargar Excel Reportes Nomina",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        -832,
        112
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/me/drive/items/{{$json.id || \"SIN_EXCEL\"}}/content",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOAuth2Api",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          },
          "timeout": 180000
        }
      },
      "credentials": {
        "microsoftOAuth2Api": {
          "id": "7pDFU0vOqasMJYBC",
          "name": "Microsoft account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "99e04bec-aa7b-4ee9-b0e3-0aa373bcc796",
      "name": "Leer Excel Nomina",
      "type": "n8n-nodes-base.spreadsheetFile",
      "typeVersion": 2,
      "position": [
        -656,
        112
      ],
      "parameters": {
        "options": {}
      }
    },
    {
      "id": "7ddfe223-9f53-41a4-8d94-5121544e187e",
      "name": "Calcular Totales Nomina",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -464,
        112
      ],
      "parameters": {
        "jsCode": "function parseMonto(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 = String(value).replace(/\\$/g, '').replace(/\\s/g, '').trim();\n  if (!raw || raw === '-') return 0;\n  if (/^\\d{1,3}(\\.\\d{3})+(,\\d+)?$/.test(raw)) {\n    const n = Number(raw.replace(/\\./g, '').replace(',', '.'));\n    return Number.isFinite(n) ? Math.max(0, Math.round(n)) : 0;\n  }\n  if (/^\\d{1,3}(,\\d{3})+(\\.\\d+)?$/.test(raw)) {\n    const n = Number(raw.replace(/,/g, ''));\n    return Number.isFinite(n) ? Math.max(0, Math.round(n)) : 0;\n  }\n  const fallback = Number(raw.replace(/\\./g, '').replace(',', '.'));\n  if (Number.isFinite(fallback)) return Math.max(0, Math.round(fallback));\n  const digits = raw.replace(/[^\\d]/g, '');\n  return digits ? parseInt(digits, 10) : 0;\n}\n\nconst sd = $getWorkflowStaticData('global');\nconst executionKey = String($execution.id || '').trim();\nif (!sd.nominaExecutionState || typeof sd.nominaExecutionState !== 'object') sd.nominaExecutionState = {};\nconst executionState = executionKey ? (sd.nominaExecutionState[executionKey] || {}) : {};\nconst prep = Object.keys(executionState).length ? executionState : (($('Evaluar Lote Nomina').item.json) || {});\nconst excelMeta = (($('Buscar Excel Reportes Nomina').item.json) || {});\nconst downloadMeta = (($('Descargar Excel Reportes Nomina').item.json) || {});\nconst excelRetryCount = Number(prep.excelRetryCount || 0);\n\nif (downloadMeta.error || excelMeta.error) {\n  const nextItem = {\n    ...prep,\n    excelRetryCount,\n    excelReady: false,\n    excelReadyReason: 'No fue posible descargar el Excel de reportes.',\n    excelWebUrl: String(excelMeta.webUrl || ''),\n  };\n  if (executionKey) sd.nominaExecutionState[executionKey] = nextItem;\n  return [{\n    json: nextItem\n  }];\n}\n\nlet facturasCount = 0;\nlet facturasTotal = 0;\nlet rowsWithData = 0;\nfor (const item of items) {\n  const row = item.json || {};\n  const monto = parseMonto(row.monto_total);\n  const numeroFactura = String(row.numero_factura || '').trim();\n  const numeroOc = String(row.numero_oc || '').trim();\n  const tipoDocumento = String(row.tipo_documento || '').trim();\n  const rutProveedor = String(row.rut_proveedor || '').trim();\n  const nombreProveedor = String(row.nombre_proveedor || '').trim();\n  const fechaDocumento = String(row.fecha_documento || '').trim();\n  const fechaVencimiento = String(row.fecha_vencimiento || '').trim();\n  const montoNeto = parseMonto(row.monto_neto);\n  const montoIva = parseMonto(row.monto_iva);\n  const hasMeaningfulData = Boolean(\n    numeroFactura ||\n    numeroOc ||\n    tipoDocumento ||\n    rutProveedor ||\n    nombreProveedor ||\n    fechaDocumento ||\n    fechaVencimiento ||\n    montoNeto > 0 ||\n    montoIva > 0 ||\n    monto > 0\n  );\n  if (!hasMeaningfulData) continue;\n  rowsWithData += 1;\n  if (!Number.isFinite(monto) || monto <= 0) continue;\n  facturasCount += 1;\n  facturasTotal += monto;\n}\n\nconst facturasTotalTexto = facturasTotal > 0 ? '$' + Number(facturasTotal).toLocaleString('es-CL') : '$0';\nconst payload = {\n  driveId: prep.driveId,\n  folderId: prep.nomFolderId,\n  folderName: prep.nomFolderName,\n  linkedFacturasFolderName: prep.nomFolderName,\n  linkedFacturasFolderPathEncoded: prep.reportesNomPathEncoded,\n  webUrl: prep.nomWebUrl,\n  excelWebUrl: String(excelMeta.webUrl || ''),\n  weekFolder: prep.weekFolder,\n  bankType: prep.bankType || 'No especificado',\n  fechaSolicitudTexto: prep.fechaSolicitudTexto,\n  facturasTotal,\n  facturasTotalTexto,\n  facturasYearMonth: prep.yearMonth,\n  facturasDriveId: prep.driveId,\n  yearMonth: prep.yearMonth,\n    emailContador: prep.emailContador || 'pagosproveedores@mye.cl',\n    emailVerificador: prep.emailVerificador || 'xinostroza@mye.cl',\n    emailFinanzas: prep.emailFinanzas || 'rpulgar@mye.cl',\n  stage: 'verificador',\n  exp: Date.now() + 1000 * 60 * 60 * 48,\n};\n\nconst nextItem = {\n  ...prep,\n  folderId: prep.nomFolderId,\n  folderName: prep.nomFolderName,\n  webUrl: prep.nomWebUrl,\n  excelRetryCount,\n  excelReady: rowsWithData > 0,\n  excelReadyReason: rowsWithData > 0\n    ? (facturasTotal > 0 ? '' : 'El Excel fue leido, pero aun no se detectaron montos validos.')\n    : 'El Excel aun no contiene filas validas.',\n  excelWebUrl: String(excelMeta.webUrl || ''),\n  facturasCount,\n  facturasTotal,\n  facturasTotalTexto,\n  payloadStr: JSON.stringify(payload),\n};\nif (executionKey) sd.nominaExecutionState[executionKey] = nextItem;\n\nreturn [{\n  json: nextItem\n}];"
      }
    },
    {
      "id": "51e0bebd-72ab-47bd-92d9-00fe253a593a",
      "name": "IF Excel Nomina Listo",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        -256,
        112
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "leftValue": "={{$json.excelReady === true}}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "4791e6b4-e43f-43c3-9f5e-572dc677a369",
      "name": "Preparar Reintento Excel Nomina",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        16,
        336
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nconst executionKey = String($execution.id || '').trim();\nfunction getNextPending(excludedPendingKey = '', preferredQueue = []) {\n  if (!sd.nominaPendingByKey || typeof sd.nominaPendingByKey !== 'object') sd.nominaPendingByKey = {};\n  if (!sd.nominaLocks || typeof sd.nominaLocks !== 'object') sd.nominaLocks = {};\n  const queue = (Array.isArray(preferredQueue) ? preferredQueue : [])\n    .filter((entry) => String(entry?.nominaLinkKey || '').trim() && String(entry?.nominaLinkKey || '').trim() !== excludedPendingKey)\n    .sort((a, b) => Number(b.lastEventTs || 0) - Number(a.lastEventTs || 0));\n  const queueIndex = queue.findIndex((entry) => {\n    const pendingLockKey = String(entry?.nominasNomPath || '').trim();\n    return pendingLockKey && !sd.nominaLocks[pendingLockKey];\n  });\n  if (queueIndex < 0) return [];\n  const [selected] = queue.splice(queueIndex, 1);\n  return [{ json: { ...selected, batchPendingQueue: queue } }];\n}\nif (!sd.nominaExecutionState || typeof sd.nominaExecutionState !== 'object') sd.nominaExecutionState = {};\nconst executionState = executionKey ? (sd.nominaExecutionState[executionKey] || {}) : {};\nconst loteMeta = Object.keys(executionState).length ? executionState : (($('Evaluar Lote Nomina').item.json) || {});\nconst filterMeta = (($('Filtrar Evento Nomina').item.json) || {});\nconst base = loteMeta.nominasNomPath ? loteMeta : filterMeta;\n\nconst current = {\n  ...base,\n  ...$json,\n};\n\nconst nextRetry = Number(current.excelRetryCount || 0) + 1;\nconst MAX_RETRIES = 30;\nconst pendingKey = String(current.nominaLinkKey || '').trim();\nconst batchPendingQueue = Array.isArray(current.batchPendingQueue) ? current.batchPendingQueue : [];\n\nif (nextRetry > MAX_RETRIES) {\n  const lockKey = String(current.nominasNomPath || '').trim();\n  if (sd.nominaLocks && lockKey) delete sd.nominaLocks[lockKey];\n  const nextPending = getNextPending(pendingKey, batchPendingQueue);\n  if (executionKey) {\n    if (nextPending.length) sd.nominaExecutionState[executionKey] = { ...nextPending[0].json };\n    else delete sd.nominaExecutionState[executionKey];\n  }\n  return nextPending;\n}\n\nconst nextItem = {\n  ...current,\n  excelRetryCount: nextRetry,\n};\nif (executionKey) sd.nominaExecutionState[executionKey] = nextItem;\n\nreturn [{\n  json: nextItem\n}];"
      }
    },
    {
      "id": "5c90f47f-9bcf-44a6-b16e-44cfc9356d06",
      "webhookId": "f921686f-8de4-4462-8940-075a92c1cf90",
      "name": "Wait Reintento Excel Nomina",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        224,
        336
      ],
      "parameters": {
        "amount": 20,
        "unit": "seconds"
      }
    },
    {
      "id": "9c2680a3-347f-4b34-ae20-c85dddab00d1",
      "name": "HMAC Token Email Nomina",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "parameters": {
        "action": "hmac",
        "binaryData": false,
        "type": "SHA256",
        "value": "={{$json.payloadStr}}",
        "dataPropertyName": "data",
        "encoding": "base64"
      },
      "credentials": {
        "crypto": {
          "id": "hB0SZXy6Hqtzg9IQ",
          "name": "Crypto account"
        }
      }
    },
    {
      "id": "203b9f5d-0710-4e04-b970-1bcbca444844",
      "name": "Build Token Email Nomina",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        192,
        0
      ],
      "parameters": {
        "jsCode": "function b64url(s) {\n  return Buffer.from(s)\n    .toString('base64')\n    .replace(/=+$/,'')\n    .replace(/\\+/g,'-')\n    .replace(/\\//g,'_');\n}\nfunction resolveBaseWebhookUrl(value) {\n  const normalize = (candidate) => {\n    const raw = String(candidate ?? '').trim();\n    if (!raw) return '';\n    const withProtocol = /^[a-z]+:\\/\\//i.test(raw) || raw.startsWith('/') ? raw : 'https://' + raw;\n    return withProtocol.replace(/\\/+$/,'').replace(/\\/webhook$/,'');\n  };\n  for (const candidate of [value, $vars?.baseWebhookUrl, $vars?.BASE_WEBHOOK_URL, $vars?.WEBHOOK_URL]) {\n    const normalized = normalize(candidate);\n    if (normalized) return normalized;\n  }\n  return 'https://nonsuspended-worried-alyssa.ngrok-free.dev';\n}\nconst sig = $json.data;\nconst token = b64url($json.payloadStr) + '.' + b64url(sig);\nconst baseWebhookUrl = resolveBaseWebhookUrl($json.baseWebhookUrl);\nconst approveUrl = baseWebhookUrl + '/webhook/nominas-approve?token=' + token;\nconst rejectUrl = baseWebhookUrl + '/webhook/nominas-reject?token=' + token;\nreturn [{ json: { ...$json, token, approveUrl, rejectUrl } }];"
      }
    },
    {
      "id": "d0899cb8-a455-4c81-9c64-8394575ef27f",
      "webhookId": "76045c73-b78f-4621-848d-b0f6d125f63a",
      "name": "Email Aprobacion Verificador Nomina",
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        368,
        0
      ],
      "parameters": {
        "toRecipients": "={{$json.recipientEmail || $json.emailVerificador || \"xinostroza@mye.cl\"}}",
        "subject": "=Solicitud Validación Nomina {{$json.bankType || 'No especificado'}} por {{$json.facturasTotalTexto || '$0'}}",
        "bodyContent": "={{ (() => { const v = $json || {}; const esc = (x) => String(x ?? '').replace(/[&<>]/g, (m) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[m])); const bank = v.bankType || 'No especificado'; const total = v.facturasTotalTexto || '$0'; const date = v.fechaSolicitudTexto || 'No disponible'; const nomUrl = v.webUrl || '#'; const excelUrl = v.excelWebUrl || '#'; const approveUrl = v.approveUrl || '#'; const rejectUrl = v.rejectUrl || '#'; return `<table role='presentation' width='100%' cellpadding='0' cellspacing='0' border='0' bgcolor='#f4f6f8' style='font-family: Arial, Helvetica, sans-serif;'><tr><td align='center' style='padding:20px;'><table role='presentation' width='600' cellpadding='0' cellspacing='0' border='0' bgcolor='#ffffff' style='border-radius:6px; overflow:hidden;'><tr><td bgcolor='#ff171a' style='color:#ffffff; padding:16px; font-size:18px;'>Solicitud de validación de nómina</td></tr><tr><td style='padding:20px; color:#333333; font-size:14px; line-height:1.6;'><p>Estimada Ximena Inostroza:</p><p>Junto con saludar, la nómina está lista para ser validada. Una vez finalizada su revisión, favor aprobar o rechazar con justificación la siguiente nómina.</p><ul><li><b>Tipo de banco:</b> ${esc(bank)}</li><li><b>Monto consolidado:</b> ${esc(total)}</li><li><b>Carpeta de nómina:</b> <a href='${nomUrl}'>Abrir carpeta</a></li><li><b>Archivo de control:</b> <a href='${excelUrl}'>Abrir facturas_semana.xlsx</a></li><li><b>Fecha:</b> ${esc(date)}</li></ul><p style='margin-top:20px;'><a href='${approveUrl}' style='background:#ff171a; color:#ffffff; text-decoration:none; padding:12px 18px; border-radius:4px; display:inline-block; margin-right:10px;'>Aprobar</a><a href='${rejectUrl}' style='background:#808080; color:#ffffff; text-decoration:none; padding:12px 18px; border-radius:4px; display:inline-block;'>Rechazar</a></p><hr style='border:none; border-top:1px solid #e0e0e0; margin:20px 0;'><div style='font-size:12px; color:#666;'><img src='https://mye.cl/nuevas-firmas/gifs/FIRMA-MARCAS-MYE.gif' alt='Firma MYE' style='max-width:420px; width:100%; height:auto;'></div></td></tr></table></td></tr></table>`; })() }}",
        "additionalFields": {
          "bodyContentType": "html"
        }
      },
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "id": "p4O6wj9qoVBj9C9X",
          "name": "Microsoft Outlook account"
        }
      }
    },
    {
      "id": "470abcb2-8002-4141-ac84-0281c07d3312",
      "name": "Marcar Nomina Verificadora Enviada",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        576,
        0
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nif (!sd.nominaApprovalSentByKey || typeof sd.nominaApprovalSentByKey !== 'object') sd.nominaApprovalSentByKey = {};\nconst key = String($json.nominaLinkKey || '').trim();\nif (!key) return items;\nconst now = Date.now();\nsd.nominaApprovalSentByKey[key] = {\n  action: 'sent_to_verificador',\n  atMs: now,\n  atIso: new Date(now).toISOString(),\n  folderName: $json.folderName || '',\n  pdfSignature: String($json.lotePdfSignature || ''),\n};\nreturn items;"
      }
    },
    {
      "id": "0fd7beb3-72f4-452d-afe1-ea8aa4c98205",
      "name": "Unlock Nomina",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        768,
        0
      ],
      "parameters": {
        "jsCode": "const sd = $getWorkflowStaticData('global');\nconst executionKey = String($execution.id || '').trim();\nfunction getNextPending(excludedPendingKey = '', preferredQueue = []) {\n  if (!sd.nominaPendingByKey || typeof sd.nominaPendingByKey !== 'object') sd.nominaPendingByKey = {};\n  if (!sd.nominaLocks || typeof sd.nominaLocks !== 'object') sd.nominaLocks = {};\n  const queue = (Array.isArray(preferredQueue) ? preferredQueue : [])\n    .filter((entry) => String(entry?.nominaLinkKey || '').trim() && String(entry?.nominaLinkKey || '').trim() !== excludedPendingKey)\n    .sort((a, b) => Number(b.lastEventTs || 0) - Number(a.lastEventTs || 0));\n  const queueIndex = queue.findIndex((entry) => {\n    const pendingLockKey = String(entry?.nominasNomPath || '').trim();\n    return pendingLockKey && !sd.nominaLocks[pendingLockKey];\n  });\n  if (queueIndex < 0) return [];\n  const [selected] = queue.splice(queueIndex, 1);\n  return [{ json: { ...selected, batchPendingQueue: queue } }];\n}\nif (!sd.nominaExecutionState || typeof sd.nominaExecutionState !== 'object') sd.nominaExecutionState = {};\nconst executionState = executionKey ? (sd.nominaExecutionState[executionKey] || {}) : {};\nconst current = Object.keys(executionState).length ? executionState : ($json || {});\nconst key = String(current.nominasNomPath || '').trim();\nconst pendingKey = String(current.nominaLinkKey || '').trim();\nconst batchPendingQueue = Array.isArray(current.batchPendingQueue) ? current.batchPendingQueue : [];\nif (sd.nominaLocks && key && sd.nominaLocks[key]) delete sd.nominaLocks[key];\nif (sd.nominaPendingByKey && pendingKey && sd.nominaPendingByKey[pendingKey]) delete sd.nominaPendingByKey[pendingKey];\nconst nextPending = getNextPending(pendingKey, batchPendingQueue);\nif (executionKey) {\n  if (nextPending.length) sd.nominaExecutionState[executionKey] = { ...nextPending[0].json };\n  else delete sd.nominaExecutionState[executionKey];\n}\nreturn nextPending;"
      }
    }
  ],
  "connections": {
    "Microsoft OneDrive Trigger": {
      "main": [
        [
          {
            "node": "Filtrar Evento Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filtrar Evento Nomina": {
      "main": [
        [
          {
            "node": "Lock Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lock Nomina": {
      "main": [
        [
          {
            "node": "Wait Lote Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Lote Nomina": {
      "main": [
        [
          {
            "node": "Obtener NOM Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obtener NOM Nomina": {
      "main": [
        [
          {
            "node": "Resolver NOM Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolver NOM Nomina": {
      "main": [
        [
          {
            "node": "IF NOM Nomina Disponible",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF NOM Nomina Disponible": {
      "main": [
        [
          {
            "node": "Listar PDF Nomina",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Lock Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Listar PDF Nomina": {
      "main": [
        [
          {
            "node": "Evaluar Lote Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluar Lote Nomina": {
      "main": [
        [
          {
            "node": "IF Lote Nomina Estable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Lote Nomina Estable": {
      "main": [
        [
          {
            "node": "Buscar Excel Reportes Nomina",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Preparar Reintento Lote Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Reintento Lote Nomina": {
      "main": [
        [
          {
            "node": "Wait Reintento Lote Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Reintento Lote Nomina": {
      "main": [
        [
          {
            "node": "Obtener NOM Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Buscar Excel Reportes Nomina": {
      "main": [
        [
          {
            "node": "IF Excel Reporte Existe",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Excel Reporte Existe": {
      "main": [
        [
          {
            "node": "Descargar Excel Reportes Nomina",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Preparar Reintento Excel Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Descargar Excel Reportes Nomina": {
      "main": [
        [
          {
            "node": "Leer Excel Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer Excel Nomina": {
      "main": [
        [
          {
            "node": "Calcular Totales Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calcular Totales Nomina": {
      "main": [
        [
          {
            "node": "IF Excel Nomina Listo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Excel Nomina Listo": {
      "main": [
        [
          {
            "node": "HMAC Token Email Nomina",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Preparar Reintento Excel Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Reintento Excel Nomina": {
      "main": [
        [
          {
            "node": "Wait Reintento Excel Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Reintento Excel Nomina": {
      "main": [
        [
          {
            "node": "Buscar Excel Reportes Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HMAC Token Email Nomina": {
      "main": [
        [
          {
            "node": "Build Token Email Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Token Email Nomina": {
      "main": [
        [
          {
            "node": "Email Aprobacion Verificador Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Aprobacion Verificador Nomina": {
      "main": [
        [
          {
            "node": "Marcar Nomina Verificadora Enviada",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Marcar Nomina Verificadora Enviada": {
      "main": [
        [
          {
            "node": "Unlock Nomina",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unlock Nomina": {
      "main": [
        [
          {
            "node": "Lock Nomina",
            "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=NDslMjM0OyUyMzE7MzsyYzQ2MzQ4ZS0wYmJjLTQ3ODAtODFhOC0zOGZiN2QyNDI1YmU7NjM5MTIzNzk2MzUzNDMwMDAwOzE4ODkzODIyNjQ7JTIzOyUyMzslMjMwOyUyMw",
      "lastTimeChecked": "2026-04-21T14:47:15.001Z"
    },
    "global": {
      "nominaPendingByKey": {},
      "nominaLocks": {},
      "nominaRecent": {
        "01HJAGCLUVPVWTE3VJHFDIXRQZUE6ZBDWA|nom chile toyota 153.mm.pdf|Nominas/2026-04/Semana 4/Banco de Chile/NOM-FACT-2026-04-21_10-45-17": 1776782835645
      },
      "nominaApprovalSentByKey": {},
      "nominaExecutionState": {}
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "pinData": {},
  "versionId": "2ccd6fc6-64f3-41b3-a130-b2d00fe9de4c",
  "activeVersionId": "2ccd6fc6-64f3-41b3-a130-b2d00fe9de4c",
  "versionCounter": 410,
  "triggerCount": 1,
  "tags": [],
  "shared": [
    {
      "updatedAt": "2026-04-09T15:53:26.909Z",
      "createdAt": "2026-04-09T15:53:26.909Z",
      "role": "workflow:owner",
      "workflowId": "Zn5zN9pEkuMK7Doa",
      "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
  }
}