diff --git a/cruce-de-seguridad-social-honduras-producci-n-final-v3.json b/cruce-de-seguridad-social-honduras-producci-n-final-v3.json
new file mode 100644
index 0000000..de89673
--- /dev/null
+++ b/cruce-de-seguridad-social-honduras-producci-n-final-v3.json
@@ -0,0 +1,1455 @@
+{
+ "updatedAt": "2026-07-04T14:18:20.579Z",
+ "createdAt": "2026-07-04T13:43:12.562Z",
+ "id": "j04wjKihKPzBIqW8",
+ "name": "Cruce de Seguridad Social Honduras - PRODUCCI脫N FINAL v3",
+ "description": null,
+ "active": true,
+ "isArchived": false,
+ "nodes": [
+ {
+ "parameters": {
+ "formTitle": "Cruce IHSS vs N贸mina Honduras",
+ "formDescription": "Sube la n贸mina mensual de Honduras y una o varias planillas IHSS en PDF. El flujo generar谩 un reporte simple con cruce de identidad y cruce de montos IHSS.",
+ "formFields": {
+ "values": [
+ {
+ "fieldLabel": "Mes a validar",
+ "fieldType": "dropdown",
+ "fieldOptions": {
+ "values": [
+ {
+ "option": "01"
+ },
+ {
+ "option": "02"
+ },
+ {
+ "option": "03"
+ },
+ {
+ "option": "04"
+ },
+ {
+ "option": "05"
+ },
+ {
+ "option": "06"
+ },
+ {
+ "option": "07"
+ },
+ {
+ "option": "08"
+ },
+ {
+ "option": "09"
+ },
+ {
+ "option": "10"
+ },
+ {
+ "option": "11"
+ },
+ {
+ "option": "12"
+ }
+ ]
+ },
+ "requiredField": true
+ },
+ {
+ "fieldLabel": "Planillas IHSS PDF",
+ "fieldType": "file",
+ "acceptFileTypes": ".pdf",
+ "requiredField": true
+ },
+ {
+ "fieldLabel": "N贸mina",
+ "fieldType": "file",
+ "multipleFiles": false,
+ "acceptFileTypes": ".xlsx, .xls, .csv",
+ "requiredField": true
+ }
+ ]
+ },
+ "options": {
+ "ignoreBots": true
+ }
+ },
+ "type": "n8n-nodes-base.formTrigger",
+ "typeVersion": 2.3,
+ "position": [
+ -1184,
+ 336
+ ],
+ "id": "d7172e37-a0d6-43c7-a6d6-8523344e8551",
+ "name": "On form submission",
+ "webhookId": "d92ae3d4-ceef-4580-8439-679f5dd7aa67"
+ },
+ {
+ "parameters": {
+ "jsCode": "const item = $input.all()[0];\nconst now = new Date();\nconst pad = n => String(n).padStart(2, '0');\nconst ts = `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;\nconst runId = `HN_IHSS_${ts}_${Math.random().toString(36).slice(2, 8)}`;\nconst mes = String(item.json['Mes a validar'] || '').padStart(2, '0');\nconst anio = now.getFullYear();\nconst meses = { '01':'Enero','02':'Febrero','03':'Marzo','04':'Abril','05':'Mayo','06':'Junio','07':'Julio','08':'Agosto','09':'Septiembre','10':'Octubre','11':'Noviembre','12':'Diciembre' };\nconst fechaTitulo = `${pad(now.getDate())}-${pad(now.getMonth()+1)}-${now.getFullYear()}`;\nconst periodoLabel = `${meses[mes] || mes} ${anio}`;\nconst reportTitle = `Cruce Seguridad Social Honduras - ${periodoLabel} - ${fechaTitulo}`;\nconst data = $getWorkflowStaticData('global');\ndata.hnCruceIHSSNomina = data.hnCruceIHSSNomina || {};\ndata.hnCruceIHSSNomina[runId] = {\n startedAt: now.toISOString(),\n mes,\n anio,\n periodoLabel,\n ihss: [],\n ihssPlanillas: [],\n nomina: [],\n payrollIhssSummary: {},\n payrollIhssBlocks: [],\n pdfFiles: [],\n payrollFile: '',\n};\nreturn [{ json: { ...item.json, runId, mes, anio, periodoLabel, reportTitle, emailTo: 'iaracena@gomezleemarketing.com' }, binary: item.binary }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ -944,
+ 336
+ ],
+ "id": "864517a7-a3ab-48d8-a59e-2ed21a91139b",
+ "name": "Inicializar memoria"
+ },
+ {
+ "parameters": {
+ "jsCode": "const base = $input.all()[0];\nconst bin = base.binary || {};\nconst out = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '').toLowerCase();\n const lower = fileName.toLowerCase();\n const isPdf = mime.includes('pdf') || lower.endsWith('.pdf') || key.toLowerCase().includes('pdf') || key.toLowerCase().includes('planilla');\n if (!isPdf) continue;\n out.push({\n json: { runId: base.json.runId, mes: base.json.mes, anio: base.json.anio, fileName, originalBinaryKey: key },\n binary: { data: file },\n });\n}\nif (!out.length) {\n throw new Error('No encontr茅 planillas IHSS en PDF. Revisa que el campo de planillas acepte .pdf y que hayas adjuntado al menos un PDF.');\n}\nconst data = $getWorkflowStaticData('global');\nif (data.hnCruceIHSSNomina?.[base.json.runId]) {\n data.hnCruceIHSSNomina[base.json.runId].pdfFiles = out.map(i => i.json.fileName);\n}\nreturn out;"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 128,
+ 96
+ ],
+ "id": "cce199b0-319f-4c8a-a2aa-d6abcf820c87",
+ "name": "Separar planillas PDF"
+ },
+ {
+ "parameters": {
+ "operation": "pdf",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.extractFromFile",
+ "typeVersion": 1.1,
+ "position": [
+ 368,
+ 96
+ ],
+ "id": "1c2d343d-0e91-4591-bf6f-72378f7a020a",
+ "name": "Extraer texto PDF"
+ },
+ {
+ "parameters": {
+ "jsCode": "function normalizeText(s) {\n return String(s || '').replace(/\\r/g, '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').replace(/脵/g, 'U').replace(/霉/g, 'u');\n}\nfunction clean(s) { return String(s || '').replace(/\\s+/g, ' ').trim(); }\nfunction cleanNameLine(s) { return clean(String(s || '').replace(/\\s+\\d{1,2}\\s+[\\d,.]+.*$/, '')); }\nfunction toNumber(v) {\n if (v === null || v === undefined || v === '') return 0;\n let s = String(v).replace(/LPS|L|Q|USD|\\$|,/gi, '').replace(/\\s/g, '').trim();\n if (/^\\((.*)\\)$/.test(s)) s = '-' + s.replace(/^\\(|\\)$/g, '');\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n}\nfunction money(n) { return Number((Number(n) || 0).toFixed(2)); }\nfunction firstMatch(text, patterns) { for (const p of patterns) { const m = text.match(p); if (m) return m[1] || m[0]; } return ''; }\nfunction numMatch(text, patterns) { const v = firstMatch(text, patterns); return v ? toNumber(v) : 0; }\nfunction unique13Digits(raw) {\n const found = String(raw || '').match(/\\b\\d{13}\\b/g) || [];\n return [...new Set(found)].filter(id => id !== '0000000000000');\n}\nfunction cleanNamesFromBlock(block) {\n return String(block || '').split('\\n')\n .map(cleanNameLine)\n .filter(l => l && !/^\\d+$/.test(l))\n .filter(l => /[A-Z脩]/i.test(l))\n .filter(l => !/SALARIO|DIAS|TRABAJADOR|PATRONO|ESTADO|PERIODO|RIESGO|PAGINA|CUOTA|RECIBO|MONTO|TOTAL|PERSONA JURIDICA|PRIVADO|PUBLICIDAD|SECTOR|ACTIVIDAD|DIRECCION|TELEFONO|ZONA|CUADRA|NO\\.?\\s*AFILIACION|IDENTIDAD|NOMBRE Y APELLIDO/i.test(l));\n}\nfunction expectedWorkers(text) {\n const nums = [...String(text || '').matchAll(/TRABAJADORES\\s+(\\d{1,4})/gi)].map(m => Number(m[1])).filter(n => Number.isFinite(n) && n > 0);\n return nums.length ? Math.max(...nums) : 0;\n}\nfunction addCandidate(store, ids, names, meta) {\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i];\n if (!id || id === '0000000000000') continue;\n if (!store.has(id)) {\n store.set(id, { identidad: id, nombre_planilla: clean(names[i] || ''), archivo_planilla: meta.fileName, numero_patronal: meta.patronal, recibo: meta.recibo, periodo: meta.periodo, cuota_trabajador: 0 });\n } else {\n const ex = store.get(id);\n if (!ex.nombre_planilla && clean(names[i] || '')) ex.nombre_planilla = clean(names[i]);\n }\n }\n}\nfunction extractCandidatesFromText(text, meta) {\n const store = new Map();\n const reA = /NO\\.?\\s*AFILIACION([\\s\\S]*?)IDENTIDAD([\\s\\S]*?)NOMBRE\\s+Y\\s+APELLIDO/gi;\n let m;\n while ((m = reA.exec(text)) !== null) addCandidate(store, unique13Digits(m[1]), cleanNamesFromBlock(m[2]), meta);\n const reB = /IDENTIDAD([\\s\\S]*?)NO\\.?\\s*AFILIACION[\\s\\S]*?NOMBRE\\s+Y\\s+APELLIDO([\\s\\S]*?)(?:\\n\\s*SALARIO|\\n\\s*30\\s|\\n\\s*Hago\\s+Constar|\\f|$)/gi;\n while ((m = reB.exec(text)) !== null) addCandidate(store, unique13Digits(m[1]), cleanNamesFromBlock(m[2]), meta);\n const reC = /IDENTIDAD([\\s\\S]*?)NOMBRE\\s+Y\\s+APELLIDO/gi;\n while ((m = reC.exec(text)) !== null) {\n const ids = unique13Digits(m[1]);\n if (ids.length) addCandidate(store, ids, [], meta);\n }\n const expected = expectedWorkers(text);\n if ((expected && store.size < Math.ceil(expected * 0.8)) || store.size === 0) addCandidate(store, unique13Digits(text), [], meta);\n return [...store.values()];\n}\nfunction extractPlanillaSummary(text, meta) {\n const trabajadores = numMatch(text, [/TRABAJADORES\\s+(\\d{1,4})/i]);\n const salario = numMatch(text, [/MONTO\\s+DE\\s+SALARIOS\\s+([\\d,.]+)/i]);\n const cuotaTrabajador = numMatch(text, [/CUOTA\\s+TRABAJADOR\\s+([\\d,.]+)/i]);\n const cuotaPatrono = numMatch(text, [/CUOTA\\s+PATRONO\\s+([\\d,.]+)/i]);\n const totalSinRecargo = money(cuotaTrabajador + cuotaPatrono);\n return {\n archivo_planilla: meta.fileName,\n numero_patronal: meta.patronal,\n recibo: meta.recibo,\n periodo: meta.periodo,\n trabajadores,\n salario_reportado: money(salario),\n cuota_trabajador: money(cuotaTrabajador),\n cuota_patrono: money(cuotaPatrono),\n total_planilla: totalSinRecargo,\n };\n}\nfunction periodoKey(v) { return String(v || '').replace(/\\s/g, '').trim(); }\nfunction planillaKey(p) {\n return [p.numero_patronal || '', p.recibo || '', periodoKey(p.periodo), p.trabajadores || '', money(p.cuota_trabajador || 0)].join('|');\n}\nconst init = $items('Inicializar memoria')[0].json;\nconst runId = init.runId;\nconst expectedPeriodo = `${String(init.mes || '').padStart(2, '0')}/${init.anio}`;\nconst items = $input.all();\nconst sourcePdfItems = $items('Separar planillas PDF');\nconst all = [];\nconst planillas = [];\nconst duplicatePlanillas = [];\nconst periodoMismatches = [];\nconst seenPlanillas = new Map();\nfor (let idx = 0; idx < items.length; idx++) {\n const item = items[idx];\n const source = sourcePdfItems[idx]?.json || {};\n const fileName = item.json.fileName || source.fileName || source.originalBinaryKey || item.json.file || item.json.originalBinaryKey || `Planilla PDF ${idx + 1}`;\n const text = normalizeText(item.json.text || item.json.data || item.json.content || '');\n if (!text.trim()) continue;\n const meta = {\n fileName,\n patronal: firstMatch(text, [/([0-9]{3}-[0-9]{4}-[0-9]{4}-[0-9])\\s*NUMERO\\s+PATRONAL/i, /NUMERO\\s+PATRONAL\\s*\\n?\\s*([0-9]{3}-[0-9]{4}-[0-9]{4}-[0-9])/i, /\\b([0-9]{3}-[0-9]{4}-[0-9]{4}-[0-9])\\b/i]),\n periodo: firstMatch(text, [/(\\d{2}\\s*\\/\\s*\\d{4})\\s*PERIODO/i, /PERIODO\\s*\\n?\\s*(\\d{2}\\s*\\/\\s*\\d{4})/i]),\n recibo: firstMatch(text, [/RECIBO\\s+NUMERO\\s*\\n?\\s*(\\d{5,})/i, /RECIBO\\s+DE\\s+PAGO[\\s\\S]{0,80}?\\b(\\d{6,})\\b/i, /\\*(\\d{6,})\\*/]),\n };\n const planilla = extractPlanillaSummary(text, meta);\n const candidates = extractCandidatesFromText(text, meta);\n const uniqueWorkersInPdf = new Set(candidates.map(r => r.identidad).filter(Boolean)).size;\n\n // Producci贸n: algunos PDFs no exponen bien TRABAJADORES / MONTO DE SALARIOS en texto plano.\n if (!planilla.trabajadores || planilla.trabajadores < 1) planilla.trabajadores = uniqueWorkersInPdf;\n if (!planilla.salario_reportado || planilla.salario_reportado < 1000) {\n planilla.salario_reportado = planilla.cuota_trabajador\n ? money(planilla.cuota_trabajador / 0.05)\n : money(uniqueWorkersInPdf * 11903.13);\n }\n\n // Producci贸n: si cargan el mismo PDF/recibo dos veces, NO se suma dos veces.\n const key = planillaKey(planilla);\n if (seenPlanillas.has(key)) {\n duplicatePlanillas.push({ ...planilla, archivo_original: seenPlanillas.get(key).archivo_planilla, archivo_duplicado: fileName });\n continue;\n }\n seenPlanillas.set(key, planilla);\n\n // Control de per铆odo: no detiene el flujo, pero queda visible como hallazgo.\n if (planilla.periodo && periodoKey(planilla.periodo) !== expectedPeriodo) {\n periodoMismatches.push({ archivo_planilla: fileName, periodo_detectado: planilla.periodo, periodo_esperado: expectedPeriodo });\n }\n\n planillas.push(planilla);\n const cuotaPorPersona = planilla.trabajadores ? money(planilla.cuota_trabajador / planilla.trabajadores) : 0;\n all.push(...candidates.map(r => ({...r, cuota_trabajador: cuotaPorPersona})));\n}\nconst byId = new Map();\nfor (const r of all) {\n if (!r.identidad) continue;\n if (!byId.has(r.identidad)) {\n byId.set(r.identidad, { ...r, archivos_planilla: r.archivo_planilla ? [r.archivo_planilla] : [], patronales: r.numero_patronal ? [r.numero_patronal] : [], recibos: r.recibo ? [r.recibo] : [] });\n } else {\n const ex = byId.get(r.identidad);\n if (r.nombre_planilla && !ex.nombre_planilla) ex.nombre_planilla = r.nombre_planilla;\n if (r.archivo_planilla && !ex.archivos_planilla.includes(r.archivo_planilla)) ex.archivos_planilla.push(r.archivo_planilla);\n if (r.numero_patronal && !ex.patronales.includes(r.numero_patronal)) ex.patronales.push(r.numero_patronal);\n if (r.recibo && !ex.recibos.includes(r.recibo)) ex.recibos.push(r.recibo);\n if (!ex.cuota_trabajador && r.cuota_trabajador) ex.cuota_trabajador = r.cuota_trabajador;\n }\n}\nconst registros = [...byId.values()].map(r => ({ identidad: r.identidad, nombre_planilla: r.nombre_planilla || '', archivos_planilla: r.archivos_planilla.join(' | '), numero_patronal: r.patronales.join(' | '), recibo: r.recibos.join(' | '), periodo: r.periodo || '', cuota_trabajador: money(r.cuota_trabajador) }));\nif (!registros.length) throw new Error('No pude extraer identidades de las planillas IHSS. Revisa que los PDF sean legibles y que no sean escaneos sin texto.');\nconst data = $getWorkflowStaticData('global');\ndata.hnCruceIHSSNomina = data.hnCruceIHSSNomina || {};\ndata.hnCruceIHSSNomina[runId] = data.hnCruceIHSSNomina[runId] || { ihss: [], nomina: [] };\ndata.hnCruceIHSSNomina[runId].ihss = registros;\ndata.hnCruceIHSSNomina[runId].ihssPlanillas = planillas;\ndata.hnCruceIHSSNomina[runId].duplicatePlanillas = duplicatePlanillas;\ndata.hnCruceIHSSNomina[runId].periodoMismatches = periodoMismatches;\nreturn [{ json: { runId, ihssCount: registros.length, planillasCount: planillas.length, duplicatePlanillasCount: duplicatePlanillas.length, periodoMismatchesCount: periodoMismatches.length, pdfFiles: [...new Set(registros.flatMap(r => String(r.archivos_planilla).split(' | ').filter(Boolean)))] } }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 608,
+ 96
+ ],
+ "id": "1b5b71d1-2008-4ff3-b2e8-b3b327a7e819",
+ "name": "Guardar IHSS en memoria"
+ },
+ {
+ "parameters": {
+ "jsCode": "const base = $input.all()[0];\nconst bin = base.binary || {};\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '').toLowerCase();\n const lower = fileName.toLowerCase();\n const isExcel = lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.csv') || mime.includes('spreadsheet') || mime.includes('excel') || key.toLowerCase().includes('nomina');\n if (isExcel && !lower.endsWith('.pdf')) candidates.push([key, file]);\n}\nif (!candidates.length) {\n throw new Error('No encontr茅 el archivo de n贸mina. Debe ser .xlsx, .xls o .csv en el campo Nomina.');\n}\nif (candidates.length > 1) {\n // Prefer the binary field whose key includes Nomina; otherwise use the first Excel-like file.\n const idx = candidates.findIndex(([key]) => key.toLowerCase().includes('nomina'));\n const chosen = candidates[idx >= 0 ? idx : 0];\n candidates.length = 0;\n candidates.push(chosen);\n}\nconst [key, file] = candidates[0];\nconst data = $getWorkflowStaticData('global');\nif (data.hnCruceIHSSNomina?.[base.json.runId]) data.hnCruceIHSSNomina[base.json.runId].payrollFile = file.fileName || key;\nreturn [{ json: { runId: base.json.runId, mes: base.json.mes, anio: base.json.anio, payrollFile: file.fileName || key }, binary: { data: file } }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 128,
+ 528
+ ],
+ "id": "1ec130f8-c6bc-4c42-8c4b-0117bd3901f4",
+ "name": "Preparar n贸mina"
+ },
+ {
+ "parameters": {
+ "operation": "update",
+ "fileId": {
+ "__rl": true,
+ "value": "1LNJtm2ITumAFGQwWmHXpoeSOWXBXjdTDpjSBBwqnHd4",
+ "mode": "list",
+ "cachedResultName": "Nomina Honduras",
+ "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1LNJtm2ITumAFGQwWmHXpoeSOWXBXjdTDpjSBBwqnHd4/edit"
+ },
+ "changeFileContent": true,
+ "options": {}
+ },
+ "type": "n8n-nodes-base.googleDrive",
+ "typeVersion": 3,
+ "position": [
+ 368,
+ 528
+ ],
+ "id": "da97a37f-7878-4f9a-a9a6-73c19c4abe47",
+ "name": "Actualizar staging N贸mina",
+ "credentials": {
+ "googleDriveOAuth2Api": {
+ "id": "g23xdGLZRzBGqKgH",
+ "name": "Isaac - Google Drive"
+ }
+ }
+ },
+ {
+ "parameters": {},
+ "type": "n8n-nodes-base.wait",
+ "typeVersion": 1.1,
+ "position": [
+ 592,
+ 528
+ ],
+ "id": "505b8ad3-db01-4ab5-a8f7-4741f846fb2b",
+ "name": "Esperar conversi贸n",
+ "webhookId": "wait-conversion-nomina"
+ },
+ {
+ "parameters": {
+ "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1LNJtm2ITumAFGQwWmHXpoeSOWXBXjdTDpjSBBwqnHd4' }}?fields=sheets(properties(title,sheetId,hidden))",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 816,
+ 528
+ ],
+ "id": "35aa0ca4-2e93-4c92-9813-e36993dd1a67",
+ "name": "Listar hojas N贸mina",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst spreadsheetId = $json.spreadsheetId || $node['Actualizar staging N贸mina'].json.id || '1LNJtm2ITumAFGQwWmHXpoeSOWXBXjdTDpjSBBwqnHd4';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]/g, ''); }\nconst ignoreBase = /DO NOT DELETE|NVScriptsProperties|AutoCrat|Resumen|Presupuesto|Retroactivo|Combustible|Combustibles/i;\nconst sheets = ($json.sheets || []).map(s => s.properties || {}).filter(p => p.title && !p.hidden);\nconst out = [];\nfor (const p of sheets) {\n const title = String(p.title || '');\n const n = norm(title);\n if (n === 'ihssrap' || (n.includes('ihss') && n.includes('rap'))) {\n out.push({ json: { runId: init.runId, spreadsheetId, sheetTitle: title, sheetId: p.sheetId, sheetKind: 'ihss_rap' } });\n continue;\n }\n if (!ignoreBase.test(title) && !/IHSS|RAP/i.test(title)) {\n out.push({ json: { runId: init.runId, spreadsheetId, sheetTitle: title, sheetId: p.sheetId, sheetKind: 'nomina_base' } });\n }\n}\nif (!out.length) throw new Error('No encontr茅 hojas v谩lidas de n贸mina. Revisa que el archivo tenga hojas de pago/base y la hoja IHSS-RAP.');\nreturn out;"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 1056,
+ 528
+ ],
+ "id": "e301de8e-430d-4c49-af9b-5606811736bc",
+ "name": "Preparar hojas N贸mina"
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "type": "n8n-nodes-base.splitInBatches",
+ "typeVersion": 3,
+ "position": [
+ 1280,
+ 528
+ ],
+ "id": "817360d8-d0ce-4d3e-bcb9-f22b1435d503",
+ "name": "Loop hojas N贸mina"
+ },
+ {
+ "parameters": {
+ "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.spreadsheetId }}/values/{{ encodeURIComponent(\"'\" + String($json.sheetTitle).replace(/'/g, \"''\") + \"'!A1:AZ1000\") }}?majorDimension=ROWS&valueRenderOption=FORMATTED_VALUE&dateTimeRenderOption=FORMATTED_STRING",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 1552,
+ 720
+ ],
+ "id": "610e431e-ee51-4e68-b425-0fb6b09e2cad",
+ "name": "Leer hoja N贸mina",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "jsCode": "function normHeader(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]/g, ''); }\nfunction clean(v) { return String(v ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction cleanName(v) { return clean(v); }\nfunction plain(v) { return clean(v).normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction toNumber(v) {\n if (v === null || v === undefined || v === '') return 0;\n let s = String(v).replace(/LPS|L|Q|USD|\\$|,/gi, '').replace(/\\s/g, '').trim();\n if (/^\\((.*)\\)$/.test(s)) s = '-' + s.replace(/^\\(|\\)$/g, '');\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n}\nfunction money(n) { return Number((Number(n) || 0).toFixed(2)); }\nfunction normalizeId(v) {\n if (v === undefined || v === null) return '';\n let s = String(v).trim();\n if (/^\\d+(\\.0+)?$/.test(s)) s = s.replace(/\\.0+$/, '');\n const d = s.replace(/\\D/g, '');\n if (d.length !== 13 || d === '0000000000000') return '';\n return d;\n}\nfunction findHeader(values) {\n const maxScan = Math.min(values.length, 40);\n for (let r = 0; r < maxScan; r++) {\n const row = values[r] || [];\n const headers = row.map(normHeader);\n const idCol = headers.findIndex(h => h === 'identidad' || h === 'identida' || h === 'cedula' || h === 'idcedula' || h === 'documento' || h === 'nodocumento');\n const nameCol = headers.findIndex(h => h === 'nombre' || h === 'nombrecompleto' || h === 'empleado' || h === 'beneficiario' || h === 'colaborador' || h.startsWith('nombre'));\n if (idCol >= 0 && nameCol >= 0) return { headerRow: r, idCol, nameCol };\n }\n return null;\n}\nfunction isSpecialContextLine(line) {\n return /visado|walmart|personal\\s+especial|personal\\s+para\\s+visado|personal\\s+temporal/i.test(line);\n}\nfunction extractPersonCount(line) {\n const m = plain(line).match(/(\\d{1,3})\\s*(personas?|colaboradores?|empleados?)/i);\n return m ? Number(m[1]) : 0;\n}\nfunction isLikelySpecialName(s) {\n const raw = clean(s);\n const p = plain(raw);\n if (!raw || raw.length < 3) return false;\n if (!/[A-Za-z脕脡脥脫脷脩谩茅铆贸煤帽]/.test(raw)) return false;\n if (/empleado|patrono|total|planilla|nomina|diferencia|moneda|monto|ihss|rap|observacion|cuota|lps|salario/i.test(p)) return false;\n return true;\n}\nfunction getSpecialPairsFromRow(cells) {\n const pairs = [];\n // Formato observado en GLM Honduras: columnas J/K = referencia corta + monto.\n const candidateColumns = [9, 7, 8, 10, 11];\n for (const idx of candidateColumns) {\n const name = cells[idx] || '';\n const amount = toNumber(cells[idx + 1] || '');\n if (isLikelySpecialName(name) && amount > 0) {\n pairs.push({ referencia: name, monto: money(amount) });\n }\n }\n // Fallback: buscar cualquier par nombre/monto contiguo al final de la fila.\n for (let i = 0; i < cells.length - 1; i++) {\n const name = cells[i] || '';\n const amount = toNumber(cells[i + 1] || '');\n if (isLikelySpecialName(name) && amount > 0) {\n if (!pairs.some(p => plain(p.referencia) === plain(name) && p.monto === money(amount))) {\n pairs.push({ referencia: name, monto: money(amount) });\n }\n }\n }\n return pairs;\n}\n\nconst runId = $items('Inicializar memoria')[0].json.runId;\nlet sheetTitle = '';\nlet sheetKind = 'nomina_base';\ntry { sheetTitle = $node['Loop hojas N贸mina'].json.sheetTitle || ''; sheetKind = $node['Loop hojas N贸mina'].json.sheetKind || 'nomina_base'; } catch (e) {}\nsheetTitle = sheetTitle || 'Hoja de n贸mina';\nconst values = $json.values || [];\nconst data = $getWorkflowStaticData('global');\ndata.hnCruceIHSSNomina = data.hnCruceIHSSNomina || {};\ndata.hnCruceIHSSNomina[runId] = data.hnCruceIHSSNomina[runId] || { ihss: [], nomina: [], payrollIhssSummary: {}, payrollIhssBlocks: [], payrollSpecialEmployees: [] };\n\nif (sheetKind === 'ihss_rap') {\n const blocks = [];\n const specialEmployees = [];\n const summary = {\n totalNominaIhss: 0,\n ajusteIhss: 0,\n totalNominaIhssAjustado: 0,\n totalPlanillasIhss: 0,\n diferenciaIhss: 0,\n observacionIhss: '',\n ajusteIhssMotivo: ''\n };\n\n let inIhss = false;\n let specialContextActive = false;\n let specialContextLabel = '';\n let expectedSpecialCount = 0;\n let collectedInContext = 0;\n let specialBlockAmount = 0;\n\n for (const row of values) {\n const cells = (row || []).map(clean);\n const lineOriginal = cells.join(' ');\n const line = plain(lineOriginal);\n\n if (line.includes('ihss') && !line.includes('rap')) inIhss = true;\n if (line.includes('rap aportacion') || line.includes('rap reserva')) {\n inIhss = false;\n specialContextActive = false;\n }\n\n const concept = cells[1] || cells[0] || '';\n const amount = toNumber(cells[3] || cells[2] || '');\n const conceptNorm = plain(concept);\n\n if (conceptNorm.includes('total nomina')) {\n summary.totalNominaIhss = money(amount);\n specialContextActive = false;\n } else if (conceptNorm.includes('total planilla')) {\n summary.totalPlanillasIhss = money(amount);\n specialContextActive = false;\n } else if (inIhss && conceptNorm.includes('diferencia')) {\n summary.diferenciaIhss = money(amount);\n summary.observacionIhss = cells.slice(4).filter(Boolean).join(' ');\n specialContextActive = false;\n } else if (inIhss && concept && amount && !/ihss|moneda|empleado|total|planilla|diferencia/i.test(concept)) {\n blocks.push({ concepto: concept, moneda: cells[2] || '', monto: money(amount) });\n\n // PRODUCCI脫N: un ajuste de personal solo se suma si el bloque lo declara expl铆citamente\n // como visado/Walmart/personal especial. Esto evita inflar mayo por observaciones normales.\n if (isSpecialContextLine(lineOriginal) || isSpecialContextLine(concept)) {\n specialBlockAmount = money(specialBlockAmount + amount);\n specialContextActive = true;\n specialContextLabel = concept;\n expectedSpecialCount = extractPersonCount(lineOriginal) || extractPersonCount(concept) || 0;\n collectedInContext = 0;\n }\n }\n\n if (inIhss && isSpecialContextLine(lineOriginal)) {\n specialContextActive = true;\n specialContextLabel = concept || lineOriginal;\n expectedSpecialCount = extractPersonCount(lineOriginal) || expectedSpecialCount || 0;\n collectedInContext = 0;\n }\n\n // Caso Honduras marzo: empleados fuera de hojas Base incluidos expl铆citamente en IHSS-RAP\n // como \"Personal para visado de Walmart\". Solo se leen nombres/montos si ese contexto existe.\n if (inIhss && specialContextActive) {\n const pairs = getSpecialPairsFromRow(cells);\n for (const pair of pairs) {\n if (!specialEmployees.some(e => plain(e.referencia) === plain(pair.referencia) && e.monto === pair.monto)) {\n specialEmployees.push({ referencia: pair.referencia, monto: pair.monto, fuente: sheetTitle, contexto: specialContextLabel || 'Personal especial / visado Walmart' });\n collectedInContext++;\n }\n }\n if (expectedSpecialCount && collectedInContext >= expectedSpecialCount) {\n specialContextActive = false;\n }\n }\n }\n\n const specialTotal = money(specialEmployees.reduce((a, e) => a + Number(e.monto || 0), 0));\n\n // Regla final de producci贸n:\n // - Si hay empleados especiales expl铆citos, se suma su total.\n // - Si no se leyeron empleados pero hay bloque expl铆cito de visado/Walmart, se suma el monto del bloque.\n // - Si no hay contexto expl铆cito, ajuste = 0.00.\n summary.ajusteIhss = money(specialTotal || specialBlockAmount || 0);\n summary.ajusteIhssMotivo = summary.ajusteIhss\n ? (specialEmployees.length ? 'Personal especial/visado detectado expl铆citamente en IHSS-RAP.' : 'Bloque especial/visado detectado expl铆citamente en IHSS-RAP.')\n : 'Sin ajuste especial expl铆cito.';\n summary.totalNominaIhssAjustado = money(summary.totalNominaIhss + summary.ajusteIhss);\n\n // La diferencia productiva de IHSS debe usar TOTAL N贸mina ajustado - Total Planillas.\n // Evita capturar diferencias de RAP Aportaci贸n / RAP Reserva cuando la hoja tiene varias secciones.\n if (summary.totalNominaIhssAjustado || summary.totalPlanillasIhss) {\n summary.diferenciaIhss = money(summary.totalNominaIhssAjustado - summary.totalPlanillasIhss);\n }\n if (!summary.observacionIhss && Math.abs(summary.diferenciaIhss || 0) > 0.05) {\n summary.observacionIhss = summary.ajusteIhss\n ? 'La hoja IHSS-RAP incluye ajuste de personal especial/visado. Diferencia restante corresponde a redondeo o ajuste menor.'\n : 'La hoja IHSS-RAP muestra diferencia en IHSS. Revisar soporte/observaci贸n interna de n贸mina con Administraci贸n.';\n }\n\n data.hnCruceIHSSNomina[runId].payrollIhssSummary = summary;\n data.hnCruceIHSSNomina[runId].payrollIhssBlocks = blocks;\n data.hnCruceIHSSNomina[runId].payrollSpecialEmployees = specialEmployees;\n return [{ json: { runId, sheetTitle, sheetKind, identitiesFound: 0, ihssSummaryFound: Boolean(summary.totalNominaIhss || summary.totalPlanillasIhss), ajusteIhss: summary.ajusteIhss, specialEmployeesFound: specialEmployees.length } }];\n}\n\nconst header = findHeader(values);\nconst records = [];\nif (header) {\n for (let r = header.headerRow + 1; r < values.length; r++) {\n const row = values[r] || [];\n const identidad = normalizeId(row[header.idCol]);\n if (!identidad) continue;\n const nombre = cleanName(row[header.nameCol]);\n if (!nombre || /^total/i.test(nombre)) continue;\n records.push({ identidad, nombre_nomina: nombre, hoja_nomina: sheetTitle });\n }\n}\ndata.hnCruceIHSSNomina[runId].nomina.push(...records);\nreturn [{ json: { runId, sheetTitle, sheetKind, identitiesFound: records.length, headerDetected: Boolean(header), headerRow: header ? header.headerRow + 1 : '' } }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 1792,
+ 720
+ ],
+ "id": "5c268a10-b7bb-438b-b6fa-1f10f278ca9e",
+ "name": "Acumular identidades N贸mina"
+ },
+ {
+ "parameters": {},
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.2,
+ "position": [
+ 2320,
+ 368
+ ],
+ "id": "0a92d6f6-78cf-494e-851d-927fe8655441",
+ "name": "Esperar ramas"
+ },
+ {
+ "parameters": {
+ "jsCode": "function clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction money(n) { return Number((Number(n) || 0).toFixed(2)); }\nfunction fmt(n) { return Number(n || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }\nfunction normalizeId(v) {\n const d = String(v ?? '').replace(/\\D/g, '');\n if (d.length !== 13 || d === '0000000000000') return '';\n return d;\n}\nfunction plain(s) { return clean(s).normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction dedupe(records, source) {\n const map = new Map();\n for (const r of records || []) {\n const id = normalizeId(r.identidad);\n if (!id) continue;\n if (!map.has(id)) map.set(id, { identidad: id, names: new Set(), sheets: new Set(), files: new Set(), patronales: new Set(), recibos: new Set(), cuota_trabajador: 0, raw: [] });\n const x = map.get(id);\n if (source === 'ihss') {\n if (clean(r.nombre_planilla)) x.names.add(clean(r.nombre_planilla));\n if (clean(r.archivos_planilla)) String(r.archivos_planilla).split(' | ').forEach(v => v && x.files.add(v));\n if (clean(r.numero_patronal)) String(r.numero_patronal).split(' | ').forEach(v => v && x.patronales.add(v));\n if (clean(r.recibo)) String(r.recibo).split(' | ').forEach(v => v && x.recibos.add(v));\n if (r.cuota_trabajador) x.cuota_trabajador = money(r.cuota_trabajador);\n } else {\n if (clean(r.nombre_nomina)) x.names.add(clean(r.nombre_nomina));\n if (clean(r.hoja_nomina)) x.sheets.add(clean(r.hoja_nomina));\n }\n x.raw.push(r);\n }\n return map;\n}\nfunction referenceMatchesName(reference, fullName) {\n const ref = plain(reference);\n const name = plain(fullName);\n if (!ref || !name) return false;\n const refTokens = ref.split(/\\s+/).filter(t => t.length >= 3 && !['para','personal','visado','walmart'].includes(t));\n if (!refTokens.length) return false;\n return refTokens.every(t => name.includes(t) || name.includes(t.replace(/ck/g, 'k')) || name.includes(t.replace(/k/g, 'ck')));\n}\nconst init = $items('Inicializar memoria')[0].json;\nconst runId = init.runId;\nconst data = $getWorkflowStaticData('global');\nconst slot = data.hnCruceIHSSNomina?.[runId];\nif (!slot) throw new Error(`No encontr茅 memoria temporal para la ejecuci贸n ${runId}.`);\nconst ihss = dedupe(slot.ihss, 'ihss');\nconst nomina = dedupe(slot.nomina, 'nomina');\nif (ihss.size === 0) throw new Error('No se extrajo ninguna identidad desde las planillas IHSS.');\nif (nomina.size === 0) throw new Error('No se extrajo ninguna identidad desde la n贸mina.');\n\n// Producci贸n Honduras: resolver empleados especiales de IHSS-RAP (ej. visado Walmart) por nombre contra planilla IHSS.\n// Esto evita que aparezcan como \"Solo IHSS\" cuando la n贸mina los trae solo en el resumen IHSS-RAP sin identidad.\nconst specialEmployees = slot.payrollSpecialEmployees || [];\nconst resolvedSpecialEmployees = [];\nconst unresolvedSpecialEmployees = [];\nfor (const sp of specialEmployees) {\n const candidates = [...ihss.values()].filter(r => {\n if (nomina.has(r.identidad)) return false;\n const names = [...r.names];\n return names.some(n => referenceMatchesName(sp.referencia, n));\n });\n if (candidates.length === 1) {\n const r = candidates[0];\n nomina.set(r.identidad, {\n identidad: r.identidad,\n names: new Set([...r.names, `IHSS-RAP: ${sp.referencia}`]),\n sheets: new Set([sp.fuente || 'IHSS-RAP', 'Personal especial / visado Walmart']),\n files: new Set(), patronales: new Set(), recibos: new Set(), cuota_trabajador: 0,\n raw: [{ identidad: r.identidad, nombre_nomina: [...r.names][0] || sp.referencia, hoja_nomina: sp.fuente || 'IHSS-RAP', fuente_especial: 'Personal especial / visado Walmart', monto: sp.monto }]\n });\n resolvedSpecialEmployees.push({ referencia: sp.referencia, identidad: r.identidad, nombre: [...r.names][0] || '', monto: sp.monto, estado: 'Resuelto por nombre contra IHSS' });\n } else {\n unresolvedSpecialEmployees.push({ referencia: sp.referencia, monto: sp.monto, coincidencias: candidates.length });\n }\n}\n\n// Fallback de producci贸n: si IHSS-RAP trae un bloque especial con monto, pero no se pudieron leer\n// los nombres de las columnas internas, se resuelve contra los empleados que est谩n solo en IHSS.\n// Solo se aplica cuando la cantidad faltante coincide con el monto especial / cuota IHSS por empleado.\nconst payrollAdjustmentForSpecial = money((slot.payrollIhssSummary || {}).ajusteIhss || 0);\nif (!resolvedSpecialEmployees.length && payrollAdjustmentForSpecial > 0.05) {\n const pendingIHSS = [...ihss.values()].filter(r => !nomina.has(r.identidad));\n const avgCuota = pendingIHSS.length\n ? money(pendingIHSS.reduce((a, r) => a + Number(r.cuota_trabajador || 0), 0) / pendingIHSS.length)\n : 0;\n const expectedSpecialCount = avgCuota ? Math.round(payrollAdjustmentForSpecial / avgCuota) : 0;\n const amountMatches = avgCuota && Math.abs(payrollAdjustmentForSpecial - money(avgCuota * pendingIHSS.length)) <= 1.00;\n if (pendingIHSS.length > 0 && pendingIHSS.length === expectedSpecialCount && amountMatches) {\n for (const r of pendingIHSS) {\n const nm = [...r.names][0] || '';\n nomina.set(r.identidad, {\n identidad: r.identidad,\n names: new Set([...r.names, 'IHSS-RAP: Personal especial / visado Walmart']),\n sheets: new Set(['IHSS-RAP', 'Personal especial / visado Walmart']),\n files: new Set(), patronales: new Set(), recibos: new Set(), cuota_trabajador: 0,\n raw: [{ identidad: r.identidad, nombre_nomina: nm, hoja_nomina: 'IHSS-RAP', fuente_especial: 'Personal especial / visado Walmart', monto: r.cuota_trabajador }]\n });\n resolvedSpecialEmployees.push({ referencia: 'Personal especial / visado Walmart', identidad: r.identidad, nombre: nm, monto: r.cuota_trabajador, estado: 'Resuelto por monto contra empleados solo IHSS' });\n }\n }\n}\n\nconst soloIHSS = [...ihss.values()].filter(r => !nomina.has(r.identidad));\nconst soloNomina = [...nomina.values()].filter(r => !ihss.has(r.identidad));\nconst ambas = [...ihss.keys()].filter(id => nomina.has(id)).length;\nconst diferenciasIdentidad = soloIHSS.length + soloNomina.length;\nconst planillas = slot.ihssPlanillas || [];\nconst duplicatePlanillas = slot.duplicatePlanillas || [];\nconst periodoMismatches = slot.periodoMismatches || [];\nconst pdfTrabajadores = planillas.reduce((a,p) => a + Number(p.trabajadores || 0), 0);\nconst pdfSalario = money(planillas.reduce((a,p) => a + Number(p.salario_reportado || 0), 0));\nconst pdfCuotaTrabajador = money(planillas.reduce((a,p) => a + Number(p.cuota_trabajador || 0), 0));\nconst pdfCuotaPatrono = money(planillas.reduce((a,p) => a + Number(p.cuota_patrono || 0), 0));\nconst pdfTotalPlanilla = money(planillas.reduce((a,p) => a + Number(p.total_planilla || 0), 0));\nconst payroll = slot.payrollIhssSummary || {};\nconst payrollBaseIhss = money(payroll.totalNominaIhss || 0);\nconst payrollAdjustmentIhss = money(payroll.ajusteIhss || 0);\nconst payrollIhss = money(payroll.totalNominaIhssAjustado || (payrollBaseIhss + payrollAdjustmentIhss));\nconst diffMontos = money(payrollIhss - pdfCuotaTrabajador);\nlet resultadoMontos = 'SIN DIFERENCIAS';\nlet lecturaMontos = 'El total descontado en n贸mina por IHSS coincide con la cuota trabajador de las planillas IHSS.';\nif (Math.abs(diffMontos) > 0.05) {\n resultadoMontos = 'REVISAR DIFERENCIA';\n lecturaMontos = diffMontos > 0 ? 'La n贸mina descont贸 m谩s IHSS que lo reportado como cuota trabajador en las planillas.' : 'La n贸mina descont贸 menos IHSS que lo reportado como cuota trabajador en las planillas.';\n}\nfunction titleCaseName(s) {\n return String(s || '')\n .toLowerCase()\n .replace(/\\b([a-z谩茅铆贸煤帽眉])/gi, c => c.toUpperCase())\n .replace(/\\bDe\\b/g, 'de')\n .replace(/\\bDel\\b/g, 'del')\n .replace(/\\bLa\\b/g, 'la');\n}\nfunction inferEmployeeFromObservation(obs) {\n const text = clean(obs);\n const patterns = [\n /(?:a|de)\\s+([A-Z脕脡脥脫脷脩][A-Za-z脕脡脥脫脷脩谩茅铆贸煤帽眉\\s]{6,80}?)(?:\\s+en\\s+|\\s+por\\s+|\\.|,|$)/i,\n /emplead[oa]\\s+([A-Z脕脡脥脫脷脩][A-Za-z脕脡脥脫脷脩谩茅铆贸煤帽眉\\s]{6,80}?)(?:\\s+en\\s+|\\s+por\\s+|\\.|,|$)/i\n ];\n for (const p of patterns) {\n const m = text.match(p);\n if (m && m[1]) {\n const name = clean(m[1]).replace(/\\s+(1Q|2Q|Colgate|Motorola|Nestle|Whirlpool|GLM).*$/i, '');\n if (name.length >= 6) return titleCaseName(name);\n }\n }\n return '';\n}\nconst observacionMonto = payroll.observacionIhss || '';\nconst empleadoObs = inferEmployeeFromObservation(observacionMonto);\nconst casosMonto = [];\nif (Math.abs(diffMontos) > 0.05) {\n casosMonto.push({\n tipo: Math.abs(diffMontos) <= 1 ? 'Diferencia menor / redondeo' : (diffMontos > 0 ? 'Cobro de m谩s en n贸mina' : 'Cobro de menos en n贸mina'),\n empleado: Math.abs(diffMontos) <= 1 ? 'Redondeo total' : (empleadoObs || 'No identificado autom谩ticamente'),\n monto: Math.abs(diffMontos),\n fuente: observacionMonto ? 'Hoja IHSS-RAP' : 'Diferencia total',\n observacion: observacionMonto || 'La n贸mina y las planillas tienen diferencia, pero la hoja IHSS-RAP no trae una observaci贸n con el empleado espec铆fico.',\n accion: Math.abs(diffMontos) <= 1 ? 'Aceptar como diferencia menor si Administraci贸n confirma redondeo.' : (empleadoObs ? 'Revisar este caso con N贸mina/Administraci贸n.' : 'Revisar detalle interno de n贸mina para identificar el colaborador.')\n });\n}\nconst identidadRows = [];\nidentidadRows.push(['Cruce de identidad - Honduras']);\nidentidadRows.push([`Per铆odo: ${init.periodoLabel || `${init.mes}/${init.anio}`}`]);\nidentidadRows.push(['Resultado', diferenciasIdentidad ? 'REVISAR DIFERENCIAS' : 'SIN DIFERENCIAS']);\nidentidadRows.push(['']);\nidentidadRows.push(['Resumen', 'Cantidad']);\nidentidadRows.push(['Identidades 煤nicas en planillas IHSS', ihss.size]);\nidentidadRows.push(['Identidades 煤nicas en n贸mina', nomina.size]);\nidentidadRows.push(['Coinciden en ambas', ambas]);\nidentidadRows.push(['Diferencias encontradas', diferenciasIdentidad]);\nidentidadRows.push(['Personal especial IHSS-RAP resuelto', resolvedSpecialEmployees.length]);\nidentidadRows.push(['']);\nidentidadRows.push(['Lectura r谩pida']);\nidentidadRows.push([diferenciasIdentidad ? 'Hay empleados que aparecen en una fuente y no en la otra. Revisar las secciones de abajo.' : 'No hay empleados faltantes ni sobrantes entre n贸mina y planillas IHSS.']);\nidentidadRows.push(['']);\nidentidadRows.push(['Personal especial resuelto desde IHSS-RAP']);\nidentidadRows.push(['Referencia', 'Identidad resuelta', 'Nombre en IHSS', 'Monto', 'Estado']);\nif (resolvedSpecialEmployees.length) for (const r of resolvedSpecialEmployees) identidadRows.push([r.referencia, r.identidad, r.nombre, r.monto, r.estado]); else identidadRows.push(['No aplica', '', '', '', '']);\nif (unresolvedSpecialEmployees.length) {\n identidadRows.push(['']);\n identidadRows.push(['Personal especial NO resuelto autom谩ticamente']);\n identidadRows.push(['Referencia', 'Monto', 'Coincidencias posibles']);\n for (const r of unresolvedSpecialEmployees) identidadRows.push([r.referencia, r.monto, r.coincidencias]);\n}\nidentidadRows.push(['']);\nidentidadRows.push(['Empleados en planilla IHSS que NO aparecen en n贸mina']);\nidentidadRows.push(['Identidad', 'Nombre en planilla IHSS', 'N煤mero patronal', 'Archivo planilla', 'Qu茅 significa']);\nif (soloIHSS.length) for (const r of soloIHSS) identidadRows.push([r.identidad, [...r.names].join(' | '), [...r.patronales].join(' | '), [...r.files].join(' | '), 'Est谩 en IHSS, pero no aparece en la n贸mina cargada.']); else identidadRows.push(['Sin diferencias', '', '', '', '']);\nidentidadRows.push(['']);\nidentidadRows.push(['Empleados en n贸mina que NO aparecen en planilla IHSS']);\nidentidadRows.push(['Identidad', 'Nombre en n贸mina', 'Hoja n贸mina', 'Qu茅 significa']);\nif (soloNomina.length) for (const r of soloNomina) identidadRows.push([r.identidad, [...r.names].join(' | '), [...r.sheets].join(' | '), 'Est谩 en n贸mina, pero no aparece en las planillas IHSS cargadas.']); else identidadRows.push(['Sin diferencias', '', '', '']);\nif (duplicatePlanillas.length) {\n identidadRows.push(['']);\n identidadRows.push(['Planillas duplicadas ignoradas']);\n identidadRows.push(['Archivo duplicado', 'Archivo original', 'N煤mero patronal', 'Recibo', 'Per铆odo', 'Cuota trabajador']);\n for (const d of duplicatePlanillas) identidadRows.push([d.archivo_duplicado, d.archivo_original, d.numero_patronal, d.recibo, d.periodo, d.cuota_trabajador]);\n}\nif (periodoMismatches.length) {\n identidadRows.push(['']);\n identidadRows.push(['Alertas de per铆odo']);\n identidadRows.push(['Archivo', 'Per铆odo detectado', 'Per铆odo esperado']);\n for (const p of periodoMismatches) identidadRows.push([p.archivo_planilla, p.periodo_detectado, p.periodo_esperado]);\n}\nconst montosRows = [];\nmontosRows.push(['Cruce de montos IHSS - Honduras']);\nmontosRows.push([`Per铆odo: ${init.periodoLabel || `${init.mes}/${init.anio}`}`]);\nmontosRows.push(['Resultado', resultadoMontos]);\nmontosRows.push(['']);\nmontosRows.push(['Resumen', 'Monto']);\nmontosRows.push(['IHSS descontado en n贸mina base', payrollBaseIhss]);\nmontosRows.push(['Ajuste IHSS personal especial / visado', payrollAdjustmentIhss]);\nmontosRows.push(['IHSS descontado en n贸mina ajustado', payrollIhss]);\nmontosRows.push(['Cuota trabajador seg煤n planillas IHSS', pdfCuotaTrabajador]);\nmontosRows.push(['Diferencia', diffMontos]);\nmontosRows.push(['']);\nmontosRows.push(['Lectura r谩pida']);\nmontosRows.push([lecturaMontos]);\nmontosRows.push(['']);\nmontosRows.push(['Observaci贸n en n贸mina']);\nmontosRows.push([payroll.observacionIhss || (Math.abs(diffMontos) > 0.05 ? 'No se encontr贸 observaci贸n en la hoja IHSS-RAP. Revisar con Administraci贸n.' : 'Sin observaci贸n necesaria.')]);\nmontosRows.push(['']);\nmontosRows.push(['Casos con diferencia']);\nmontosRows.push(['Tipo', 'Empleado / referencia', 'Monto diferencia', 'Fuente', 'Acci贸n sugerida']);\nif (casosMonto.length) {\n for (const c of casosMonto) montosRows.push([c.tipo, c.empleado, c.monto, c.fuente, c.accion]);\n} else {\n montosRows.push(['Sin diferencias', '', '', '', '']);\n}\nmontosRows.push(['']);\nmontosRows.push(['Detalle / observaci贸n del caso']);\nif (casosMonto.length) {\n for (const c of casosMonto) montosRows.push([c.observacion]);\n} else {\n montosRows.push(['No aplica.']);\n}\nmontosRows.push(['']);\nmontosRows.push(['Qu茅 se compara']);\nmontosRows.push(['N贸mina', 'Columna / dato', 'Explicaci贸n']);\nmontosRows.push(['N贸mina Honduras', 'Hoja IHSS-RAP > TOTAL Nomina + ajustes especiales', 'Total mensual descontado al colaborador por IHSS, incluyendo personal especial si est谩 documentado en IHSS-RAP.']);\nmontosRows.push(['Planillas IHSS', 'CUOTA TRABAJADOR', 'Total que IHSS espera cobrar a los colaboradores. No incluye cuota patronal.']);\nconst resumenRows = [];\nresumenRows.push(['Resumen de planillas IHSS - Honduras']);\nresumenRows.push([`Per铆odo: ${init.periodoLabel || `${init.mes}/${init.anio}`}`]);\nresumenRows.push(['']);\nresumenRows.push(['Archivo PDF', 'N煤mero patronal', 'Recibo', 'Trabajadores', 'Salario reportado', 'Cuota trabajador', 'Cuota patrono', 'Total planilla']);\nfor (const p of planillas) resumenRows.push([p.archivo_planilla, p.numero_patronal, p.recibo, p.trabajadores, p.salario_reportado, p.cuota_trabajador, p.cuota_patrono, p.total_planilla]);\nresumenRows.push(['TOTAL', '', '', pdfTrabajadores, pdfSalario, pdfCuotaTrabajador, pdfCuotaPatrono, pdfTotalPlanilla]);\nif (duplicatePlanillas.length) {\n resumenRows.push(['']);\n resumenRows.push(['Duplicados ignorados']);\n resumenRows.push(['Archivo duplicado', 'Archivo original', 'N煤mero patronal', 'Recibo', 'Cuota trabajador']);\n for (const d of duplicatePlanillas) resumenRows.push([d.archivo_duplicado, d.archivo_original, d.numero_patronal, d.recibo, d.cuota_trabajador]);\n}\nconst detalleRows = [];\ndetalleRows.push(['Detalle IHSS seg煤n n贸mina - Honduras']);\ndetalleRows.push([`Per铆odo: ${init.periodoLabel || `${init.mes}/${init.anio}`}`]);\ndetalleRows.push(['']);\ndetalleRows.push(['Concepto / bloque', 'Moneda', 'Monto IHSS n贸mina']);\nfor (const b of (slot.payrollIhssBlocks || [])) detalleRows.push([b.concepto, b.moneda, b.monto]);\ndetalleRows.push(['TOTAL N贸mina IHSS base', 'Lps', payrollBaseIhss]);\ndetalleRows.push(['Ajuste personal especial / visado', 'Lps', payrollAdjustmentIhss]);\ndetalleRows.push(['Motivo ajuste especial', '', payroll.ajusteIhssMotivo || (payrollAdjustmentIhss ? 'Ajuste especial detectado.' : 'Sin ajuste especial expl铆cito.')]);\ndetalleRows.push(['TOTAL N贸mina IHSS ajustado', 'Lps', payrollIhss]);\ndetalleRows.push(['Total Planillas seg煤n n贸mina', 'Lps', money(payroll.totalPlanillasIhss || 0)]);\ndetalleRows.push(['Diferencia seg煤n n贸mina ajustada', 'Lps', diffMontos]);\ndetalleRows.push(['Observaci贸n', '', payroll.observacionIhss || '']);\nif (specialEmployees.length) {\n detalleRows.push(['']);\n detalleRows.push(['Personal especial detectado en IHSS-RAP']);\n detalleRows.push(['Referencia', 'Monto', 'Identidad resuelta', 'Nombre resuelto']);\n for (const sp of specialEmployees) {\n const res = resolvedSpecialEmployees.find(r => plain(r.referencia) === plain(sp.referencia));\n detalleRows.push([sp.referencia, sp.monto, res?.identidad || '', res?.nombre || '']);\n }\n}\nconst summary = { ihss: ihss.size, nomina: nomina.size, ambas, soloIHSS: soloIHSS.length, soloNomina: soloNomina.length, diferenciasIdentidad, pdfFiles: slot.pdfFiles || [], payrollFile: slot.payrollFile || '', planillas: planillas.length, duplicatePlanillas: duplicatePlanillas.length, duplicatePlanillasDetalle: duplicatePlanillas, periodoMismatches: periodoMismatches.length, periodoMismatchesDetalle: periodoMismatches, pdfTrabajadores, pdfCuotaTrabajador, pdfCuotaPatrono, pdfTotalPlanilla, payrollBaseIhss, payrollAdjustmentIhss, payrollIhss, diffMontos, resultadoMontos, observacionIhss: payroll.observacionIhss || '', ajusteIhssMotivo: payroll.ajusteIhssMotivo || '', specialEmployees, resolvedSpecialEmployees, unresolvedSpecialEmployees, casosMonto, casosMontoCount: casosMonto.length, casoMontoPrincipal: casosMonto[0] || null };\ndelete data.hnCruceIHSSNomina[runId];\nreturn [{ json: { runId, reportTitle: init.reportTitle, emailTo: init.emailTo || 'iaracena@gomezleemarketing.com', identidadRows, montosRows, resumenRows, detalleRows, summary, rowCounts: { identidad: identidadRows.length, montos: montosRows.length, resumen: resumenRows.length, detalle: detalleRows.length } } }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 2544,
+ 368
+ ],
+ "id": "e1391cfa-4e6b-428d-a5b2-4495e3428383",
+ "name": "Construir reporte producci贸n"
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "https://sheets.googleapis.com/v4/spreadsheets",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={{ JSON.stringify({ properties: { title: $json.reportTitle }, sheets: [ { properties: { title: 'Cruce Identidad' } }, { properties: { title: 'Cruce Montos IHSS' } }, { properties: { title: 'Resumen Planillas' } }, { properties: { title: 'Detalle N贸mina IHSS' } } ] }) }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 2784,
+ 368
+ ],
+ "id": "286439d1-a4bf-4167-80ec-0e08edaccac8",
+ "name": "Crear Google Sheet Reporte",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.spreadsheetId }}/values:batchUpdate",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={{ JSON.stringify({ valueInputOption: 'USER_ENTERED', data: [ { range: 'Cruce Identidad!A1', majorDimension: 'ROWS', values: $('Construir reporte producci贸n').item.json.identidadRows }, { range: 'Cruce Montos IHSS!A1', majorDimension: 'ROWS', values: $('Construir reporte producci贸n').item.json.montosRows }, { range: 'Resumen Planillas!A1', majorDimension: 'ROWS', values: $('Construir reporte producci贸n').item.json.resumenRows }, { range: 'Detalle N贸mina IHSS!A1', majorDimension: 'ROWS', values: $('Construir reporte producci贸n').item.json.detalleRows } ] }) }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 3008,
+ 368
+ ],
+ "id": "bc29c2b3-b24c-4da8-9d3d-c4c34dcedd6d",
+ "name": "Escribir reporte",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $('Crear Google Sheet Reporte').item.json.spreadsheetId }}:batchUpdate",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={{ (() => {\n const sheets = $('Crear Google Sheet Reporte').item.json.sheets || [];\n const id = title => (sheets.find(s => s.properties.title === title) || {}).properties.sheetId;\n const requests = [];\n const titles = ['Cruce Identidad','Cruce Montos IHSS','Resumen Planillas','Detalle N贸mina IHSS'];\n for (const title of titles) {\n const sid = id(title);\n if (sid === undefined) continue;\n requests.push(\n { repeatCell: { range: { sheetId: sid, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 0.91, green: 0.96, blue: 0.91 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { autoResizeDimensions: { dimensions: { sheetId: sid, dimension: 'COLUMNS', startIndex: 0, endIndex: 10 } } }\n );\n }\n const sidId = id('Cruce Identidad');\n if (sidId !== undefined) {\n requests.push({ repeatCell: { range: { sheetId: sidId, startRowIndex: 4, endRowIndex: 5 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidId, startRowIndex: 14, endRowIndex: 15 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidId, startRowIndex: 19, endRowIndex: 20 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n }\n const sidMontos = id('Cruce Montos IHSS');\n if (sidMontos !== undefined) {\n requests.push({ repeatCell: { range: { sheetId: sidMontos, startRowIndex: 4, endRowIndex: 8, startColumnIndex: 1, endColumnIndex: 2 }, cell: { userEnteredFormat: { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } } }, fields: 'userEnteredFormat.numberFormat' } });\n requests.push({ repeatCell: { range: { sheetId: sidMontos, startRowIndex: 4, endRowIndex: 5 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidMontos, startRowIndex: 15, endRowIndex: 17 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 1, green: 0.95, blue: 0.85 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidMontos, startRowIndex: 16, startColumnIndex: 2, endColumnIndex: 3 }, cell: { userEnteredFormat: { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } } }, fields: 'userEnteredFormat.numberFormat' } });\n }\n const sidResumen = id('Resumen Planillas');\n if (sidResumen !== undefined) {\n requests.push({ repeatCell: { range: { sheetId: sidResumen, startRowIndex: 3, endRowIndex: 4 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidResumen, startRowIndex: 4, startColumnIndex: 4, endColumnIndex: 8 }, cell: { userEnteredFormat: { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } } }, fields: 'userEnteredFormat.numberFormat' } });\n }\n const sidDetalle = id('Detalle N贸mina IHSS');\n if (sidDetalle !== undefined) {\n requests.push({ repeatCell: { range: { sheetId: sidDetalle, startRowIndex: 3, endRowIndex: 4 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidDetalle, startRowIndex: 4, startColumnIndex: 2, endColumnIndex: 3 }, cell: { userEnteredFormat: { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } } }, fields: 'userEnteredFormat.numberFormat' } });\n }\n return JSON.stringify({ requests });\n})() }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 3248,
+ 368
+ ],
+ "id": "d72c6cb7-6ed8-4682-b89b-2c2745259891",
+ "name": "Formatear reporte",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "=https://www.googleapis.com/drive/v3/files/{{ $('Crear Google Sheet Reporte').item.json.spreadsheetId }}/permissions?sendNotificationEmail=false&supportsAllDrives=true",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleDriveOAuth2Api",
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={{ JSON.stringify({ role: 'writer', type: 'user', emailAddress: 'iaracena@gomezleemarketing.com' }) }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 3472,
+ 368
+ ],
+ "id": "e43983bb-e876-47e0-9d60-0164e42b19b6",
+ "name": "Compartir con Isaac",
+ "credentials": {
+ "googleDriveOAuth2Api": {
+ "id": "g23xdGLZRzBGqKgH",
+ "name": "Isaac - Google Drive"
+ }
+ },
+ "onError": "continueRegularOutput"
+ },
+ {
+ "parameters": {
+ "sendTo": "iaracena@gomezleemarketing.com, ymadera@gomezleemarketing.com",
+ "subject": "={{ (() => { const init = $('Inicializar memoria').item.json || {}; return `Cruce Seguridad Social Honduras - ${init.periodoLabel || (init.mes + '/' + init.anio)}`; })() }}",
+ "message": "={{ (() => {\n const summary = $('Construir reporte producci贸n').item.json.summary || {};\n const sheetUrl = $('Crear Google Sheet Reporte').item.json.spreadsheetUrl || '#';\n const init = $('Inicializar memoria').item.json || {};\n const periodo = init.periodoLabel || `${init.mes}/${init.anio}`;\n const fmt = n => Number(n || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n const diferenciasIdentidad = Number(summary.diferenciasIdentidad || 0);\n const diffMontos = Number(summary.diffMontos || 0);\n const diffReal = Math.abs(diffMontos) > 0.05;\n const statusText = diferenciasIdentidad || diffReal ? 'COMPLETADO 路 REVISAR HALLAZGOS' : 'COMPLETADO 路 SIN DIFERENCIAS';\n const statusBg = diferenciasIdentidad || diffReal ? '#fff7e6' : '#eefaf0';\n const statusBorder = diferenciasIdentidad || diffReal ? '#f59e0b' : '#5fbf47';\n const statusColor = diferenciasIdentidad || diffReal ? '#92400e' : '#2f7d32';\n const logo = 'data:image/png;base64,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';\n const caso = summary.casoMontoPrincipal || null;\n const row = (label, value) => `\n
\n | ${label} | \n ${value} | \n
`;\n const section = (title, rowsHtml) => `\n `;\n const casoHtml = caso ? section('Caso de monto a revisar',\n row('Tipo', caso.tipo || 'Diferencia de monto') +\n row('Empleado / referencia', caso.empleado || 'No identificado') +\n row('Monto diferencia', `L ${fmt(caso.monto || Math.abs(diffMontos))}`) +\n `| Observaci贸n: ${caso.observacion || summary.observacionIhss || 'Revisar con Administraci贸n.'} |
`\n ) : '';\n return `\n \n
\n \n \n SEGURIDAD SOCIAL 路 HONDURAS \n Cruce mensual completado\n ${periodo} \n \n \n \n PLANILLAS IHSS ${summary.ihss || 0} |
| \n N脫MINA ${summary.nomina || 0} |
| \n DIF. MONTO L ${fmt(diffMontos)} |
| \n \n \n ${section('Identidad',\n row('Resultado', diferenciasIdentidad ? 'Revisar diferencias' : 'Sin diferencias') +\n row('Coinciden en ambas', summary.ambas || 0) +\n row('Solo IHSS / Solo n贸mina', `${summary.soloIHSS || 0} / ${summary.soloNomina || 0}`)\n )}\n ${section('Montos IHSS',\n row('IHSS descontado en n贸mina', `L ${fmt(summary.payrollIhss)}`) +\n row('Cuota trabajador planillas', `L ${fmt(summary.pdfCuotaTrabajador)}`) +\n row('Diferencia', `L ${fmt(diffMontos)}`)\n )}\n ${casoHtml}\n \n Archivos procesados N贸mina: ${summary.payrollFile || ''} Planillas PDF: ${(summary.pdfFiles || []).join(', ')} \n |
\n
\n
`;\n})() }}",
+ "options": {
+ "appendAttribution": false
+ }
+ },
+ "type": "n8n-nodes-base.gmail",
+ "typeVersion": 2.2,
+ "position": [
+ 3696,
+ 368
+ ],
+ "id": "5ba64fdc-0918-47a8-b07d-95774ad08a76",
+ "name": "Enviar correo",
+ "executeOnce": true,
+ "webhookId": "gmail-cruce-ihss-hn",
+ "credentials": {
+ "gmailOAuth2": {
+ "id": "UDcO1FLJqA453V2D",
+ "name": "Gmail account 3"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "content": "## INICIO DEL CRUCE HONDURAS\n\nEl usuario sube el mes a validar, la n贸mina mensual de Honduras y una o varias planillas IHSS en PDF. El flujo inicializa la ejecuci贸n, guarda el per铆odo seleccionado y prepara la memoria temporal para acumular los datos de identidad, planillas y montos IHSS.",
+ "height": 304,
+ "width": 848
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "typeVersion": 1,
+ "position": [
+ -1376,
+ 208
+ ],
+ "id": "27144621-a6ad-426c-b022-0876fd9837cf",
+ "name": "Sticky Note"
+ },
+ {
+ "parameters": {
+ "content": "## LECTURA DE PLANILLAS Y N脫MINA\n\nEl flujo extrae identidades desde las planillas IHSS y desde las hojas v谩lidas de la n贸mina. Tambi茅n lee la hoja IHSS-RAP para obtener el total mensual descontado por IHSS, separando el cruce de identidad del cruce de montos.",
+ "height": 928,
+ "width": 1968,
+ "color": 5
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "typeVersion": 1,
+ "position": [
+ 0,
+ 0
+ ],
+ "id": "5493f7c6-783e-4367-9e0e-9e8054b93500",
+ "name": "Sticky Note1"
+ },
+ {
+ "parameters": {
+ "content": "## REPORTE FINAL Y CORREO\n\nCuando ambas ramas terminan, el flujo genera un reporte en Google Sheets con cruce de identidad, cruce de montos IHSS, resumen de planillas y detalle de n贸mina. El correo resume los hallazgos y muestra si hay diferencias o casos puntuales para revisar.",
+ "height": 336,
+ "width": 1680,
+ "color": 4
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "typeVersion": 1,
+ "position": [
+ 2224,
+ 240
+ ],
+ "id": "6988f020-8306-464b-b511-8ce4854a0ed5",
+ "name": "Sticky Note2"
+ }
+ ],
+ "connections": {
+ "On form submission": {
+ "main": [
+ [
+ {
+ "node": "Inicializar memoria",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Inicializar memoria": {
+ "main": [
+ [
+ {
+ "node": "Separar planillas PDF",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Preparar n贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Separar planillas PDF": {
+ "main": [
+ [
+ {
+ "node": "Extraer texto PDF",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Extraer texto PDF": {
+ "main": [
+ [
+ {
+ "node": "Guardar IHSS en memoria",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Guardar IHSS en memoria": {
+ "main": [
+ [
+ {
+ "node": "Esperar ramas",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Preparar n贸mina": {
+ "main": [
+ [
+ {
+ "node": "Actualizar staging N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Actualizar staging N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Esperar conversi贸n",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Esperar conversi贸n": {
+ "main": [
+ [
+ {
+ "node": "Listar hojas N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Listar hojas N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Preparar hojas N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Preparar hojas N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Loop hojas N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Loop hojas N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Esperar ramas",
+ "type": "main",
+ "index": 1
+ }
+ ],
+ [
+ {
+ "node": "Leer hoja N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Leer hoja N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Acumular identidades N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Acumular identidades N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Loop hojas N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Esperar ramas": {
+ "main": [
+ [
+ {
+ "node": "Construir reporte producci贸n",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Crear Google Sheet Reporte": {
+ "main": [
+ [
+ {
+ "node": "Escribir reporte",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Escribir reporte": {
+ "main": [
+ [
+ {
+ "node": "Formatear reporte",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Formatear reporte": {
+ "main": [
+ [
+ {
+ "node": "Compartir con Isaac",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Compartir con Isaac": {
+ "main": [
+ [
+ {
+ "node": "Enviar correo",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Construir reporte producci贸n": {
+ "main": [
+ [
+ {
+ "node": "Crear Google Sheet Reporte",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "settings": {
+ "executionOrder": "v1",
+ "binaryMode": "separate"
+ },
+ "staticData": {
+ "global": {
+ "hnCruceIHSSNomina": {}
+ }
+ },
+ "meta": null,
+ "versionId": "1d2417e8-d16e-4c4e-ae50-0181cc6c6b4f",
+ "activeVersionId": "1d2417e8-d16e-4c4e-ae50-0181cc6c6b4f",
+ "versionCounter": 17,
+ "triggerCount": 1,
+ "shared": [
+ {
+ "updatedAt": "2026-07-04T13:43:12.569Z",
+ "createdAt": "2026-07-04T13:43:12.569Z",
+ "role": "workflow:owner",
+ "workflowId": "j04wjKihKPzBIqW8",
+ "projectId": "PJpTANzTXIFibWsW",
+ "project": {
+ "updatedAt": "2026-04-22T14:25:09.686Z",
+ "createdAt": "2026-04-22T14:22:54.790Z",
+ "id": "PJpTANzTXIFibWsW",
+ "name": "Isaac Aracena ",
+ "type": "personal",
+ "icon": null,
+ "description": null,
+ "creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
+ }
+ }
+ ],
+ "tags": [],
+ "activeVersion": {
+ "updatedAt": "2026-07-04T14:18:23.000Z",
+ "createdAt": "2026-07-04T14:18:20.582Z",
+ "versionId": "1d2417e8-d16e-4c4e-ae50-0181cc6c6b4f",
+ "workflowId": "j04wjKihKPzBIqW8",
+ "nodes": [
+ {
+ "parameters": {
+ "formTitle": "Cruce IHSS vs N贸mina Honduras",
+ "formDescription": "Sube la n贸mina mensual de Honduras y una o varias planillas IHSS en PDF. El flujo generar谩 un reporte simple con cruce de identidad y cruce de montos IHSS.",
+ "formFields": {
+ "values": [
+ {
+ "fieldLabel": "Mes a validar",
+ "fieldType": "dropdown",
+ "fieldOptions": {
+ "values": [
+ {
+ "option": "01"
+ },
+ {
+ "option": "02"
+ },
+ {
+ "option": "03"
+ },
+ {
+ "option": "04"
+ },
+ {
+ "option": "05"
+ },
+ {
+ "option": "06"
+ },
+ {
+ "option": "07"
+ },
+ {
+ "option": "08"
+ },
+ {
+ "option": "09"
+ },
+ {
+ "option": "10"
+ },
+ {
+ "option": "11"
+ },
+ {
+ "option": "12"
+ }
+ ]
+ },
+ "requiredField": true
+ },
+ {
+ "fieldLabel": "Planillas IHSS PDF",
+ "fieldType": "file",
+ "acceptFileTypes": ".pdf",
+ "requiredField": true
+ },
+ {
+ "fieldLabel": "N贸mina",
+ "fieldType": "file",
+ "multipleFiles": false,
+ "acceptFileTypes": ".xlsx, .xls, .csv",
+ "requiredField": true
+ }
+ ]
+ },
+ "options": {
+ "ignoreBots": true
+ }
+ },
+ "type": "n8n-nodes-base.formTrigger",
+ "typeVersion": 2.3,
+ "position": [
+ -1184,
+ 336
+ ],
+ "id": "d7172e37-a0d6-43c7-a6d6-8523344e8551",
+ "name": "On form submission",
+ "webhookId": "d92ae3d4-ceef-4580-8439-679f5dd7aa67"
+ },
+ {
+ "parameters": {
+ "jsCode": "const item = $input.all()[0];\nconst now = new Date();\nconst pad = n => String(n).padStart(2, '0');\nconst ts = `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;\nconst runId = `HN_IHSS_${ts}_${Math.random().toString(36).slice(2, 8)}`;\nconst mes = String(item.json['Mes a validar'] || '').padStart(2, '0');\nconst anio = now.getFullYear();\nconst meses = { '01':'Enero','02':'Febrero','03':'Marzo','04':'Abril','05':'Mayo','06':'Junio','07':'Julio','08':'Agosto','09':'Septiembre','10':'Octubre','11':'Noviembre','12':'Diciembre' };\nconst fechaTitulo = `${pad(now.getDate())}-${pad(now.getMonth()+1)}-${now.getFullYear()}`;\nconst periodoLabel = `${meses[mes] || mes} ${anio}`;\nconst reportTitle = `Cruce Seguridad Social Honduras - ${periodoLabel} - ${fechaTitulo}`;\nconst data = $getWorkflowStaticData('global');\ndata.hnCruceIHSSNomina = data.hnCruceIHSSNomina || {};\ndata.hnCruceIHSSNomina[runId] = {\n startedAt: now.toISOString(),\n mes,\n anio,\n periodoLabel,\n ihss: [],\n ihssPlanillas: [],\n nomina: [],\n payrollIhssSummary: {},\n payrollIhssBlocks: [],\n pdfFiles: [],\n payrollFile: '',\n};\nreturn [{ json: { ...item.json, runId, mes, anio, periodoLabel, reportTitle, emailTo: 'iaracena@gomezleemarketing.com' }, binary: item.binary }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ -944,
+ 336
+ ],
+ "id": "864517a7-a3ab-48d8-a59e-2ed21a91139b",
+ "name": "Inicializar memoria"
+ },
+ {
+ "parameters": {
+ "jsCode": "const base = $input.all()[0];\nconst bin = base.binary || {};\nconst out = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '').toLowerCase();\n const lower = fileName.toLowerCase();\n const isPdf = mime.includes('pdf') || lower.endsWith('.pdf') || key.toLowerCase().includes('pdf') || key.toLowerCase().includes('planilla');\n if (!isPdf) continue;\n out.push({\n json: { runId: base.json.runId, mes: base.json.mes, anio: base.json.anio, fileName, originalBinaryKey: key },\n binary: { data: file },\n });\n}\nif (!out.length) {\n throw new Error('No encontr茅 planillas IHSS en PDF. Revisa que el campo de planillas acepte .pdf y que hayas adjuntado al menos un PDF.');\n}\nconst data = $getWorkflowStaticData('global');\nif (data.hnCruceIHSSNomina?.[base.json.runId]) {\n data.hnCruceIHSSNomina[base.json.runId].pdfFiles = out.map(i => i.json.fileName);\n}\nreturn out;"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 128,
+ 96
+ ],
+ "id": "cce199b0-319f-4c8a-a2aa-d6abcf820c87",
+ "name": "Separar planillas PDF"
+ },
+ {
+ "parameters": {
+ "operation": "pdf",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.extractFromFile",
+ "typeVersion": 1.1,
+ "position": [
+ 368,
+ 96
+ ],
+ "id": "1c2d343d-0e91-4591-bf6f-72378f7a020a",
+ "name": "Extraer texto PDF"
+ },
+ {
+ "parameters": {
+ "jsCode": "function normalizeText(s) {\n return String(s || '').replace(/\\r/g, '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').replace(/脵/g, 'U').replace(/霉/g, 'u');\n}\nfunction clean(s) { return String(s || '').replace(/\\s+/g, ' ').trim(); }\nfunction cleanNameLine(s) { return clean(String(s || '').replace(/\\s+\\d{1,2}\\s+[\\d,.]+.*$/, '')); }\nfunction toNumber(v) {\n if (v === null || v === undefined || v === '') return 0;\n let s = String(v).replace(/LPS|L|Q|USD|\\$|,/gi, '').replace(/\\s/g, '').trim();\n if (/^\\((.*)\\)$/.test(s)) s = '-' + s.replace(/^\\(|\\)$/g, '');\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n}\nfunction money(n) { return Number((Number(n) || 0).toFixed(2)); }\nfunction firstMatch(text, patterns) { for (const p of patterns) { const m = text.match(p); if (m) return m[1] || m[0]; } return ''; }\nfunction numMatch(text, patterns) { const v = firstMatch(text, patterns); return v ? toNumber(v) : 0; }\nfunction unique13Digits(raw) {\n const found = String(raw || '').match(/\\b\\d{13}\\b/g) || [];\n return [...new Set(found)].filter(id => id !== '0000000000000');\n}\nfunction cleanNamesFromBlock(block) {\n return String(block || '').split('\\n')\n .map(cleanNameLine)\n .filter(l => l && !/^\\d+$/.test(l))\n .filter(l => /[A-Z脩]/i.test(l))\n .filter(l => !/SALARIO|DIAS|TRABAJADOR|PATRONO|ESTADO|PERIODO|RIESGO|PAGINA|CUOTA|RECIBO|MONTO|TOTAL|PERSONA JURIDICA|PRIVADO|PUBLICIDAD|SECTOR|ACTIVIDAD|DIRECCION|TELEFONO|ZONA|CUADRA|NO\\.?\\s*AFILIACION|IDENTIDAD|NOMBRE Y APELLIDO/i.test(l));\n}\nfunction expectedWorkers(text) {\n const nums = [...String(text || '').matchAll(/TRABAJADORES\\s+(\\d{1,4})/gi)].map(m => Number(m[1])).filter(n => Number.isFinite(n) && n > 0);\n return nums.length ? Math.max(...nums) : 0;\n}\nfunction addCandidate(store, ids, names, meta) {\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i];\n if (!id || id === '0000000000000') continue;\n if (!store.has(id)) {\n store.set(id, { identidad: id, nombre_planilla: clean(names[i] || ''), archivo_planilla: meta.fileName, numero_patronal: meta.patronal, recibo: meta.recibo, periodo: meta.periodo, cuota_trabajador: 0 });\n } else {\n const ex = store.get(id);\n if (!ex.nombre_planilla && clean(names[i] || '')) ex.nombre_planilla = clean(names[i]);\n }\n }\n}\nfunction extractCandidatesFromText(text, meta) {\n const store = new Map();\n const reA = /NO\\.?\\s*AFILIACION([\\s\\S]*?)IDENTIDAD([\\s\\S]*?)NOMBRE\\s+Y\\s+APELLIDO/gi;\n let m;\n while ((m = reA.exec(text)) !== null) addCandidate(store, unique13Digits(m[1]), cleanNamesFromBlock(m[2]), meta);\n const reB = /IDENTIDAD([\\s\\S]*?)NO\\.?\\s*AFILIACION[\\s\\S]*?NOMBRE\\s+Y\\s+APELLIDO([\\s\\S]*?)(?:\\n\\s*SALARIO|\\n\\s*30\\s|\\n\\s*Hago\\s+Constar|\\f|$)/gi;\n while ((m = reB.exec(text)) !== null) addCandidate(store, unique13Digits(m[1]), cleanNamesFromBlock(m[2]), meta);\n const reC = /IDENTIDAD([\\s\\S]*?)NOMBRE\\s+Y\\s+APELLIDO/gi;\n while ((m = reC.exec(text)) !== null) {\n const ids = unique13Digits(m[1]);\n if (ids.length) addCandidate(store, ids, [], meta);\n }\n const expected = expectedWorkers(text);\n if ((expected && store.size < Math.ceil(expected * 0.8)) || store.size === 0) addCandidate(store, unique13Digits(text), [], meta);\n return [...store.values()];\n}\nfunction extractPlanillaSummary(text, meta) {\n const trabajadores = numMatch(text, [/TRABAJADORES\\s+(\\d{1,4})/i]);\n const salario = numMatch(text, [/MONTO\\s+DE\\s+SALARIOS\\s+([\\d,.]+)/i]);\n const cuotaTrabajador = numMatch(text, [/CUOTA\\s+TRABAJADOR\\s+([\\d,.]+)/i]);\n const cuotaPatrono = numMatch(text, [/CUOTA\\s+PATRONO\\s+([\\d,.]+)/i]);\n const totalSinRecargo = money(cuotaTrabajador + cuotaPatrono);\n return {\n archivo_planilla: meta.fileName,\n numero_patronal: meta.patronal,\n recibo: meta.recibo,\n periodo: meta.periodo,\n trabajadores,\n salario_reportado: money(salario),\n cuota_trabajador: money(cuotaTrabajador),\n cuota_patrono: money(cuotaPatrono),\n total_planilla: totalSinRecargo,\n };\n}\nfunction periodoKey(v) { return String(v || '').replace(/\\s/g, '').trim(); }\nfunction planillaKey(p) {\n return [p.numero_patronal || '', p.recibo || '', periodoKey(p.periodo), p.trabajadores || '', money(p.cuota_trabajador || 0)].join('|');\n}\nconst init = $items('Inicializar memoria')[0].json;\nconst runId = init.runId;\nconst expectedPeriodo = `${String(init.mes || '').padStart(2, '0')}/${init.anio}`;\nconst items = $input.all();\nconst sourcePdfItems = $items('Separar planillas PDF');\nconst all = [];\nconst planillas = [];\nconst duplicatePlanillas = [];\nconst periodoMismatches = [];\nconst seenPlanillas = new Map();\nfor (let idx = 0; idx < items.length; idx++) {\n const item = items[idx];\n const source = sourcePdfItems[idx]?.json || {};\n const fileName = item.json.fileName || source.fileName || source.originalBinaryKey || item.json.file || item.json.originalBinaryKey || `Planilla PDF ${idx + 1}`;\n const text = normalizeText(item.json.text || item.json.data || item.json.content || '');\n if (!text.trim()) continue;\n const meta = {\n fileName,\n patronal: firstMatch(text, [/([0-9]{3}-[0-9]{4}-[0-9]{4}-[0-9])\\s*NUMERO\\s+PATRONAL/i, /NUMERO\\s+PATRONAL\\s*\\n?\\s*([0-9]{3}-[0-9]{4}-[0-9]{4}-[0-9])/i, /\\b([0-9]{3}-[0-9]{4}-[0-9]{4}-[0-9])\\b/i]),\n periodo: firstMatch(text, [/(\\d{2}\\s*\\/\\s*\\d{4})\\s*PERIODO/i, /PERIODO\\s*\\n?\\s*(\\d{2}\\s*\\/\\s*\\d{4})/i]),\n recibo: firstMatch(text, [/RECIBO\\s+NUMERO\\s*\\n?\\s*(\\d{5,})/i, /RECIBO\\s+DE\\s+PAGO[\\s\\S]{0,80}?\\b(\\d{6,})\\b/i, /\\*(\\d{6,})\\*/]),\n };\n const planilla = extractPlanillaSummary(text, meta);\n const candidates = extractCandidatesFromText(text, meta);\n const uniqueWorkersInPdf = new Set(candidates.map(r => r.identidad).filter(Boolean)).size;\n\n // Producci贸n: algunos PDFs no exponen bien TRABAJADORES / MONTO DE SALARIOS en texto plano.\n if (!planilla.trabajadores || planilla.trabajadores < 1) planilla.trabajadores = uniqueWorkersInPdf;\n if (!planilla.salario_reportado || planilla.salario_reportado < 1000) {\n planilla.salario_reportado = planilla.cuota_trabajador\n ? money(planilla.cuota_trabajador / 0.05)\n : money(uniqueWorkersInPdf * 11903.13);\n }\n\n // Producci贸n: si cargan el mismo PDF/recibo dos veces, NO se suma dos veces.\n const key = planillaKey(planilla);\n if (seenPlanillas.has(key)) {\n duplicatePlanillas.push({ ...planilla, archivo_original: seenPlanillas.get(key).archivo_planilla, archivo_duplicado: fileName });\n continue;\n }\n seenPlanillas.set(key, planilla);\n\n // Control de per铆odo: no detiene el flujo, pero queda visible como hallazgo.\n if (planilla.periodo && periodoKey(planilla.periodo) !== expectedPeriodo) {\n periodoMismatches.push({ archivo_planilla: fileName, periodo_detectado: planilla.periodo, periodo_esperado: expectedPeriodo });\n }\n\n planillas.push(planilla);\n const cuotaPorPersona = planilla.trabajadores ? money(planilla.cuota_trabajador / planilla.trabajadores) : 0;\n all.push(...candidates.map(r => ({...r, cuota_trabajador: cuotaPorPersona})));\n}\nconst byId = new Map();\nfor (const r of all) {\n if (!r.identidad) continue;\n if (!byId.has(r.identidad)) {\n byId.set(r.identidad, { ...r, archivos_planilla: r.archivo_planilla ? [r.archivo_planilla] : [], patronales: r.numero_patronal ? [r.numero_patronal] : [], recibos: r.recibo ? [r.recibo] : [] });\n } else {\n const ex = byId.get(r.identidad);\n if (r.nombre_planilla && !ex.nombre_planilla) ex.nombre_planilla = r.nombre_planilla;\n if (r.archivo_planilla && !ex.archivos_planilla.includes(r.archivo_planilla)) ex.archivos_planilla.push(r.archivo_planilla);\n if (r.numero_patronal && !ex.patronales.includes(r.numero_patronal)) ex.patronales.push(r.numero_patronal);\n if (r.recibo && !ex.recibos.includes(r.recibo)) ex.recibos.push(r.recibo);\n if (!ex.cuota_trabajador && r.cuota_trabajador) ex.cuota_trabajador = r.cuota_trabajador;\n }\n}\nconst registros = [...byId.values()].map(r => ({ identidad: r.identidad, nombre_planilla: r.nombre_planilla || '', archivos_planilla: r.archivos_planilla.join(' | '), numero_patronal: r.patronales.join(' | '), recibo: r.recibos.join(' | '), periodo: r.periodo || '', cuota_trabajador: money(r.cuota_trabajador) }));\nif (!registros.length) throw new Error('No pude extraer identidades de las planillas IHSS. Revisa que los PDF sean legibles y que no sean escaneos sin texto.');\nconst data = $getWorkflowStaticData('global');\ndata.hnCruceIHSSNomina = data.hnCruceIHSSNomina || {};\ndata.hnCruceIHSSNomina[runId] = data.hnCruceIHSSNomina[runId] || { ihss: [], nomina: [] };\ndata.hnCruceIHSSNomina[runId].ihss = registros;\ndata.hnCruceIHSSNomina[runId].ihssPlanillas = planillas;\ndata.hnCruceIHSSNomina[runId].duplicatePlanillas = duplicatePlanillas;\ndata.hnCruceIHSSNomina[runId].periodoMismatches = periodoMismatches;\nreturn [{ json: { runId, ihssCount: registros.length, planillasCount: planillas.length, duplicatePlanillasCount: duplicatePlanillas.length, periodoMismatchesCount: periodoMismatches.length, pdfFiles: [...new Set(registros.flatMap(r => String(r.archivos_planilla).split(' | ').filter(Boolean)))] } }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 608,
+ 96
+ ],
+ "id": "1b5b71d1-2008-4ff3-b2e8-b3b327a7e819",
+ "name": "Guardar IHSS en memoria"
+ },
+ {
+ "parameters": {
+ "jsCode": "const base = $input.all()[0];\nconst bin = base.binary || {};\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '').toLowerCase();\n const lower = fileName.toLowerCase();\n const isExcel = lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.csv') || mime.includes('spreadsheet') || mime.includes('excel') || key.toLowerCase().includes('nomina');\n if (isExcel && !lower.endsWith('.pdf')) candidates.push([key, file]);\n}\nif (!candidates.length) {\n throw new Error('No encontr茅 el archivo de n贸mina. Debe ser .xlsx, .xls o .csv en el campo Nomina.');\n}\nif (candidates.length > 1) {\n // Prefer the binary field whose key includes Nomina; otherwise use the first Excel-like file.\n const idx = candidates.findIndex(([key]) => key.toLowerCase().includes('nomina'));\n const chosen = candidates[idx >= 0 ? idx : 0];\n candidates.length = 0;\n candidates.push(chosen);\n}\nconst [key, file] = candidates[0];\nconst data = $getWorkflowStaticData('global');\nif (data.hnCruceIHSSNomina?.[base.json.runId]) data.hnCruceIHSSNomina[base.json.runId].payrollFile = file.fileName || key;\nreturn [{ json: { runId: base.json.runId, mes: base.json.mes, anio: base.json.anio, payrollFile: file.fileName || key }, binary: { data: file } }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 128,
+ 528
+ ],
+ "id": "1ec130f8-c6bc-4c42-8c4b-0117bd3901f4",
+ "name": "Preparar n贸mina"
+ },
+ {
+ "parameters": {
+ "operation": "update",
+ "fileId": {
+ "__rl": true,
+ "value": "1LNJtm2ITumAFGQwWmHXpoeSOWXBXjdTDpjSBBwqnHd4",
+ "mode": "list",
+ "cachedResultName": "Nomina Honduras",
+ "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1LNJtm2ITumAFGQwWmHXpoeSOWXBXjdTDpjSBBwqnHd4/edit"
+ },
+ "changeFileContent": true,
+ "options": {}
+ },
+ "type": "n8n-nodes-base.googleDrive",
+ "typeVersion": 3,
+ "position": [
+ 368,
+ 528
+ ],
+ "id": "da97a37f-7878-4f9a-a9a6-73c19c4abe47",
+ "name": "Actualizar staging N贸mina",
+ "credentials": {
+ "googleDriveOAuth2Api": {
+ "id": "g23xdGLZRzBGqKgH",
+ "name": "Isaac - Google Drive"
+ }
+ }
+ },
+ {
+ "parameters": {},
+ "type": "n8n-nodes-base.wait",
+ "typeVersion": 1.1,
+ "position": [
+ 592,
+ 528
+ ],
+ "id": "505b8ad3-db01-4ab5-a8f7-4741f846fb2b",
+ "name": "Esperar conversi贸n",
+ "webhookId": "wait-conversion-nomina"
+ },
+ {
+ "parameters": {
+ "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1LNJtm2ITumAFGQwWmHXpoeSOWXBXjdTDpjSBBwqnHd4' }}?fields=sheets(properties(title,sheetId,hidden))",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 816,
+ 528
+ ],
+ "id": "35aa0ca4-2e93-4c92-9813-e36993dd1a67",
+ "name": "Listar hojas N贸mina",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst spreadsheetId = $json.spreadsheetId || $node['Actualizar staging N贸mina'].json.id || '1LNJtm2ITumAFGQwWmHXpoeSOWXBXjdTDpjSBBwqnHd4';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]/g, ''); }\nconst ignoreBase = /DO NOT DELETE|NVScriptsProperties|AutoCrat|Resumen|Presupuesto|Retroactivo|Combustible|Combustibles/i;\nconst sheets = ($json.sheets || []).map(s => s.properties || {}).filter(p => p.title && !p.hidden);\nconst out = [];\nfor (const p of sheets) {\n const title = String(p.title || '');\n const n = norm(title);\n if (n === 'ihssrap' || (n.includes('ihss') && n.includes('rap'))) {\n out.push({ json: { runId: init.runId, spreadsheetId, sheetTitle: title, sheetId: p.sheetId, sheetKind: 'ihss_rap' } });\n continue;\n }\n if (!ignoreBase.test(title) && !/IHSS|RAP/i.test(title)) {\n out.push({ json: { runId: init.runId, spreadsheetId, sheetTitle: title, sheetId: p.sheetId, sheetKind: 'nomina_base' } });\n }\n}\nif (!out.length) throw new Error('No encontr茅 hojas v谩lidas de n贸mina. Revisa que el archivo tenga hojas de pago/base y la hoja IHSS-RAP.');\nreturn out;"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 1056,
+ 528
+ ],
+ "id": "e301de8e-430d-4c49-af9b-5606811736bc",
+ "name": "Preparar hojas N贸mina"
+ },
+ {
+ "parameters": {
+ "options": {}
+ },
+ "type": "n8n-nodes-base.splitInBatches",
+ "typeVersion": 3,
+ "position": [
+ 1280,
+ 528
+ ],
+ "id": "817360d8-d0ce-4d3e-bcb9-f22b1435d503",
+ "name": "Loop hojas N贸mina"
+ },
+ {
+ "parameters": {
+ "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.spreadsheetId }}/values/{{ encodeURIComponent(\"'\" + String($json.sheetTitle).replace(/'/g, \"''\") + \"'!A1:AZ1000\") }}?majorDimension=ROWS&valueRenderOption=FORMATTED_VALUE&dateTimeRenderOption=FORMATTED_STRING",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 1552,
+ 720
+ ],
+ "id": "610e431e-ee51-4e68-b425-0fb6b09e2cad",
+ "name": "Leer hoja N贸mina",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "jsCode": "function normHeader(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]/g, ''); }\nfunction clean(v) { return String(v ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction cleanName(v) { return clean(v); }\nfunction plain(v) { return clean(v).normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction toNumber(v) {\n if (v === null || v === undefined || v === '') return 0;\n let s = String(v).replace(/LPS|L|Q|USD|\\$|,/gi, '').replace(/\\s/g, '').trim();\n if (/^\\((.*)\\)$/.test(s)) s = '-' + s.replace(/^\\(|\\)$/g, '');\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n}\nfunction money(n) { return Number((Number(n) || 0).toFixed(2)); }\nfunction normalizeId(v) {\n if (v === undefined || v === null) return '';\n let s = String(v).trim();\n if (/^\\d+(\\.0+)?$/.test(s)) s = s.replace(/\\.0+$/, '');\n const d = s.replace(/\\D/g, '');\n if (d.length !== 13 || d === '0000000000000') return '';\n return d;\n}\nfunction findHeader(values) {\n const maxScan = Math.min(values.length, 40);\n for (let r = 0; r < maxScan; r++) {\n const row = values[r] || [];\n const headers = row.map(normHeader);\n const idCol = headers.findIndex(h => h === 'identidad' || h === 'identida' || h === 'cedula' || h === 'idcedula' || h === 'documento' || h === 'nodocumento');\n const nameCol = headers.findIndex(h => h === 'nombre' || h === 'nombrecompleto' || h === 'empleado' || h === 'beneficiario' || h === 'colaborador' || h.startsWith('nombre'));\n if (idCol >= 0 && nameCol >= 0) return { headerRow: r, idCol, nameCol };\n }\n return null;\n}\nfunction isSpecialContextLine(line) {\n return /visado|walmart|personal\\s+especial|personal\\s+para\\s+visado|personal\\s+temporal/i.test(line);\n}\nfunction extractPersonCount(line) {\n const m = plain(line).match(/(\\d{1,3})\\s*(personas?|colaboradores?|empleados?)/i);\n return m ? Number(m[1]) : 0;\n}\nfunction isLikelySpecialName(s) {\n const raw = clean(s);\n const p = plain(raw);\n if (!raw || raw.length < 3) return false;\n if (!/[A-Za-z脕脡脥脫脷脩谩茅铆贸煤帽]/.test(raw)) return false;\n if (/empleado|patrono|total|planilla|nomina|diferencia|moneda|monto|ihss|rap|observacion|cuota|lps|salario/i.test(p)) return false;\n return true;\n}\nfunction getSpecialPairsFromRow(cells) {\n const pairs = [];\n // Formato observado en GLM Honduras: columnas J/K = referencia corta + monto.\n const candidateColumns = [9, 7, 8, 10, 11];\n for (const idx of candidateColumns) {\n const name = cells[idx] || '';\n const amount = toNumber(cells[idx + 1] || '');\n if (isLikelySpecialName(name) && amount > 0) {\n pairs.push({ referencia: name, monto: money(amount) });\n }\n }\n // Fallback: buscar cualquier par nombre/monto contiguo al final de la fila.\n for (let i = 0; i < cells.length - 1; i++) {\n const name = cells[i] || '';\n const amount = toNumber(cells[i + 1] || '');\n if (isLikelySpecialName(name) && amount > 0) {\n if (!pairs.some(p => plain(p.referencia) === plain(name) && p.monto === money(amount))) {\n pairs.push({ referencia: name, monto: money(amount) });\n }\n }\n }\n return pairs;\n}\n\nconst runId = $items('Inicializar memoria')[0].json.runId;\nlet sheetTitle = '';\nlet sheetKind = 'nomina_base';\ntry { sheetTitle = $node['Loop hojas N贸mina'].json.sheetTitle || ''; sheetKind = $node['Loop hojas N贸mina'].json.sheetKind || 'nomina_base'; } catch (e) {}\nsheetTitle = sheetTitle || 'Hoja de n贸mina';\nconst values = $json.values || [];\nconst data = $getWorkflowStaticData('global');\ndata.hnCruceIHSSNomina = data.hnCruceIHSSNomina || {};\ndata.hnCruceIHSSNomina[runId] = data.hnCruceIHSSNomina[runId] || { ihss: [], nomina: [], payrollIhssSummary: {}, payrollIhssBlocks: [], payrollSpecialEmployees: [] };\n\nif (sheetKind === 'ihss_rap') {\n const blocks = [];\n const specialEmployees = [];\n const summary = {\n totalNominaIhss: 0,\n ajusteIhss: 0,\n totalNominaIhssAjustado: 0,\n totalPlanillasIhss: 0,\n diferenciaIhss: 0,\n observacionIhss: '',\n ajusteIhssMotivo: ''\n };\n\n let inIhss = false;\n let specialContextActive = false;\n let specialContextLabel = '';\n let expectedSpecialCount = 0;\n let collectedInContext = 0;\n let specialBlockAmount = 0;\n\n for (const row of values) {\n const cells = (row || []).map(clean);\n const lineOriginal = cells.join(' ');\n const line = plain(lineOriginal);\n\n if (line.includes('ihss') && !line.includes('rap')) inIhss = true;\n if (line.includes('rap aportacion') || line.includes('rap reserva')) {\n inIhss = false;\n specialContextActive = false;\n }\n\n const concept = cells[1] || cells[0] || '';\n const amount = toNumber(cells[3] || cells[2] || '');\n const conceptNorm = plain(concept);\n\n if (conceptNorm.includes('total nomina')) {\n summary.totalNominaIhss = money(amount);\n specialContextActive = false;\n } else if (conceptNorm.includes('total planilla')) {\n summary.totalPlanillasIhss = money(amount);\n specialContextActive = false;\n } else if (inIhss && conceptNorm.includes('diferencia')) {\n summary.diferenciaIhss = money(amount);\n summary.observacionIhss = cells.slice(4).filter(Boolean).join(' ');\n specialContextActive = false;\n } else if (inIhss && concept && amount && !/ihss|moneda|empleado|total|planilla|diferencia/i.test(concept)) {\n blocks.push({ concepto: concept, moneda: cells[2] || '', monto: money(amount) });\n\n // PRODUCCI脫N: un ajuste de personal solo se suma si el bloque lo declara expl铆citamente\n // como visado/Walmart/personal especial. Esto evita inflar mayo por observaciones normales.\n if (isSpecialContextLine(lineOriginal) || isSpecialContextLine(concept)) {\n specialBlockAmount = money(specialBlockAmount + amount);\n specialContextActive = true;\n specialContextLabel = concept;\n expectedSpecialCount = extractPersonCount(lineOriginal) || extractPersonCount(concept) || 0;\n collectedInContext = 0;\n }\n }\n\n if (inIhss && isSpecialContextLine(lineOriginal)) {\n specialContextActive = true;\n specialContextLabel = concept || lineOriginal;\n expectedSpecialCount = extractPersonCount(lineOriginal) || expectedSpecialCount || 0;\n collectedInContext = 0;\n }\n\n // Caso Honduras marzo: empleados fuera de hojas Base incluidos expl铆citamente en IHSS-RAP\n // como \"Personal para visado de Walmart\". Solo se leen nombres/montos si ese contexto existe.\n if (inIhss && specialContextActive) {\n const pairs = getSpecialPairsFromRow(cells);\n for (const pair of pairs) {\n if (!specialEmployees.some(e => plain(e.referencia) === plain(pair.referencia) && e.monto === pair.monto)) {\n specialEmployees.push({ referencia: pair.referencia, monto: pair.monto, fuente: sheetTitle, contexto: specialContextLabel || 'Personal especial / visado Walmart' });\n collectedInContext++;\n }\n }\n if (expectedSpecialCount && collectedInContext >= expectedSpecialCount) {\n specialContextActive = false;\n }\n }\n }\n\n const specialTotal = money(specialEmployees.reduce((a, e) => a + Number(e.monto || 0), 0));\n\n // Regla final de producci贸n:\n // - Si hay empleados especiales expl铆citos, se suma su total.\n // - Si no se leyeron empleados pero hay bloque expl铆cito de visado/Walmart, se suma el monto del bloque.\n // - Si no hay contexto expl铆cito, ajuste = 0.00.\n summary.ajusteIhss = money(specialTotal || specialBlockAmount || 0);\n summary.ajusteIhssMotivo = summary.ajusteIhss\n ? (specialEmployees.length ? 'Personal especial/visado detectado expl铆citamente en IHSS-RAP.' : 'Bloque especial/visado detectado expl铆citamente en IHSS-RAP.')\n : 'Sin ajuste especial expl铆cito.';\n summary.totalNominaIhssAjustado = money(summary.totalNominaIhss + summary.ajusteIhss);\n\n // La diferencia productiva de IHSS debe usar TOTAL N贸mina ajustado - Total Planillas.\n // Evita capturar diferencias de RAP Aportaci贸n / RAP Reserva cuando la hoja tiene varias secciones.\n if (summary.totalNominaIhssAjustado || summary.totalPlanillasIhss) {\n summary.diferenciaIhss = money(summary.totalNominaIhssAjustado - summary.totalPlanillasIhss);\n }\n if (!summary.observacionIhss && Math.abs(summary.diferenciaIhss || 0) > 0.05) {\n summary.observacionIhss = summary.ajusteIhss\n ? 'La hoja IHSS-RAP incluye ajuste de personal especial/visado. Diferencia restante corresponde a redondeo o ajuste menor.'\n : 'La hoja IHSS-RAP muestra diferencia en IHSS. Revisar soporte/observaci贸n interna de n贸mina con Administraci贸n.';\n }\n\n data.hnCruceIHSSNomina[runId].payrollIhssSummary = summary;\n data.hnCruceIHSSNomina[runId].payrollIhssBlocks = blocks;\n data.hnCruceIHSSNomina[runId].payrollSpecialEmployees = specialEmployees;\n return [{ json: { runId, sheetTitle, sheetKind, identitiesFound: 0, ihssSummaryFound: Boolean(summary.totalNominaIhss || summary.totalPlanillasIhss), ajusteIhss: summary.ajusteIhss, specialEmployeesFound: specialEmployees.length } }];\n}\n\nconst header = findHeader(values);\nconst records = [];\nif (header) {\n for (let r = header.headerRow + 1; r < values.length; r++) {\n const row = values[r] || [];\n const identidad = normalizeId(row[header.idCol]);\n if (!identidad) continue;\n const nombre = cleanName(row[header.nameCol]);\n if (!nombre || /^total/i.test(nombre)) continue;\n records.push({ identidad, nombre_nomina: nombre, hoja_nomina: sheetTitle });\n }\n}\ndata.hnCruceIHSSNomina[runId].nomina.push(...records);\nreturn [{ json: { runId, sheetTitle, sheetKind, identitiesFound: records.length, headerDetected: Boolean(header), headerRow: header ? header.headerRow + 1 : '' } }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 1792,
+ 720
+ ],
+ "id": "5c268a10-b7bb-438b-b6fa-1f10f278ca9e",
+ "name": "Acumular identidades N贸mina"
+ },
+ {
+ "parameters": {},
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.2,
+ "position": [
+ 2320,
+ 368
+ ],
+ "id": "0a92d6f6-78cf-494e-851d-927fe8655441",
+ "name": "Esperar ramas"
+ },
+ {
+ "parameters": {
+ "jsCode": "function clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction money(n) { return Number((Number(n) || 0).toFixed(2)); }\nfunction fmt(n) { return Number(n || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }\nfunction normalizeId(v) {\n const d = String(v ?? '').replace(/\\D/g, '');\n if (d.length !== 13 || d === '0000000000000') return '';\n return d;\n}\nfunction plain(s) { return clean(s).normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction dedupe(records, source) {\n const map = new Map();\n for (const r of records || []) {\n const id = normalizeId(r.identidad);\n if (!id) continue;\n if (!map.has(id)) map.set(id, { identidad: id, names: new Set(), sheets: new Set(), files: new Set(), patronales: new Set(), recibos: new Set(), cuota_trabajador: 0, raw: [] });\n const x = map.get(id);\n if (source === 'ihss') {\n if (clean(r.nombre_planilla)) x.names.add(clean(r.nombre_planilla));\n if (clean(r.archivos_planilla)) String(r.archivos_planilla).split(' | ').forEach(v => v && x.files.add(v));\n if (clean(r.numero_patronal)) String(r.numero_patronal).split(' | ').forEach(v => v && x.patronales.add(v));\n if (clean(r.recibo)) String(r.recibo).split(' | ').forEach(v => v && x.recibos.add(v));\n if (r.cuota_trabajador) x.cuota_trabajador = money(r.cuota_trabajador);\n } else {\n if (clean(r.nombre_nomina)) x.names.add(clean(r.nombre_nomina));\n if (clean(r.hoja_nomina)) x.sheets.add(clean(r.hoja_nomina));\n }\n x.raw.push(r);\n }\n return map;\n}\nfunction referenceMatchesName(reference, fullName) {\n const ref = plain(reference);\n const name = plain(fullName);\n if (!ref || !name) return false;\n const refTokens = ref.split(/\\s+/).filter(t => t.length >= 3 && !['para','personal','visado','walmart'].includes(t));\n if (!refTokens.length) return false;\n return refTokens.every(t => name.includes(t) || name.includes(t.replace(/ck/g, 'k')) || name.includes(t.replace(/k/g, 'ck')));\n}\nconst init = $items('Inicializar memoria')[0].json;\nconst runId = init.runId;\nconst data = $getWorkflowStaticData('global');\nconst slot = data.hnCruceIHSSNomina?.[runId];\nif (!slot) throw new Error(`No encontr茅 memoria temporal para la ejecuci贸n ${runId}.`);\nconst ihss = dedupe(slot.ihss, 'ihss');\nconst nomina = dedupe(slot.nomina, 'nomina');\nif (ihss.size === 0) throw new Error('No se extrajo ninguna identidad desde las planillas IHSS.');\nif (nomina.size === 0) throw new Error('No se extrajo ninguna identidad desde la n贸mina.');\n\n// Producci贸n Honduras: resolver empleados especiales de IHSS-RAP (ej. visado Walmart) por nombre contra planilla IHSS.\n// Esto evita que aparezcan como \"Solo IHSS\" cuando la n贸mina los trae solo en el resumen IHSS-RAP sin identidad.\nconst specialEmployees = slot.payrollSpecialEmployees || [];\nconst resolvedSpecialEmployees = [];\nconst unresolvedSpecialEmployees = [];\nfor (const sp of specialEmployees) {\n const candidates = [...ihss.values()].filter(r => {\n if (nomina.has(r.identidad)) return false;\n const names = [...r.names];\n return names.some(n => referenceMatchesName(sp.referencia, n));\n });\n if (candidates.length === 1) {\n const r = candidates[0];\n nomina.set(r.identidad, {\n identidad: r.identidad,\n names: new Set([...r.names, `IHSS-RAP: ${sp.referencia}`]),\n sheets: new Set([sp.fuente || 'IHSS-RAP', 'Personal especial / visado Walmart']),\n files: new Set(), patronales: new Set(), recibos: new Set(), cuota_trabajador: 0,\n raw: [{ identidad: r.identidad, nombre_nomina: [...r.names][0] || sp.referencia, hoja_nomina: sp.fuente || 'IHSS-RAP', fuente_especial: 'Personal especial / visado Walmart', monto: sp.monto }]\n });\n resolvedSpecialEmployees.push({ referencia: sp.referencia, identidad: r.identidad, nombre: [...r.names][0] || '', monto: sp.monto, estado: 'Resuelto por nombre contra IHSS' });\n } else {\n unresolvedSpecialEmployees.push({ referencia: sp.referencia, monto: sp.monto, coincidencias: candidates.length });\n }\n}\n\n// Fallback de producci贸n: si IHSS-RAP trae un bloque especial con monto, pero no se pudieron leer\n// los nombres de las columnas internas, se resuelve contra los empleados que est谩n solo en IHSS.\n// Solo se aplica cuando la cantidad faltante coincide con el monto especial / cuota IHSS por empleado.\nconst payrollAdjustmentForSpecial = money((slot.payrollIhssSummary || {}).ajusteIhss || 0);\nif (!resolvedSpecialEmployees.length && payrollAdjustmentForSpecial > 0.05) {\n const pendingIHSS = [...ihss.values()].filter(r => !nomina.has(r.identidad));\n const avgCuota = pendingIHSS.length\n ? money(pendingIHSS.reduce((a, r) => a + Number(r.cuota_trabajador || 0), 0) / pendingIHSS.length)\n : 0;\n const expectedSpecialCount = avgCuota ? Math.round(payrollAdjustmentForSpecial / avgCuota) : 0;\n const amountMatches = avgCuota && Math.abs(payrollAdjustmentForSpecial - money(avgCuota * pendingIHSS.length)) <= 1.00;\n if (pendingIHSS.length > 0 && pendingIHSS.length === expectedSpecialCount && amountMatches) {\n for (const r of pendingIHSS) {\n const nm = [...r.names][0] || '';\n nomina.set(r.identidad, {\n identidad: r.identidad,\n names: new Set([...r.names, 'IHSS-RAP: Personal especial / visado Walmart']),\n sheets: new Set(['IHSS-RAP', 'Personal especial / visado Walmart']),\n files: new Set(), patronales: new Set(), recibos: new Set(), cuota_trabajador: 0,\n raw: [{ identidad: r.identidad, nombre_nomina: nm, hoja_nomina: 'IHSS-RAP', fuente_especial: 'Personal especial / visado Walmart', monto: r.cuota_trabajador }]\n });\n resolvedSpecialEmployees.push({ referencia: 'Personal especial / visado Walmart', identidad: r.identidad, nombre: nm, monto: r.cuota_trabajador, estado: 'Resuelto por monto contra empleados solo IHSS' });\n }\n }\n}\n\nconst soloIHSS = [...ihss.values()].filter(r => !nomina.has(r.identidad));\nconst soloNomina = [...nomina.values()].filter(r => !ihss.has(r.identidad));\nconst ambas = [...ihss.keys()].filter(id => nomina.has(id)).length;\nconst diferenciasIdentidad = soloIHSS.length + soloNomina.length;\nconst planillas = slot.ihssPlanillas || [];\nconst duplicatePlanillas = slot.duplicatePlanillas || [];\nconst periodoMismatches = slot.periodoMismatches || [];\nconst pdfTrabajadores = planillas.reduce((a,p) => a + Number(p.trabajadores || 0), 0);\nconst pdfSalario = money(planillas.reduce((a,p) => a + Number(p.salario_reportado || 0), 0));\nconst pdfCuotaTrabajador = money(planillas.reduce((a,p) => a + Number(p.cuota_trabajador || 0), 0));\nconst pdfCuotaPatrono = money(planillas.reduce((a,p) => a + Number(p.cuota_patrono || 0), 0));\nconst pdfTotalPlanilla = money(planillas.reduce((a,p) => a + Number(p.total_planilla || 0), 0));\nconst payroll = slot.payrollIhssSummary || {};\nconst payrollBaseIhss = money(payroll.totalNominaIhss || 0);\nconst payrollAdjustmentIhss = money(payroll.ajusteIhss || 0);\nconst payrollIhss = money(payroll.totalNominaIhssAjustado || (payrollBaseIhss + payrollAdjustmentIhss));\nconst diffMontos = money(payrollIhss - pdfCuotaTrabajador);\nlet resultadoMontos = 'SIN DIFERENCIAS';\nlet lecturaMontos = 'El total descontado en n贸mina por IHSS coincide con la cuota trabajador de las planillas IHSS.';\nif (Math.abs(diffMontos) > 0.05) {\n resultadoMontos = 'REVISAR DIFERENCIA';\n lecturaMontos = diffMontos > 0 ? 'La n贸mina descont贸 m谩s IHSS que lo reportado como cuota trabajador en las planillas.' : 'La n贸mina descont贸 menos IHSS que lo reportado como cuota trabajador en las planillas.';\n}\nfunction titleCaseName(s) {\n return String(s || '')\n .toLowerCase()\n .replace(/\\b([a-z谩茅铆贸煤帽眉])/gi, c => c.toUpperCase())\n .replace(/\\bDe\\b/g, 'de')\n .replace(/\\bDel\\b/g, 'del')\n .replace(/\\bLa\\b/g, 'la');\n}\nfunction inferEmployeeFromObservation(obs) {\n const text = clean(obs);\n const patterns = [\n /(?:a|de)\\s+([A-Z脕脡脥脫脷脩][A-Za-z脕脡脥脫脷脩谩茅铆贸煤帽眉\\s]{6,80}?)(?:\\s+en\\s+|\\s+por\\s+|\\.|,|$)/i,\n /emplead[oa]\\s+([A-Z脕脡脥脫脷脩][A-Za-z脕脡脥脫脷脩谩茅铆贸煤帽眉\\s]{6,80}?)(?:\\s+en\\s+|\\s+por\\s+|\\.|,|$)/i\n ];\n for (const p of patterns) {\n const m = text.match(p);\n if (m && m[1]) {\n const name = clean(m[1]).replace(/\\s+(1Q|2Q|Colgate|Motorola|Nestle|Whirlpool|GLM).*$/i, '');\n if (name.length >= 6) return titleCaseName(name);\n }\n }\n return '';\n}\nconst observacionMonto = payroll.observacionIhss || '';\nconst empleadoObs = inferEmployeeFromObservation(observacionMonto);\nconst casosMonto = [];\nif (Math.abs(diffMontos) > 0.05) {\n casosMonto.push({\n tipo: Math.abs(diffMontos) <= 1 ? 'Diferencia menor / redondeo' : (diffMontos > 0 ? 'Cobro de m谩s en n贸mina' : 'Cobro de menos en n贸mina'),\n empleado: Math.abs(diffMontos) <= 1 ? 'Redondeo total' : (empleadoObs || 'No identificado autom谩ticamente'),\n monto: Math.abs(diffMontos),\n fuente: observacionMonto ? 'Hoja IHSS-RAP' : 'Diferencia total',\n observacion: observacionMonto || 'La n贸mina y las planillas tienen diferencia, pero la hoja IHSS-RAP no trae una observaci贸n con el empleado espec铆fico.',\n accion: Math.abs(diffMontos) <= 1 ? 'Aceptar como diferencia menor si Administraci贸n confirma redondeo.' : (empleadoObs ? 'Revisar este caso con N贸mina/Administraci贸n.' : 'Revisar detalle interno de n贸mina para identificar el colaborador.')\n });\n}\nconst identidadRows = [];\nidentidadRows.push(['Cruce de identidad - Honduras']);\nidentidadRows.push([`Per铆odo: ${init.periodoLabel || `${init.mes}/${init.anio}`}`]);\nidentidadRows.push(['Resultado', diferenciasIdentidad ? 'REVISAR DIFERENCIAS' : 'SIN DIFERENCIAS']);\nidentidadRows.push(['']);\nidentidadRows.push(['Resumen', 'Cantidad']);\nidentidadRows.push(['Identidades 煤nicas en planillas IHSS', ihss.size]);\nidentidadRows.push(['Identidades 煤nicas en n贸mina', nomina.size]);\nidentidadRows.push(['Coinciden en ambas', ambas]);\nidentidadRows.push(['Diferencias encontradas', diferenciasIdentidad]);\nidentidadRows.push(['Personal especial IHSS-RAP resuelto', resolvedSpecialEmployees.length]);\nidentidadRows.push(['']);\nidentidadRows.push(['Lectura r谩pida']);\nidentidadRows.push([diferenciasIdentidad ? 'Hay empleados que aparecen en una fuente y no en la otra. Revisar las secciones de abajo.' : 'No hay empleados faltantes ni sobrantes entre n贸mina y planillas IHSS.']);\nidentidadRows.push(['']);\nidentidadRows.push(['Personal especial resuelto desde IHSS-RAP']);\nidentidadRows.push(['Referencia', 'Identidad resuelta', 'Nombre en IHSS', 'Monto', 'Estado']);\nif (resolvedSpecialEmployees.length) for (const r of resolvedSpecialEmployees) identidadRows.push([r.referencia, r.identidad, r.nombre, r.monto, r.estado]); else identidadRows.push(['No aplica', '', '', '', '']);\nif (unresolvedSpecialEmployees.length) {\n identidadRows.push(['']);\n identidadRows.push(['Personal especial NO resuelto autom谩ticamente']);\n identidadRows.push(['Referencia', 'Monto', 'Coincidencias posibles']);\n for (const r of unresolvedSpecialEmployees) identidadRows.push([r.referencia, r.monto, r.coincidencias]);\n}\nidentidadRows.push(['']);\nidentidadRows.push(['Empleados en planilla IHSS que NO aparecen en n贸mina']);\nidentidadRows.push(['Identidad', 'Nombre en planilla IHSS', 'N煤mero patronal', 'Archivo planilla', 'Qu茅 significa']);\nif (soloIHSS.length) for (const r of soloIHSS) identidadRows.push([r.identidad, [...r.names].join(' | '), [...r.patronales].join(' | '), [...r.files].join(' | '), 'Est谩 en IHSS, pero no aparece en la n贸mina cargada.']); else identidadRows.push(['Sin diferencias', '', '', '', '']);\nidentidadRows.push(['']);\nidentidadRows.push(['Empleados en n贸mina que NO aparecen en planilla IHSS']);\nidentidadRows.push(['Identidad', 'Nombre en n贸mina', 'Hoja n贸mina', 'Qu茅 significa']);\nif (soloNomina.length) for (const r of soloNomina) identidadRows.push([r.identidad, [...r.names].join(' | '), [...r.sheets].join(' | '), 'Est谩 en n贸mina, pero no aparece en las planillas IHSS cargadas.']); else identidadRows.push(['Sin diferencias', '', '', '']);\nif (duplicatePlanillas.length) {\n identidadRows.push(['']);\n identidadRows.push(['Planillas duplicadas ignoradas']);\n identidadRows.push(['Archivo duplicado', 'Archivo original', 'N煤mero patronal', 'Recibo', 'Per铆odo', 'Cuota trabajador']);\n for (const d of duplicatePlanillas) identidadRows.push([d.archivo_duplicado, d.archivo_original, d.numero_patronal, d.recibo, d.periodo, d.cuota_trabajador]);\n}\nif (periodoMismatches.length) {\n identidadRows.push(['']);\n identidadRows.push(['Alertas de per铆odo']);\n identidadRows.push(['Archivo', 'Per铆odo detectado', 'Per铆odo esperado']);\n for (const p of periodoMismatches) identidadRows.push([p.archivo_planilla, p.periodo_detectado, p.periodo_esperado]);\n}\nconst montosRows = [];\nmontosRows.push(['Cruce de montos IHSS - Honduras']);\nmontosRows.push([`Per铆odo: ${init.periodoLabel || `${init.mes}/${init.anio}`}`]);\nmontosRows.push(['Resultado', resultadoMontos]);\nmontosRows.push(['']);\nmontosRows.push(['Resumen', 'Monto']);\nmontosRows.push(['IHSS descontado en n贸mina base', payrollBaseIhss]);\nmontosRows.push(['Ajuste IHSS personal especial / visado', payrollAdjustmentIhss]);\nmontosRows.push(['IHSS descontado en n贸mina ajustado', payrollIhss]);\nmontosRows.push(['Cuota trabajador seg煤n planillas IHSS', pdfCuotaTrabajador]);\nmontosRows.push(['Diferencia', diffMontos]);\nmontosRows.push(['']);\nmontosRows.push(['Lectura r谩pida']);\nmontosRows.push([lecturaMontos]);\nmontosRows.push(['']);\nmontosRows.push(['Observaci贸n en n贸mina']);\nmontosRows.push([payroll.observacionIhss || (Math.abs(diffMontos) > 0.05 ? 'No se encontr贸 observaci贸n en la hoja IHSS-RAP. Revisar con Administraci贸n.' : 'Sin observaci贸n necesaria.')]);\nmontosRows.push(['']);\nmontosRows.push(['Casos con diferencia']);\nmontosRows.push(['Tipo', 'Empleado / referencia', 'Monto diferencia', 'Fuente', 'Acci贸n sugerida']);\nif (casosMonto.length) {\n for (const c of casosMonto) montosRows.push([c.tipo, c.empleado, c.monto, c.fuente, c.accion]);\n} else {\n montosRows.push(['Sin diferencias', '', '', '', '']);\n}\nmontosRows.push(['']);\nmontosRows.push(['Detalle / observaci贸n del caso']);\nif (casosMonto.length) {\n for (const c of casosMonto) montosRows.push([c.observacion]);\n} else {\n montosRows.push(['No aplica.']);\n}\nmontosRows.push(['']);\nmontosRows.push(['Qu茅 se compara']);\nmontosRows.push(['N贸mina', 'Columna / dato', 'Explicaci贸n']);\nmontosRows.push(['N贸mina Honduras', 'Hoja IHSS-RAP > TOTAL Nomina + ajustes especiales', 'Total mensual descontado al colaborador por IHSS, incluyendo personal especial si est谩 documentado en IHSS-RAP.']);\nmontosRows.push(['Planillas IHSS', 'CUOTA TRABAJADOR', 'Total que IHSS espera cobrar a los colaboradores. No incluye cuota patronal.']);\nconst resumenRows = [];\nresumenRows.push(['Resumen de planillas IHSS - Honduras']);\nresumenRows.push([`Per铆odo: ${init.periodoLabel || `${init.mes}/${init.anio}`}`]);\nresumenRows.push(['']);\nresumenRows.push(['Archivo PDF', 'N煤mero patronal', 'Recibo', 'Trabajadores', 'Salario reportado', 'Cuota trabajador', 'Cuota patrono', 'Total planilla']);\nfor (const p of planillas) resumenRows.push([p.archivo_planilla, p.numero_patronal, p.recibo, p.trabajadores, p.salario_reportado, p.cuota_trabajador, p.cuota_patrono, p.total_planilla]);\nresumenRows.push(['TOTAL', '', '', pdfTrabajadores, pdfSalario, pdfCuotaTrabajador, pdfCuotaPatrono, pdfTotalPlanilla]);\nif (duplicatePlanillas.length) {\n resumenRows.push(['']);\n resumenRows.push(['Duplicados ignorados']);\n resumenRows.push(['Archivo duplicado', 'Archivo original', 'N煤mero patronal', 'Recibo', 'Cuota trabajador']);\n for (const d of duplicatePlanillas) resumenRows.push([d.archivo_duplicado, d.archivo_original, d.numero_patronal, d.recibo, d.cuota_trabajador]);\n}\nconst detalleRows = [];\ndetalleRows.push(['Detalle IHSS seg煤n n贸mina - Honduras']);\ndetalleRows.push([`Per铆odo: ${init.periodoLabel || `${init.mes}/${init.anio}`}`]);\ndetalleRows.push(['']);\ndetalleRows.push(['Concepto / bloque', 'Moneda', 'Monto IHSS n贸mina']);\nfor (const b of (slot.payrollIhssBlocks || [])) detalleRows.push([b.concepto, b.moneda, b.monto]);\ndetalleRows.push(['TOTAL N贸mina IHSS base', 'Lps', payrollBaseIhss]);\ndetalleRows.push(['Ajuste personal especial / visado', 'Lps', payrollAdjustmentIhss]);\ndetalleRows.push(['Motivo ajuste especial', '', payroll.ajusteIhssMotivo || (payrollAdjustmentIhss ? 'Ajuste especial detectado.' : 'Sin ajuste especial expl铆cito.')]);\ndetalleRows.push(['TOTAL N贸mina IHSS ajustado', 'Lps', payrollIhss]);\ndetalleRows.push(['Total Planillas seg煤n n贸mina', 'Lps', money(payroll.totalPlanillasIhss || 0)]);\ndetalleRows.push(['Diferencia seg煤n n贸mina ajustada', 'Lps', diffMontos]);\ndetalleRows.push(['Observaci贸n', '', payroll.observacionIhss || '']);\nif (specialEmployees.length) {\n detalleRows.push(['']);\n detalleRows.push(['Personal especial detectado en IHSS-RAP']);\n detalleRows.push(['Referencia', 'Monto', 'Identidad resuelta', 'Nombre resuelto']);\n for (const sp of specialEmployees) {\n const res = resolvedSpecialEmployees.find(r => plain(r.referencia) === plain(sp.referencia));\n detalleRows.push([sp.referencia, sp.monto, res?.identidad || '', res?.nombre || '']);\n }\n}\nconst summary = { ihss: ihss.size, nomina: nomina.size, ambas, soloIHSS: soloIHSS.length, soloNomina: soloNomina.length, diferenciasIdentidad, pdfFiles: slot.pdfFiles || [], payrollFile: slot.payrollFile || '', planillas: planillas.length, duplicatePlanillas: duplicatePlanillas.length, duplicatePlanillasDetalle: duplicatePlanillas, periodoMismatches: periodoMismatches.length, periodoMismatchesDetalle: periodoMismatches, pdfTrabajadores, pdfCuotaTrabajador, pdfCuotaPatrono, pdfTotalPlanilla, payrollBaseIhss, payrollAdjustmentIhss, payrollIhss, diffMontos, resultadoMontos, observacionIhss: payroll.observacionIhss || '', ajusteIhssMotivo: payroll.ajusteIhssMotivo || '', specialEmployees, resolvedSpecialEmployees, unresolvedSpecialEmployees, casosMonto, casosMontoCount: casosMonto.length, casoMontoPrincipal: casosMonto[0] || null };\ndelete data.hnCruceIHSSNomina[runId];\nreturn [{ json: { runId, reportTitle: init.reportTitle, emailTo: init.emailTo || 'iaracena@gomezleemarketing.com', identidadRows, montosRows, resumenRows, detalleRows, summary, rowCounts: { identidad: identidadRows.length, montos: montosRows.length, resumen: resumenRows.length, detalle: detalleRows.length } } }];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 2544,
+ 368
+ ],
+ "id": "e1391cfa-4e6b-428d-a5b2-4495e3428383",
+ "name": "Construir reporte producci贸n"
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "https://sheets.googleapis.com/v4/spreadsheets",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={{ JSON.stringify({ properties: { title: $json.reportTitle }, sheets: [ { properties: { title: 'Cruce Identidad' } }, { properties: { title: 'Cruce Montos IHSS' } }, { properties: { title: 'Resumen Planillas' } }, { properties: { title: 'Detalle N贸mina IHSS' } } ] }) }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 2784,
+ 368
+ ],
+ "id": "286439d1-a4bf-4167-80ec-0e08edaccac8",
+ "name": "Crear Google Sheet Reporte",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.spreadsheetId }}/values:batchUpdate",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={{ JSON.stringify({ valueInputOption: 'USER_ENTERED', data: [ { range: 'Cruce Identidad!A1', majorDimension: 'ROWS', values: $('Construir reporte producci贸n').item.json.identidadRows }, { range: 'Cruce Montos IHSS!A1', majorDimension: 'ROWS', values: $('Construir reporte producci贸n').item.json.montosRows }, { range: 'Resumen Planillas!A1', majorDimension: 'ROWS', values: $('Construir reporte producci贸n').item.json.resumenRows }, { range: 'Detalle N贸mina IHSS!A1', majorDimension: 'ROWS', values: $('Construir reporte producci贸n').item.json.detalleRows } ] }) }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 3008,
+ 368
+ ],
+ "id": "bc29c2b3-b24c-4da8-9d3d-c4c34dcedd6d",
+ "name": "Escribir reporte",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $('Crear Google Sheet Reporte').item.json.spreadsheetId }}:batchUpdate",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleSheetsOAuth2Api",
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={{ (() => {\n const sheets = $('Crear Google Sheet Reporte').item.json.sheets || [];\n const id = title => (sheets.find(s => s.properties.title === title) || {}).properties.sheetId;\n const requests = [];\n const titles = ['Cruce Identidad','Cruce Montos IHSS','Resumen Planillas','Detalle N贸mina IHSS'];\n for (const title of titles) {\n const sid = id(title);\n if (sid === undefined) continue;\n requests.push(\n { repeatCell: { range: { sheetId: sid, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 0.91, green: 0.96, blue: 0.91 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { autoResizeDimensions: { dimensions: { sheetId: sid, dimension: 'COLUMNS', startIndex: 0, endIndex: 10 } } }\n );\n }\n const sidId = id('Cruce Identidad');\n if (sidId !== undefined) {\n requests.push({ repeatCell: { range: { sheetId: sidId, startRowIndex: 4, endRowIndex: 5 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidId, startRowIndex: 14, endRowIndex: 15 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidId, startRowIndex: 19, endRowIndex: 20 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n }\n const sidMontos = id('Cruce Montos IHSS');\n if (sidMontos !== undefined) {\n requests.push({ repeatCell: { range: { sheetId: sidMontos, startRowIndex: 4, endRowIndex: 8, startColumnIndex: 1, endColumnIndex: 2 }, cell: { userEnteredFormat: { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } } }, fields: 'userEnteredFormat.numberFormat' } });\n requests.push({ repeatCell: { range: { sheetId: sidMontos, startRowIndex: 4, endRowIndex: 5 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidMontos, startRowIndex: 15, endRowIndex: 17 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 1, green: 0.95, blue: 0.85 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidMontos, startRowIndex: 16, startColumnIndex: 2, endColumnIndex: 3 }, cell: { userEnteredFormat: { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } } }, fields: 'userEnteredFormat.numberFormat' } });\n }\n const sidResumen = id('Resumen Planillas');\n if (sidResumen !== undefined) {\n requests.push({ repeatCell: { range: { sheetId: sidResumen, startRowIndex: 3, endRowIndex: 4 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidResumen, startRowIndex: 4, startColumnIndex: 4, endColumnIndex: 8 }, cell: { userEnteredFormat: { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } } }, fields: 'userEnteredFormat.numberFormat' } });\n }\n const sidDetalle = id('Detalle N贸mina IHSS');\n if (sidDetalle !== undefined) {\n requests.push({ repeatCell: { range: { sheetId: sidDetalle, startRowIndex: 3, endRowIndex: 4 }, cell: { userEnteredFormat: { textFormat: { bold: true }, backgroundColor: { red: 0.89, green: 0.95, blue: 1 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } });\n requests.push({ repeatCell: { range: { sheetId: sidDetalle, startRowIndex: 4, startColumnIndex: 2, endColumnIndex: 3 }, cell: { userEnteredFormat: { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } } }, fields: 'userEnteredFormat.numberFormat' } });\n }\n return JSON.stringify({ requests });\n})() }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 3248,
+ 368
+ ],
+ "id": "d72c6cb7-6ed8-4682-b89b-2c2745259891",
+ "name": "Formatear reporte",
+ "credentials": {
+ "googleSheetsOAuth2Api": {
+ "id": "K0hDZh3a85MpOHCs",
+ "name": "Google Sheets account 2"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "=https://www.googleapis.com/drive/v3/files/{{ $('Crear Google Sheet Reporte').item.json.spreadsheetId }}/permissions?sendNotificationEmail=false&supportsAllDrives=true",
+ "authentication": "predefinedCredentialType",
+ "nodeCredentialType": "googleDriveOAuth2Api",
+ "sendBody": true,
+ "specifyBody": "json",
+ "jsonBody": "={{ JSON.stringify({ role: 'writer', type: 'user', emailAddress: 'iaracena@gomezleemarketing.com' }) }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.3,
+ "position": [
+ 3472,
+ 368
+ ],
+ "id": "e43983bb-e876-47e0-9d60-0164e42b19b6",
+ "name": "Compartir con Isaac",
+ "credentials": {
+ "googleDriveOAuth2Api": {
+ "id": "g23xdGLZRzBGqKgH",
+ "name": "Isaac - Google Drive"
+ }
+ },
+ "onError": "continueRegularOutput"
+ },
+ {
+ "parameters": {
+ "sendTo": "iaracena@gomezleemarketing.com, ymadera@gomezleemarketing.com",
+ "subject": "={{ (() => { const init = $('Inicializar memoria').item.json || {}; return `Cruce Seguridad Social Honduras - ${init.periodoLabel || (init.mes + '/' + init.anio)}`; })() }}",
+ "message": "={{ (() => {\n const summary = $('Construir reporte producci贸n').item.json.summary || {};\n const sheetUrl = $('Crear Google Sheet Reporte').item.json.spreadsheetUrl || '#';\n const init = $('Inicializar memoria').item.json || {};\n const periodo = init.periodoLabel || `${init.mes}/${init.anio}`;\n const fmt = n => Number(n || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n const diferenciasIdentidad = Number(summary.diferenciasIdentidad || 0);\n const diffMontos = Number(summary.diffMontos || 0);\n const diffReal = Math.abs(diffMontos) > 0.05;\n const statusText = diferenciasIdentidad || diffReal ? 'COMPLETADO 路 REVISAR HALLAZGOS' : 'COMPLETADO 路 SIN DIFERENCIAS';\n const statusBg = diferenciasIdentidad || diffReal ? '#fff7e6' : '#eefaf0';\n const statusBorder = diferenciasIdentidad || diffReal ? '#f59e0b' : '#5fbf47';\n const statusColor = diferenciasIdentidad || diffReal ? '#92400e' : '#2f7d32';\n const logo = 'data:image/png;base64,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';\n const caso = summary.casoMontoPrincipal || null;\n const row = (label, value) => `\n \n | ${label} | \n ${value} | \n
`;\n const section = (title, rowsHtml) => `\n `;\n const casoHtml = caso ? section('Caso de monto a revisar',\n row('Tipo', caso.tipo || 'Diferencia de monto') +\n row('Empleado / referencia', caso.empleado || 'No identificado') +\n row('Monto diferencia', `L ${fmt(caso.monto || Math.abs(diffMontos))}`) +\n `| Observaci贸n: ${caso.observacion || summary.observacionIhss || 'Revisar con Administraci贸n.'} |
`\n ) : '';\n return `\n \n
\n \n \n SEGURIDAD SOCIAL 路 HONDURAS \n Cruce mensual completado\n ${periodo} \n \n \n \n PLANILLAS IHSS ${summary.ihss || 0} |
| \n N脫MINA ${summary.nomina || 0} |
| \n DIF. MONTO L ${fmt(diffMontos)} |
| \n \n \n ${section('Identidad',\n row('Resultado', diferenciasIdentidad ? 'Revisar diferencias' : 'Sin diferencias') +\n row('Coinciden en ambas', summary.ambas || 0) +\n row('Solo IHSS / Solo n贸mina', `${summary.soloIHSS || 0} / ${summary.soloNomina || 0}`)\n )}\n ${section('Montos IHSS',\n row('IHSS descontado en n贸mina', `L ${fmt(summary.payrollIhss)}`) +\n row('Cuota trabajador planillas', `L ${fmt(summary.pdfCuotaTrabajador)}`) +\n row('Diferencia', `L ${fmt(diffMontos)}`)\n )}\n ${casoHtml}\n \n Archivos procesados N贸mina: ${summary.payrollFile || ''} Planillas PDF: ${(summary.pdfFiles || []).join(', ')} \n |
\n
\n
`;\n})() }}",
+ "options": {
+ "appendAttribution": false
+ }
+ },
+ "type": "n8n-nodes-base.gmail",
+ "typeVersion": 2.2,
+ "position": [
+ 3696,
+ 368
+ ],
+ "id": "5ba64fdc-0918-47a8-b07d-95774ad08a76",
+ "name": "Enviar correo",
+ "executeOnce": true,
+ "webhookId": "gmail-cruce-ihss-hn",
+ "credentials": {
+ "gmailOAuth2": {
+ "id": "UDcO1FLJqA453V2D",
+ "name": "Gmail account 3"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "content": "## INICIO DEL CRUCE HONDURAS\n\nEl usuario sube el mes a validar, la n贸mina mensual de Honduras y una o varias planillas IHSS en PDF. El flujo inicializa la ejecuci贸n, guarda el per铆odo seleccionado y prepara la memoria temporal para acumular los datos de identidad, planillas y montos IHSS.",
+ "height": 304,
+ "width": 848
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "typeVersion": 1,
+ "position": [
+ -1376,
+ 208
+ ],
+ "id": "27144621-a6ad-426c-b022-0876fd9837cf",
+ "name": "Sticky Note"
+ },
+ {
+ "parameters": {
+ "content": "## LECTURA DE PLANILLAS Y N脫MINA\n\nEl flujo extrae identidades desde las planillas IHSS y desde las hojas v谩lidas de la n贸mina. Tambi茅n lee la hoja IHSS-RAP para obtener el total mensual descontado por IHSS, separando el cruce de identidad del cruce de montos.",
+ "height": 928,
+ "width": 1968,
+ "color": 5
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "typeVersion": 1,
+ "position": [
+ 0,
+ 0
+ ],
+ "id": "5493f7c6-783e-4367-9e0e-9e8054b93500",
+ "name": "Sticky Note1"
+ },
+ {
+ "parameters": {
+ "content": "## REPORTE FINAL Y CORREO\n\nCuando ambas ramas terminan, el flujo genera un reporte en Google Sheets con cruce de identidad, cruce de montos IHSS, resumen de planillas y detalle de n贸mina. El correo resume los hallazgos y muestra si hay diferencias o casos puntuales para revisar.",
+ "height": 336,
+ "width": 1680,
+ "color": 4
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "typeVersion": 1,
+ "position": [
+ 2224,
+ 240
+ ],
+ "id": "6988f020-8306-464b-b511-8ce4854a0ed5",
+ "name": "Sticky Note2"
+ }
+ ],
+ "connections": {
+ "On form submission": {
+ "main": [
+ [
+ {
+ "node": "Inicializar memoria",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Inicializar memoria": {
+ "main": [
+ [
+ {
+ "node": "Separar planillas PDF",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Preparar n贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Separar planillas PDF": {
+ "main": [
+ [
+ {
+ "node": "Extraer texto PDF",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Extraer texto PDF": {
+ "main": [
+ [
+ {
+ "node": "Guardar IHSS en memoria",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Guardar IHSS en memoria": {
+ "main": [
+ [
+ {
+ "node": "Esperar ramas",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Preparar n贸mina": {
+ "main": [
+ [
+ {
+ "node": "Actualizar staging N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Actualizar staging N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Esperar conversi贸n",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Esperar conversi贸n": {
+ "main": [
+ [
+ {
+ "node": "Listar hojas N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Listar hojas N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Preparar hojas N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Preparar hojas N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Loop hojas N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Loop hojas N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Esperar ramas",
+ "type": "main",
+ "index": 1
+ }
+ ],
+ [
+ {
+ "node": "Leer hoja N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Leer hoja N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Acumular identidades N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Acumular identidades N贸mina": {
+ "main": [
+ [
+ {
+ "node": "Loop hojas N贸mina",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Esperar ramas": {
+ "main": [
+ [
+ {
+ "node": "Construir reporte producci贸n",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Crear Google Sheet Reporte": {
+ "main": [
+ [
+ {
+ "node": "Escribir reporte",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Escribir reporte": {
+ "main": [
+ [
+ {
+ "node": "Formatear reporte",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Formatear reporte": {
+ "main": [
+ [
+ {
+ "node": "Compartir con Isaac",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Compartir con Isaac": {
+ "main": [
+ [
+ {
+ "node": "Enviar correo",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Construir reporte producci贸n": {
+ "main": [
+ [
+ {
+ "node": "Crear Google Sheet Reporte",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "authors": "Isaac Aracena",
+ "name": "Version 1d2417e8",
+ "description": "",
+ "autosaved": true,
+ "workflowPublishHistory": [
+ {
+ "createdAt": "2026-07-04T14:18:23.846Z",
+ "id": 2446,
+ "workflowId": "j04wjKihKPzBIqW8",
+ "versionId": "1d2417e8-d16e-4c4e-ae50-0181cc6c6b4f",
+ "event": "activated",
+ "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
+ }
+ ]
+ }
+}
\ No newline at end of file