2447 lines
242 KiB
JSON
2447 lines
242 KiB
JSON
{
|
|
"updatedAt": "2026-07-04T14:18:46.100Z",
|
|
"createdAt": "2026-07-03T18:08:04.259Z",
|
|
"id": "loFncgFuvBlXUf8u",
|
|
"name": "Cruce de Seguridad Social Guatemala - Q1 Q2 PRODUCCIÓN FINAL",
|
|
"description": null,
|
|
"active": true,
|
|
"isArchived": false,
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"formTitle": "Cruce Seguridad Social Guatemala",
|
|
"formDescription": "Sube las dos nóminas de Guatemala del mes, el archivo Sistema Propio IGSS y la Planilla Consolidada mensual. El flujo generará un Google Sheets con hallazgos de afiliación y montos.",
|
|
"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": "Año a validar",
|
|
"fieldType": "number",
|
|
"requiredField": true
|
|
},
|
|
{
|
|
"fieldLabel": "Nómina Q1",
|
|
"fieldType": "file",
|
|
"multipleFiles": false,
|
|
"acceptFileTypes": ".xlsx, .xls, .xlsm, .csv",
|
|
"requiredField": true
|
|
},
|
|
{
|
|
"fieldLabel": "Nómina Q2",
|
|
"fieldType": "file",
|
|
"multipleFiles": false,
|
|
"acceptFileTypes": ".xlsx, .xls, .xlsm, .csv",
|
|
"requiredField": true
|
|
},
|
|
{
|
|
"fieldLabel": "Sistema Propio IGSS",
|
|
"fieldType": "file",
|
|
"multipleFiles": false,
|
|
"acceptFileTypes": ".xlsx, .xls, .xlsm, .csv",
|
|
"requiredField": true
|
|
},
|
|
{
|
|
"fieldLabel": "Planilla Consolidada",
|
|
"fieldType": "file",
|
|
"multipleFiles": false,
|
|
"acceptFileTypes": ".xlsx, .xls, .xlsm, .csv",
|
|
"requiredField": true
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"ignoreBots": true
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.formTrigger",
|
|
"typeVersion": 2.3,
|
|
"position": [
|
|
80,
|
|
128
|
|
],
|
|
"id": "81aef7de-7414-49ce-adb3-cf9b7dd3b696",
|
|
"name": "On form submission",
|
|
"webhookId": "282cf895-0642-42cc-afaa-372704ba2c5f"
|
|
},
|
|
{
|
|
"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 = `GT_SEG_${ts}_${Math.random().toString(36).slice(2, 8)}`;\n\nconst mes = String(item.json['Mes a validar'] || '').padStart(2, '0');\nconst anio = String(item.json['Año a validar'] || item.json['Ano a validar'] || now.getFullYear()).trim();\nconst meses = {\n '01':'Enero','02':'Febrero','03':'Marzo','04':'Abril',\n '05':'Mayo','06':'Junio','07':'Julio','08':'Agosto',\n '09':'Septiembre','10':'Octubre','11':'Noviembre','12':'Diciembre'\n};\nconst fechaTitulo = `${pad(now.getDate())}-${pad(now.getMonth()+1)}-${now.getFullYear()}`;\nconst periodoLabel = `${meses[mes] || mes} ${anio}`;\nconst reportTitle = `Cruce Seguridad Social Guatemala - ${periodoLabel} - ${fechaTitulo}`;\n\nconst data = $getWorkflowStaticData('global');\ndata.gtSeguridadSocial = data.gtSeguridadSocial || {};\ndata.gtSeguridadSocial[runId] = {\n startedAt: now.toISOString(),\n mes,\n anio,\n periodoLabel,\n files: { q1:'', q2:'', sistema:'', consolidado:'' },\n nomina: [],\n ajustes: [],\n empleados: [],\n planillaMensual: []\n};\n\nreturn [{\n json: { ...item.json, runId, mes, anio, periodoLabel, reportTitle, emailTo: 'iaracena@gomezleemarketing.com' },\n binary: item.binary\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
320,
|
|
128
|
|
],
|
|
"id": "cfb66319-c454-46d6-923e-5d0c1ea222d3",
|
|
"name": "Inicializar memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0];\nconst bin = init.binary || {};\nconst type = 'q1';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction looksExcel(fileName, mime) { const lower = norm(fileName); const m = norm(mime); return lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.xlsm') || lower.endsWith('.csv') || m.includes('spreadsheet') || m.includes('excel') || m.includes('csv'); }\nfunction scoreCandidate(key, fileName) {\n const text = norm(`${key} ${fileName}`);\n let score = 0;\n if (type === 'q1') { if (text.includes('nomina')) score += 20; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score += 120; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'q2') { if (text.includes('nomina')) score += 20; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score += 120; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'sistema') { if (/sistema|propio|2\\.2|igss/.test(text)) score += 120; if (/consolidada|consolidado|planilla consolidada|nomina/.test(text)) score -= 80; }\n if (type === 'consolidado') { if (/consolidada|consolidado|planilla consolidada/.test(text)) score += 120; if (/sistema|propio|2\\.2|nomina/.test(text)) score -= 80; }\n return score;\n}\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '');\n if (!looksExcel(fileName, mime)) continue;\n candidates.push({ key, file, fileName, score: scoreCandidate(key, fileName) });\n}\ncandidates.sort((a,b) => b.score - a.score);\nconst chosen = candidates[0];\nif (!chosen || chosen.score <= 0) throw new Error(`No encontré el archivo requerido para ${type}. Revisa los campos del formulario y los nombres de archivo.`);\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.json.runId];\nif (slot) slot.files[type] = chosen.fileName;\nreturn [{ json: { runId: init.json.runId, fileType: type, fileName: chosen.fileName }, binary: { data: chosen.file } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
560,
|
|
128
|
|
],
|
|
"id": "1e8d8999-f196-4241-bf57-2182f5ba5f6f",
|
|
"name": "Preparar Nómina 1Q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "update",
|
|
"fileId": {
|
|
"__rl": true,
|
|
"value": "1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4",
|
|
"mode": "list",
|
|
"cachedResultName": "Nomina Guatemala",
|
|
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4/edit"
|
|
},
|
|
"changeFileContent": true,
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.googleDrive",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
928,
|
|
128
|
|
],
|
|
"id": "75adafbf-6eb4-466c-b4da-2af5402c7b76",
|
|
"name": "Actualizar staging - Nómina 1Q",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4' }}?fields=spreadsheetId,sheets(properties(title,sheetId,hidden))",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
1536,
|
|
128
|
|
],
|
|
"id": "809fb908-59f6-45f9-8a11-5a38efda1d19",
|
|
"name": "Listar hojas - Nómina 1Q",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst spreadsheetId = $json.spreadsheetId || $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4';\n\nfunction norm(s) {\n return String(s || '')\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '');\n}\nfunction shouldReadPayrollSheet(title) {\n const n = norm(title);\n\n // Primero se excluyen copias y hojas auxiliares. Abril 2026 trae\n // \"Copia de 1) Nomina General 2\"; si se permite por includes('nominageneral'),\n // duplica Q1 y genera diferencias falsas.\n const excluded = [\n 'calculos', 'resumen', 'temporales', 'auditorias', 'auditoria',\n 'bono', 'movilidad', 'viaticos', 'viatico', 'combustible',\n 'combustibles', 'tabla', 'dinamica', 'copia', 'copy', 'boletas',\n 'nvscripts', 'autocrat', 'donotdelete', 'bajas'\n ];\n if (excluded.some(x => n.includes(x))) return false;\n\n // Solo la hoja principal real. No usar includes para evitar hojas copiadas.\n if (n === '1nominageneral' || n === 'nominageneral') return true;\n\n // Cualquier otra hoja no excluida se leerá como posible ajuste/complemento,\n // pero luego el nodo de parseo valida que tenga encabezado real de nómina.\n return true;\n}\n\nconst sheets = ($json.sheets || []).map(s => s.properties || {});\nconst selected = sheets\n .filter(p => p.title && shouldReadPayrollSheet(p.title))\n .map(p => p.title);\n\nif (!selected.some(t => norm(t).includes('nominageneral'))) {\n throw new Error(`No encontré la hoja 1) Nomina General. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\n}\nif (!selected.length) {\n throw new Error(`No encontré hojas válidas para leer en la nómina. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\n}\n\nreturn [{\n json: {\n runId: init.runId,\n spreadsheetId,\n sheetTitles: selected,\n ranges: selected.map(t => `'${String(t).replace(/'/g, \"''\")}'!A:AZ`)\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1776,
|
|
128
|
|
],
|
|
"id": "4c813693-118f-4f90-87d9-336d57082397",
|
|
"name": "Resolver hoja Nómina 1Q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + $json.spreadsheetId + '/values:batchGet?' + $json.ranges.map(r => 'ranges=' + encodeURIComponent(r)).join('&') + '&valueRenderOption=UNFORMATTED_VALUE&dateTimeRenderOption=SERIAL_NUMBER' }}",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
2032,
|
|
128
|
|
],
|
|
"id": "a8d4b5d4-ff8b-43e2-91d8-16d5897e59ed",
|
|
"name": "Leer Nómina 1Q",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const qLabel = '1Q';\nconst init = $items('Inicializar memoria')[0].json;\nconst valueRanges = $json.valueRanges || [];\n\nfunction clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction removeAccents(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, ''); }\nfunction normSheet(s) { return removeAccents(s).toLowerCase().replace(/[^a-z0-9]/g, ''); }\nfunction normalizeName(s) {\n return clean(s)\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase()\n .replace(/[^A-Z0-9 ]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\nfunction toNumber(v) {\n if (v === null || v === undefined || v === '') return 0;\n if (typeof v === 'number') return Number.isFinite(v) ? v : 0;\n let s = String(v).trim().replace(/Q|\\$|USD|GTQ|,/gi, '').replace(/\\s/g, '');\n if (/^\\((.*)\\)$/.test(s)) s = '-' + s.replace(/^\\(|\\)$/g, '');\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n}\nfunction normalizeAf(v) {\n const raw = clean(v);\n if (!raw || /pendiente|n\\/a|na|sin/i.test(raw)) {\n return { value:'', status: raw ? 'Pendiente/Vacío' : 'Vacío', raw };\n }\n let digits = '';\n if (typeof v === 'number') {\n digits = Math.trunc(v).toString();\n } else {\n const s = raw.replace(/,/g, '').trim();\n if (/e\\+?/i.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) digits = Math.trunc(n).toString();\n } else {\n digits = s.replace(/\\D/g, '');\n }\n }\n if (!digits || /^0+$/.test(digits)) return { value:'', status:'Inválido', raw };\n if (digits.length < 8 || digits.length > 13) return { value:'', status:'Longitud inválida', raw };\n return { value:digits, status: /e\\+?/i.test(String(raw)) ? 'Válido científico' : 'Válido', raw };\n}\nfunction sheetNameFromRange(range) {\n let left = String(range || '').split('!')[0] || '';\n left = left.replace(/^'/, '').replace(/'$/, '').replace(/''/g, \"'\");\n return left;\n}\nfunction isMainSheet(title) {\n const n = normSheet(title);\n // Solo la hoja principal real; NO contar copias como \"Copia de 1) Nomina General 2\".\n return n === '1nominageneral' || n === 'nominageneral';\n}\nfunction looksLikePayrollSheet(values) {\n const header = (values[0] || []).map(v => removeAccents(clean(v)).toLowerCase().replace(/[^a-z0-9]/g, ''));\n return header[0] === 'proyecto'\n && header[5] === 'igss'\n && (header[7] === 'nombrecompleto' || header[7].includes('nombre'))\n && header[14].includes('salario')\n && header[19].includes('factor');\n}\nfunction parsePayrollRows(values, sheetTitle, sourceType) {\n const records = [];\n if (!looksLikePayrollSheet(values)) return records;\n let seen = false;\n for (let i = 1; i < values.length; i++) {\n const row = values[i] || [];\n const proyecto = clean(row[0]);\n const moneda = clean(row[1]);\n const igss = normalizeAf(row[5]);\n const nombre = clean(row[7]);\n\n if (seen && !proyecto && !nombre && !clean(row[5])) break;\n if (!proyecto || !nombre || /^total/i.test(proyecto) || /^total/i.test(nombre)) continue;\n seen = true;\n\n const factor = clean(row[19]);\n const factorNorm = removeAccents(factor).toLowerCase();\n const factorSi = /^(si|sí|yes|y|true|1)$/i.test(factorNorm);\n\n const salario = toNumber(row[14]);\n const vacaciones = toNumber(row[16]);\n const horasExtras = toNumber(row[18]);\n const variable = toNumber(row[21]);\n const montoAfectoOriginal = salario + vacaciones + horasExtras + (factorSi ? variable : 0);\n if (Math.abs(montoAfectoOriginal) <= 0.004 && sourceType === 'ajuste_candidato') continue;\n\n records.push({\n q: qLabel,\n sheetTitle,\n rowNumber: i + 1,\n sourceType,\n proyecto_nomina: proyecto,\n moneda,\n nombre_nomina: nombre,\n nombre_norm: normalizeName(nombre),\n igss_original: igss.raw,\n igss_norm: igss.value,\n igss_estado: igss.status,\n factor_prestacional: factor,\n salario_periodo: Number(salario.toFixed(2)),\n vacaciones: Number(vacaciones.toFixed(2)),\n horas_extras: Number(horasExtras.toFixed(2)),\n variable: Number(variable.toFixed(2)),\n variable_afecta: factorSi ? 'Sí' : 'No',\n monto_afecto_original: Number(montoAfectoOriginal.toFixed(2)),\n monto_afecto_gtq: Number(montoAfectoOriginal.toFixed(2)),\n regla_aplicada: factorSi ? 'Sueldo + vacaciones + horas extras + variable' : 'Sueldo + vacaciones + horas extras'\n });\n }\n return records;\n}\nfunction samePerson(a, b) {\n if (a.igss_norm && b.igss_norm && a.igss_norm === b.igss_norm) return true;\n return normalizeName(a.nombre_nomina) === normalizeName(b.nombre_nomina);\n}\nfunction amountEquals(a, b) {\n return Math.abs(Number(a || 0) - Number(b || 0)) <= 0.05;\n}\nfunction deriveAdjustments(baseRecords, candidateRecords) {\n const ajustes = [];\n\n function pushAdjustment(c, amount, tipo, referencia, observacion) {\n if (Math.abs(amount) <= 0.05) return;\n ajustes.push({\n ...c,\n sourceType: 'ajuste',\n monto_ajuste_original: Number(amount.toFixed(2)),\n monto_ajuste_gtq: Number(amount.toFixed(2)),\n tipo_ajuste: tipo,\n referencia: referencia || '',\n observacion_ajuste: observacion || (tipo === 'Corrección de monto'\n ? 'Se incluyó solo la diferencia entre la fila corregida y la fila principal.'\n : 'Se incluyó como complemento de nómina.')\n });\n }\n\n const used = new Set();\n const groups = new Map();\n candidateRecords.forEach((c, idx) => {\n c.__idx = idx;\n const key = `${c.sheetTitle || ''}||${c.igss_norm || c.nombre_norm || normalizeName(c.nombre_nomina)}`;\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key).push(c);\n });\n\n // Caso abril 2026: la hoja Complemento trae valor anterior y valor corregido\n // de la variable de Oliver. No se suman ambas filas completas; se suma solo\n // la diferencia entre variable corregida y variable ya incluida en la nómina.\n for (const group of groups.values()) {\n if (!group.length) continue;\n const c0 = group[0];\n const isComplementSheet = /complement|complemento/i.test(removeAccents(c0.sheetTitle || '').toLowerCase());\n if (!isComplementSheet) continue;\n\n const possibleBase = baseRecords.filter(b => samePerson(b, c0));\n const baseAny = possibleBase[0];\n if (!baseAny) continue;\n\n const baseVariable = Number(baseAny.variable || 0);\n if (Math.abs(baseVariable) <= 0.05) continue;\n\n const variableOnly = group.filter(c =>\n Math.abs(Number(c.salario_periodo || 0)) <= 0.05 &&\n Math.abs(Number(c.vacaciones || 0)) <= 0.05 &&\n Math.abs(Number(c.horas_extras || 0)) <= 0.05 &&\n Math.abs(Number(c.variable || 0)) > 0.05 &&\n amountEquals(c.monto_afecto_original, c.variable)\n );\n\n const oldValue = variableOnly.find(c => amountEquals(c.monto_afecto_original, baseVariable));\n const corrected = variableOnly\n .filter(c => c.monto_afecto_original > baseVariable + 0.05)\n .sort((a,b) => Number(b.monto_afecto_original || 0) - Number(a.monto_afecto_original || 0))[0];\n\n if (oldValue && corrected) {\n for (const c of variableOnly) used.add(c.__idx);\n const diff = Number((corrected.monto_afecto_original - baseVariable).toFixed(2));\n pushAdjustment(\n corrected,\n diff,\n 'Corrección de variable',\n `Variable principal ${baseVariable}`,\n 'Se incluyó solo la diferencia entre la variable corregida y la variable ya incluida en la nómina principal.'\n );\n }\n }\n\n for (const c of candidateRecords) {\n if (used.has(c.__idx)) continue;\n const possibleBase = baseRecords.filter(b => samePerson(b, c));\n const baseMatch = possibleBase.find(b => amountEquals(b.monto_afecto_original, c.monto_afecto_original));\n const baseAny = possibleBase[0];\n\n if (baseMatch) {\n // Esta fila es una repetición de la fila principal, no se suma como ajuste.\n continue;\n }\n\n let adjustmentAmount = c.monto_afecto_original;\n let tipo = 'Complemento';\n let baseReference = '';\n\n const isComplementSheet = /complement|complemento/i.test(removeAccents(c.sheetTitle || '').toLowerCase());\n if (!isComplementSheet && baseAny && c.monto_afecto_original > baseAny.monto_afecto_original && possibleBase.some(b => amountEquals(b.monto_afecto_original, baseAny.monto_afecto_original))) {\n // Ejemplo: hoja Oliver trae una fila corregida completa. Se suma solo la diferencia contra la fila principal.\n adjustmentAmount = Number((c.monto_afecto_original - baseAny.monto_afecto_original).toFixed(2));\n tipo = 'Corrección de monto';\n baseReference = `Monto principal ${baseAny.monto_afecto_original}`;\n }\n\n pushAdjustment(c, Number(adjustmentAmount || 0), tipo, baseReference, tipo === 'Corrección de monto'\n ? 'Se incluyó solo la diferencia entre la fila corregida y la fila principal.'\n : 'Se incluyó como complemento de nómina.');\n }\n\n return ajustes;\n}\n\nconst mainRecords = [];\nconst candidates = [];\n\nfor (const vr of valueRanges) {\n const sheetTitle = sheetNameFromRange(vr.range);\n const values = vr.values || [];\n if (!values.length) continue;\n\n if (isMainSheet(sheetTitle)) {\n mainRecords.push(...parsePayrollRows(values, sheetTitle, 'principal'));\n } else {\n candidates.push(...parsePayrollRows(values, sheetTitle, 'ajuste_candidato'));\n }\n}\n\nif (!mainRecords.length) {\n throw new Error(`No pude leer registros válidos desde 1) Nomina General para ${qLabel}.`);\n}\n\nconst ajustes = deriveAdjustments(mainRecords, candidates);\n\nconst data = $getWorkflowStaticData('global');\ndata.gtSeguridadSocial = data.gtSeguridadSocial || {};\ndata.gtSeguridadSocial[init.runId] = data.gtSeguridadSocial[init.runId] || {};\ndata.gtSeguridadSocial[init.runId].nomina.push(...mainRecords);\ndata.gtSeguridadSocial[init.runId].ajustes = data.gtSeguridadSocial[init.runId].ajustes || [];\ndata.gtSeguridadSocial[init.runId].ajustes.push(...ajustes);\n\nreturn [{\n json: {\n runId: init.runId,\n quincena: qLabel,\n payrollRows: mainRecords.length,\n invalidIgssRows: mainRecords.filter(r => !r.igss_norm).length,\n adjustmentRows: ajustes.length,\n adjustmentSheets: [...new Set(ajustes.map(a => a.sheetTitle))]\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2272,
|
|
128
|
|
],
|
|
"id": "20bbc195-d609-4009-869d-7f7b05b940ee",
|
|
"name": "Guardar Nómina 1Q en memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0];\nconst bin = init.binary || {};\nconst type = 'q2';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction looksExcel(fileName, mime) { const lower = norm(fileName); const m = norm(mime); return lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.xlsm') || lower.endsWith('.csv') || m.includes('spreadsheet') || m.includes('excel') || m.includes('csv'); }\nfunction scoreCandidate(key, fileName) {\n const text = norm(`${key} ${fileName}`);\n let score = 0;\n if (type === 'q1') { if (text.includes('nomina')) score += 20; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score += 120; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'q2') { if (text.includes('nomina')) score += 20; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score += 120; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'sistema') { if (/sistema|propio|2\\.2|igss/.test(text)) score += 120; if (/consolidada|consolidado|planilla consolidada|nomina/.test(text)) score -= 80; }\n if (type === 'consolidado') { if (/consolidada|consolidado|planilla consolidada/.test(text)) score += 120; if (/sistema|propio|2\\.2|nomina/.test(text)) score -= 80; }\n return score;\n}\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '');\n if (!looksExcel(fileName, mime)) continue;\n candidates.push({ key, file, fileName, score: scoreCandidate(key, fileName) });\n}\ncandidates.sort((a,b) => b.score - a.score);\nconst chosen = candidates[0];\nif (!chosen || chosen.score <= 0) throw new Error(`No encontré el archivo requerido para ${type}. Revisa los campos del formulario y los nombres de archivo.`);\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.json.runId];\nif (slot) slot.files[type] = chosen.fileName;\nreturn [{ json: { runId: init.json.runId, fileType: type, fileName: chosen.fileName }, binary: { data: chosen.file } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2512,
|
|
128
|
|
],
|
|
"id": "651da05c-6759-48de-99e5-2eb8cb4615e4",
|
|
"name": "Preparar Nómina 2Q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "update",
|
|
"fileId": {
|
|
"__rl": true,
|
|
"value": "1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4",
|
|
"mode": "list",
|
|
"cachedResultName": "Nomina Guatemala",
|
|
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4/edit"
|
|
},
|
|
"changeFileContent": true,
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.googleDrive",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
2768,
|
|
128
|
|
],
|
|
"id": "7d919da1-d6bb-424a-a6da-5c0e8d346f75",
|
|
"name": "Actualizar staging - Nómina 2Q",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4' }}?fields=spreadsheetId,sheets(properties(title,sheetId,hidden))",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
3296,
|
|
128
|
|
],
|
|
"id": "4fb42911-ad0a-4b08-9438-5c65ef1726ae",
|
|
"name": "Listar hojas - Nómina 2Q",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst spreadsheetId = $json.spreadsheetId || $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4';\n\nfunction norm(s) {\n return String(s || '')\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '');\n}\nfunction shouldReadPayrollSheet(title) {\n const n = norm(title);\n\n // Primero se excluyen copias y hojas auxiliares. Abril 2026 trae\n // \"Copia de 1) Nomina General 2\"; si se permite por includes('nominageneral'),\n // duplica Q1 y genera diferencias falsas.\n const excluded = [\n 'calculos', 'resumen', 'temporales', 'auditorias', 'auditoria',\n 'bono', 'movilidad', 'viaticos', 'viatico', 'combustible',\n 'combustibles', 'tabla', 'dinamica', 'copia', 'copy', 'boletas',\n 'nvscripts', 'autocrat', 'donotdelete', 'bajas'\n ];\n if (excluded.some(x => n.includes(x))) return false;\n\n // Solo la hoja principal real. No usar includes para evitar hojas copiadas.\n if (n === '1nominageneral' || n === 'nominageneral') return true;\n\n // Cualquier otra hoja no excluida se leerá como posible ajuste/complemento,\n // pero luego el nodo de parseo valida que tenga encabezado real de nómina.\n return true;\n}\n\nconst sheets = ($json.sheets || []).map(s => s.properties || {});\nconst selected = sheets\n .filter(p => p.title && shouldReadPayrollSheet(p.title))\n .map(p => p.title);\n\nif (!selected.some(t => norm(t).includes('nominageneral'))) {\n throw new Error(`No encontré la hoja 1) Nomina General. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\n}\nif (!selected.length) {\n throw new Error(`No encontré hojas válidas para leer en la nómina. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\n}\n\nreturn [{\n json: {\n runId: init.runId,\n spreadsheetId,\n sheetTitles: selected,\n ranges: selected.map(t => `'${String(t).replace(/'/g, \"''\")}'!A:AZ`)\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
3616,
|
|
128
|
|
],
|
|
"id": "a7dbed9c-8d11-4190-b934-53425ef4a7ea",
|
|
"name": "Resolver hoja Nómina 2Q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + $json.spreadsheetId + '/values:batchGet?' + $json.ranges.map(r => 'ranges=' + encodeURIComponent(r)).join('&') + '&valueRenderOption=UNFORMATTED_VALUE&dateTimeRenderOption=SERIAL_NUMBER' }}",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
3936,
|
|
128
|
|
],
|
|
"id": "4a9bc146-8d26-4e33-b49d-aa9e1ef4ce35",
|
|
"name": "Leer Nómina 2Q",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const qLabel = '2Q';\nconst init = $items('Inicializar memoria')[0].json;\nconst valueRanges = $json.valueRanges || [];\n\nfunction clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction removeAccents(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, ''); }\nfunction normSheet(s) { return removeAccents(s).toLowerCase().replace(/[^a-z0-9]/g, ''); }\nfunction normalizeName(s) {\n return clean(s)\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase()\n .replace(/[^A-Z0-9 ]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\nfunction toNumber(v) {\n if (v === null || v === undefined || v === '') return 0;\n if (typeof v === 'number') return Number.isFinite(v) ? v : 0;\n let s = String(v).trim().replace(/Q|\\$|USD|GTQ|,/gi, '').replace(/\\s/g, '');\n if (/^\\((.*)\\)$/.test(s)) s = '-' + s.replace(/^\\(|\\)$/g, '');\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n}\nfunction normalizeAf(v) {\n const raw = clean(v);\n if (!raw || /pendiente|n\\/a|na|sin/i.test(raw)) {\n return { value:'', status: raw ? 'Pendiente/Vacío' : 'Vacío', raw };\n }\n let digits = '';\n if (typeof v === 'number') {\n digits = Math.trunc(v).toString();\n } else {\n const s = raw.replace(/,/g, '').trim();\n if (/e\\+?/i.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) digits = Math.trunc(n).toString();\n } else {\n digits = s.replace(/\\D/g, '');\n }\n }\n if (!digits || /^0+$/.test(digits)) return { value:'', status:'Inválido', raw };\n if (digits.length < 8 || digits.length > 13) return { value:'', status:'Longitud inválida', raw };\n return { value:digits, status: /e\\+?/i.test(String(raw)) ? 'Válido científico' : 'Válido', raw };\n}\nfunction sheetNameFromRange(range) {\n let left = String(range || '').split('!')[0] || '';\n left = left.replace(/^'/, '').replace(/'$/, '').replace(/''/g, \"'\");\n return left;\n}\nfunction isMainSheet(title) {\n const n = normSheet(title);\n // Solo la hoja principal real; NO contar copias como \"Copia de 1) Nomina General 2\".\n return n === '1nominageneral' || n === 'nominageneral';\n}\nfunction looksLikePayrollSheet(values) {\n const header = (values[0] || []).map(v => removeAccents(clean(v)).toLowerCase().replace(/[^a-z0-9]/g, ''));\n return header[0] === 'proyecto'\n && header[5] === 'igss'\n && (header[7] === 'nombrecompleto' || header[7].includes('nombre'))\n && header[14].includes('salario')\n && header[19].includes('factor');\n}\nfunction parsePayrollRows(values, sheetTitle, sourceType) {\n const records = [];\n if (!looksLikePayrollSheet(values)) return records;\n let seen = false;\n for (let i = 1; i < values.length; i++) {\n const row = values[i] || [];\n const proyecto = clean(row[0]);\n const moneda = clean(row[1]);\n const igss = normalizeAf(row[5]);\n const nombre = clean(row[7]);\n\n if (seen && !proyecto && !nombre && !clean(row[5])) break;\n if (!proyecto || !nombre || /^total/i.test(proyecto) || /^total/i.test(nombre)) continue;\n seen = true;\n\n const factor = clean(row[19]);\n const factorNorm = removeAccents(factor).toLowerCase();\n const factorSi = /^(si|sí|yes|y|true|1)$/i.test(factorNorm);\n\n const salario = toNumber(row[14]);\n const vacaciones = toNumber(row[16]);\n const horasExtras = toNumber(row[18]);\n const variable = toNumber(row[21]);\n const montoAfectoOriginal = salario + vacaciones + horasExtras + (factorSi ? variable : 0);\n if (Math.abs(montoAfectoOriginal) <= 0.004 && sourceType === 'ajuste_candidato') continue;\n\n records.push({\n q: qLabel,\n sheetTitle,\n rowNumber: i + 1,\n sourceType,\n proyecto_nomina: proyecto,\n moneda,\n nombre_nomina: nombre,\n nombre_norm: normalizeName(nombre),\n igss_original: igss.raw,\n igss_norm: igss.value,\n igss_estado: igss.status,\n factor_prestacional: factor,\n salario_periodo: Number(salario.toFixed(2)),\n vacaciones: Number(vacaciones.toFixed(2)),\n horas_extras: Number(horasExtras.toFixed(2)),\n variable: Number(variable.toFixed(2)),\n variable_afecta: factorSi ? 'Sí' : 'No',\n monto_afecto_original: Number(montoAfectoOriginal.toFixed(2)),\n monto_afecto_gtq: Number(montoAfectoOriginal.toFixed(2)),\n regla_aplicada: factorSi ? 'Sueldo + vacaciones + horas extras + variable' : 'Sueldo + vacaciones + horas extras'\n });\n }\n return records;\n}\nfunction samePerson(a, b) {\n if (a.igss_norm && b.igss_norm && a.igss_norm === b.igss_norm) return true;\n return normalizeName(a.nombre_nomina) === normalizeName(b.nombre_nomina);\n}\nfunction amountEquals(a, b) {\n return Math.abs(Number(a || 0) - Number(b || 0)) <= 0.05;\n}\nfunction deriveAdjustments(baseRecords, candidateRecords) {\n const ajustes = [];\n\n function pushAdjustment(c, amount, tipo, referencia, observacion) {\n if (Math.abs(amount) <= 0.05) return;\n ajustes.push({\n ...c,\n sourceType: 'ajuste',\n monto_ajuste_original: Number(amount.toFixed(2)),\n monto_ajuste_gtq: Number(amount.toFixed(2)),\n tipo_ajuste: tipo,\n referencia: referencia || '',\n observacion_ajuste: observacion || (tipo === 'Corrección de monto'\n ? 'Se incluyó solo la diferencia entre la fila corregida y la fila principal.'\n : 'Se incluyó como complemento de nómina.')\n });\n }\n\n const used = new Set();\n const groups = new Map();\n candidateRecords.forEach((c, idx) => {\n c.__idx = idx;\n const key = `${c.sheetTitle || ''}||${c.igss_norm || c.nombre_norm || normalizeName(c.nombre_nomina)}`;\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key).push(c);\n });\n\n // Caso abril 2026: la hoja Complemento trae valor anterior y valor corregido\n // de la variable de Oliver. No se suman ambas filas completas; se suma solo\n // la diferencia entre variable corregida y variable ya incluida en la nómina.\n for (const group of groups.values()) {\n if (!group.length) continue;\n const c0 = group[0];\n const isComplementSheet = /complement|complemento/i.test(removeAccents(c0.sheetTitle || '').toLowerCase());\n if (!isComplementSheet) continue;\n\n const possibleBase = baseRecords.filter(b => samePerson(b, c0));\n const baseAny = possibleBase[0];\n if (!baseAny) continue;\n\n const baseVariable = Number(baseAny.variable || 0);\n if (Math.abs(baseVariable) <= 0.05) continue;\n\n const variableOnly = group.filter(c =>\n Math.abs(Number(c.salario_periodo || 0)) <= 0.05 &&\n Math.abs(Number(c.vacaciones || 0)) <= 0.05 &&\n Math.abs(Number(c.horas_extras || 0)) <= 0.05 &&\n Math.abs(Number(c.variable || 0)) > 0.05 &&\n amountEquals(c.monto_afecto_original, c.variable)\n );\n\n const oldValue = variableOnly.find(c => amountEquals(c.monto_afecto_original, baseVariable));\n const corrected = variableOnly\n .filter(c => c.monto_afecto_original > baseVariable + 0.05)\n .sort((a,b) => Number(b.monto_afecto_original || 0) - Number(a.monto_afecto_original || 0))[0];\n\n if (oldValue && corrected) {\n for (const c of variableOnly) used.add(c.__idx);\n const diff = Number((corrected.monto_afecto_original - baseVariable).toFixed(2));\n pushAdjustment(\n corrected,\n diff,\n 'Corrección de variable',\n `Variable principal ${baseVariable}`,\n 'Se incluyó solo la diferencia entre la variable corregida y la variable ya incluida en la nómina principal.'\n );\n }\n }\n\n for (const c of candidateRecords) {\n if (used.has(c.__idx)) continue;\n const possibleBase = baseRecords.filter(b => samePerson(b, c));\n const baseMatch = possibleBase.find(b => amountEquals(b.monto_afecto_original, c.monto_afecto_original));\n const baseAny = possibleBase[0];\n\n if (baseMatch) {\n // Esta fila es una repetición de la fila principal, no se suma como ajuste.\n continue;\n }\n\n let adjustmentAmount = c.monto_afecto_original;\n let tipo = 'Complemento';\n let baseReference = '';\n\n const isComplementSheet = /complement|complemento/i.test(removeAccents(c.sheetTitle || '').toLowerCase());\n if (!isComplementSheet && baseAny && c.monto_afecto_original > baseAny.monto_afecto_original && possibleBase.some(b => amountEquals(b.monto_afecto_original, baseAny.monto_afecto_original))) {\n // Ejemplo: hoja Oliver trae una fila corregida completa. Se suma solo la diferencia contra la fila principal.\n adjustmentAmount = Number((c.monto_afecto_original - baseAny.monto_afecto_original).toFixed(2));\n tipo = 'Corrección de monto';\n baseReference = `Monto principal ${baseAny.monto_afecto_original}`;\n }\n\n pushAdjustment(c, Number(adjustmentAmount || 0), tipo, baseReference, tipo === 'Corrección de monto'\n ? 'Se incluyó solo la diferencia entre la fila corregida y la fila principal.'\n : 'Se incluyó como complemento de nómina.');\n }\n\n return ajustes;\n}\n\nconst mainRecords = [];\nconst candidates = [];\n\nfor (const vr of valueRanges) {\n const sheetTitle = sheetNameFromRange(vr.range);\n const values = vr.values || [];\n if (!values.length) continue;\n\n if (isMainSheet(sheetTitle)) {\n mainRecords.push(...parsePayrollRows(values, sheetTitle, 'principal'));\n } else {\n candidates.push(...parsePayrollRows(values, sheetTitle, 'ajuste_candidato'));\n }\n}\n\nif (!mainRecords.length) {\n throw new Error(`No pude leer registros válidos desde 1) Nomina General para ${qLabel}.`);\n}\n\nconst ajustes = deriveAdjustments(mainRecords, candidates);\n\nconst data = $getWorkflowStaticData('global');\ndata.gtSeguridadSocial = data.gtSeguridadSocial || {};\ndata.gtSeguridadSocial[init.runId] = data.gtSeguridadSocial[init.runId] || {};\ndata.gtSeguridadSocial[init.runId].nomina.push(...mainRecords);\ndata.gtSeguridadSocial[init.runId].ajustes = data.gtSeguridadSocial[init.runId].ajustes || [];\ndata.gtSeguridadSocial[init.runId].ajustes.push(...ajustes);\n\nreturn [{\n json: {\n runId: init.runId,\n quincena: qLabel,\n payrollRows: mainRecords.length,\n invalidIgssRows: mainRecords.filter(r => !r.igss_norm).length,\n adjustmentRows: ajustes.length,\n adjustmentSheets: [...new Set(ajustes.map(a => a.sheetTitle))]\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
4240,
|
|
128
|
|
],
|
|
"id": "05be903c-d13c-4388-a59b-4bb4e61d7f31",
|
|
"name": "Guardar Nómina 2Q en memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0];\nconst bin = init.binary || {};\nconst type = 'sistema';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction looksExcel(fileName, mime) { const lower = norm(fileName); const m = norm(mime); return lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.xlsm') || lower.endsWith('.csv') || m.includes('spreadsheet') || m.includes('excel') || m.includes('csv'); }\nfunction scoreCandidate(key, fileName) {\n const text = norm(`${key} ${fileName}`);\n let score = 0;\n if (type === 'q1') { if (text.includes('nomina')) score += 20; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score += 120; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'q2') { if (text.includes('nomina')) score += 20; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score += 120; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'sistema') { if (/sistema|propio|2\\.2|igss/.test(text)) score += 120; if (/consolidada|consolidado|planilla consolidada|nomina/.test(text)) score -= 80; }\n if (type === 'consolidado') { if (/consolidada|consolidado|planilla consolidada/.test(text)) score += 120; if (/sistema|propio|2\\.2|nomina/.test(text)) score -= 80; }\n return score;\n}\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '');\n if (!looksExcel(fileName, mime)) continue;\n candidates.push({ key, file, fileName, score: scoreCandidate(key, fileName) });\n}\ncandidates.sort((a,b) => b.score - a.score);\nconst chosen = candidates[0];\nif (!chosen || chosen.score <= 0) throw new Error(`No encontré el archivo requerido para ${type}. Revisa los campos del formulario y los nombres de archivo.`);\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.json.runId];\nif (slot) slot.files[type] = chosen.fileName;\nreturn [{ json: { runId: init.json.runId, fileType: type, fileName: chosen.fileName }, binary: { data: chosen.file } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
4816,
|
|
144
|
|
],
|
|
"id": "68a4c8c9-bbb2-4c6f-babe-e5eb4d4000eb",
|
|
"name": "Preparar Sistema Propio"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "update",
|
|
"fileId": {
|
|
"__rl": true,
|
|
"value": "1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4",
|
|
"mode": "list",
|
|
"cachedResultName": "Nomina Guatemala",
|
|
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4/edit"
|
|
},
|
|
"changeFileContent": true,
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.googleDrive",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
5024,
|
|
144
|
|
],
|
|
"id": "80de44b1-e5b1-4c5a-a7cf-91a61f398b1a",
|
|
"name": "Actualizar staging - Sistema Propio",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4' }}?fields=spreadsheetId,sheets(properties(title,sheetId,hidden))",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
5456,
|
|
144
|
|
],
|
|
"id": "4b11aaac-9dcf-48cc-8226-43f4832f2127",
|
|
"name": "Listar hojas - Sistema Propio",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst target = 'empleados';\nconst spreadsheetId = $json.spreadsheetId || $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]/g, ''); }\nconst wanted = { nomina: ['1nominageneral','nominageneral'], empleados: ['empleados'], planilla: ['planillamensual'] }[target];\nconst sheets = ($json.sheets || []).map(s => s.properties || {});\nlet match = null;\nfor (const w of wanted) { match = sheets.find(p => norm(p.title) === w || norm(p.title).includes(w)); if (match) break; }\nif (!match) throw new Error(`No encontré la hoja esperada para ${target}. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\nreturn [{ json: { runId: init.runId, spreadsheetId, sheetTitle: match.title, sheetId: match.sheetId, target } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
5680,
|
|
144
|
|
],
|
|
"id": "11fc59a9-9bf5-46ea-a82d-563f7ae6893a",
|
|
"name": "Resolver hoja EMPLEADOS"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + $json.spreadsheetId + '/values/' + encodeURIComponent(\"'\" + $json.sheetTitle.replace(/'/g, \"''\") + \"'!A:AZ\") + '?valueRenderOption=UNFORMATTED_VALUE&dateTimeRenderOption=SERIAL_NUMBER' }}",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
5904,
|
|
144
|
|
],
|
|
"id": "40ce68e1-04ce-43b6-975b-9b48c2734073",
|
|
"name": "Leer EMPLEADOS",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst values = $json.values || [];\nfunction clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction normalizeName(s) { return clean(s).normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toUpperCase().replace(/[^A-Z0-9 ]/g, ' ').replace(/\\s+/g, ' ').trim(); }\nfunction normalizeAf(v) { const raw = clean(v); if (!raw) return ''; let digits = ''; if (typeof v === 'number') digits = Math.trunc(v).toString(); else { const s = raw.replace(/,/g, '').trim(); if (/e\\+?/i.test(s)) { const n = Number(s); if (Number.isFinite(n)) digits = Math.trunc(n).toString(); } else digits = s.replace(/\\D/g, ''); } return digits && digits.length >= 8 && digits.length <= 13 && !/^0+$/.test(digits) ? digits : ''; }\nfunction toNumber(v) { const n = Number(String(v ?? '').replace(/Q|\\$|USD|GTQ|,/gi, '').trim()); return Number.isFinite(n) ? n : 0; }\nconst records = [];\nfor (let i = 1; i < values.length; i++) { const row = values[i] || []; const afiliacion = normalizeAf(row[2]); if (!afiliacion) continue; const nombre = [row[4], row[6], row[8], row[10], row[12]].map(clean).filter(Boolean).join(' '); records.push({ afiliacion, nombre_contabilidad: nombre, nombre_norm: normalizeName(nombre), sueldo_devengado: Number(toNumber(row[14]).toFixed(2)) }); }\nconst by = new Map(); for (const r of records) if (!by.has(r.afiliacion)) by.set(r.afiliacion, r);\nconst finalRecords = [...by.values()];\nif (!finalRecords.length) throw new Error('No pude leer afiliaciones desde EMPLEADOS columna C.');\nconst data = $getWorkflowStaticData('global'); data.gtSeguridadSocial = data.gtSeguridadSocial || {}; data.gtSeguridadSocial[init.runId] = data.gtSeguridadSocial[init.runId] || {}; data.gtSeguridadSocial[init.runId].empleados = finalRecords;\nreturn [{ json: { runId: init.runId, empleadosRows: finalRecords.length } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
6144,
|
|
144
|
|
],
|
|
"id": "c20c8ed1-102f-42ac-b368-7b8277c8e5a9",
|
|
"name": "Guardar EMPLEADOS en memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0];\nconst bin = init.binary || {};\nconst type = 'consolidado';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction looksExcel(fileName, mime) { const lower = norm(fileName); const m = norm(mime); return lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.xlsm') || lower.endsWith('.csv') || m.includes('spreadsheet') || m.includes('excel') || m.includes('csv'); }\nfunction scoreCandidate(key, fileName) {\n const text = norm(`${key} ${fileName}`);\n let score = 0;\n if (type === 'q1') { if (text.includes('nomina')) score += 20; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score += 120; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'q2') { if (text.includes('nomina')) score += 20; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score += 120; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'sistema') { if (/sistema|propio|2\\.2|igss/.test(text)) score += 120; if (/consolidada|consolidado|planilla consolidada|nomina/.test(text)) score -= 80; }\n if (type === 'consolidado') { if (/consolidada|consolidado|planilla consolidada/.test(text)) score += 120; if (/sistema|propio|2\\.2|nomina/.test(text)) score -= 80; }\n return score;\n}\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '');\n if (!looksExcel(fileName, mime)) continue;\n candidates.push({ key, file, fileName, score: scoreCandidate(key, fileName) });\n}\ncandidates.sort((a,b) => b.score - a.score);\nconst chosen = candidates[0];\nif (!chosen || chosen.score <= 0) throw new Error(`No encontré el archivo requerido para ${type}. Revisa los campos del formulario y los nombres de archivo.`);\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.json.runId];\nif (slot) slot.files[type] = chosen.fileName;\nreturn [{ json: { runId: init.json.runId, fileType: type, fileName: chosen.fileName }, binary: { data: chosen.file } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
6320,
|
|
144
|
|
],
|
|
"id": "b791dc1c-9487-42ab-a3ed-ebac85b4df46",
|
|
"name": "Preparar Planilla Consolidada"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "update",
|
|
"fileId": {
|
|
"__rl": true,
|
|
"value": "1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4",
|
|
"mode": "list",
|
|
"cachedResultName": "Nomina Guatemala",
|
|
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4/edit"
|
|
},
|
|
"changeFileContent": true,
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.googleDrive",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
6528,
|
|
144
|
|
],
|
|
"id": "b9e4bd53-f993-42db-8fbd-e130872113b7",
|
|
"name": "Actualizar staging - Consolidada",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4' }}?fields=spreadsheetId,sheets(properties(title,sheetId,hidden))",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
6880,
|
|
144
|
|
],
|
|
"id": "612e0c20-72a0-4b9d-9ee3-15f76a82c191",
|
|
"name": "Listar hojas - Consolidada",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst target = 'planilla';\nconst spreadsheetId = $json.spreadsheetId || $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]/g, ''); }\nconst wanted = { nomina: ['1nominageneral','nominageneral'], empleados: ['empleados'], planilla: ['planillamensual'] }[target];\nconst sheets = ($json.sheets || []).map(s => s.properties || {});\nlet match = null;\nfor (const w of wanted) { match = sheets.find(p => norm(p.title) === w || norm(p.title).includes(w)); if (match) break; }\nif (!match) throw new Error(`No encontré la hoja esperada para ${target}. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\nreturn [{ json: { runId: init.runId, spreadsheetId, sheetTitle: match.title, sheetId: match.sheetId, target } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
7072,
|
|
144
|
|
],
|
|
"id": "a7459273-64d3-4ca3-8966-e505f1f74bf2",
|
|
"name": "Resolver hoja PLANILLA MENSUAL"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + $json.spreadsheetId + '/values/' + encodeURIComponent(\"'\" + $json.sheetTitle.replace(/'/g, \"''\") + \"'!A:AZ\") + '?valueRenderOption=UNFORMATTED_VALUE&dateTimeRenderOption=SERIAL_NUMBER' }}",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
7248,
|
|
144
|
|
],
|
|
"id": "ce9dda4f-a310-4d15-aa02-832da8b04e65",
|
|
"name": "Leer PLANILLA MENSUAL",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst values = $json.values || [];\nfunction clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction normalizeName(s) { return clean(s).normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toUpperCase().replace(/[^A-Z0-9 ]/g, ' ').replace(/\\s+/g, ' ').trim(); }\nfunction toNumber(v) { if (v === null || v === undefined || v === '') return 0; if (typeof v === 'number') return Number.isFinite(v) ? v : 0; const n = Number(String(v).replace(/Q|\\$|USD|GTQ|,/gi, '').trim()); return Number.isFinite(n) ? n : 0; }\nfunction normalizeAf(v) { const raw = clean(v); if (!raw) return ''; let digits = ''; if (typeof v === 'number') digits = Math.trunc(v).toString(); else { const s = raw.replace(/,/g, '').trim(); if (/e\\+?/i.test(s)) { const n = Number(s); if (Number.isFinite(n)) digits = Math.trunc(n).toString(); } else digits = s.replace(/\\D/g, ''); } return digits && digits.length >= 8 && digits.length <= 13 && !/^0+$/.test(digits) ? digits : ''; }\nconst records = [];\nfor (let i = 0; i < values.length; i++) { const row = values[i] || []; const afiliacion = normalizeAf(row[1]); if (!afiliacion) continue; const nombre = clean(row[2]); if (!nombre || /^total/i.test(nombre)) continue; records.push({ afiliacion, nombre_planilla: nombre, nombre_norm: normalizeName(nombre), proyecto_planilla: clean(row[4]), monto_planilla_mensual: Number(toNumber(row[25]).toFixed(2)), liquido_planilla: Number(toNumber(row[23]).toFixed(2)) }); }\nconst by = new Map();\nfor (const r of records) { if (!by.has(r.afiliacion)) by.set(r.afiliacion, { ...r }); else { const ex = by.get(r.afiliacion); ex.monto_planilla_mensual = Number((ex.monto_planilla_mensual + r.monto_planilla_mensual).toFixed(2)); ex.liquido_planilla = Number((ex.liquido_planilla + r.liquido_planilla).toFixed(2)); } }\nconst finalRecords = [...by.values()];\nif (!finalRecords.length) throw new Error('No pude leer afiliaciones y monto desde PLANILLA MENSUAL.');\nconst data = $getWorkflowStaticData('global'); data.gtSeguridadSocial = data.gtSeguridadSocial || {}; data.gtSeguridadSocial[init.runId] = data.gtSeguridadSocial[init.runId] || {}; data.gtSeguridadSocial[init.runId].planillaMensual = finalRecords;\nreturn [{ json: { runId: init.runId, planillaRows: finalRecords.length } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
7424,
|
|
144
|
|
],
|
|
"id": "eb1a7fda-e46a-47c1-aaf6-c8a726353511",
|
|
"name": "Guardar PLANILLA MENSUAL en memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "function clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction money(n) { return Number((Number(n) || 0).toFixed(2)); }\nfunction blankCell(v) { return v === '' ? null : v; }\nfunction sanitizeRows(rows) { return (rows || []).map(row => (row || []).map(blankCell)); }\nfunction removeAccents(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, ''); }\nfunction normalizeName(s) {\n return clean(s)\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase()\n .replace(/[^A-Z0-9 ]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\nfunction tokens(name) {\n const stop = new Set(['DE','DEL','LA','LAS','LOS','Y','DA','DAS','DO','VDA']);\n return normalizeName(name).split(' ').filter(t => t.length > 1 && !stop.has(t));\n}\nfunction nameScore(a, b) {\n const A = tokens(a), B = tokens(b);\n if (!A.length || !B.length) return 0;\n const setB = new Set(B);\n const inter = A.filter(x => setB.has(x)).length;\n if (inter < 2) return 0;\n return inter / Math.min(A.length, B.length);\n}\nfunction bestByName(name, list, field) {\n const exact = list.find(r => normalizeName(r[field]) === normalizeName(name));\n if (exact) return { record: exact, score: 1, method: 'nombre exacto' };\n let best = { record: null, score: 0, method: '' };\n for (const r of list) {\n const sc = nameScore(name, r[field] || '');\n if (sc > best.score) best = { record: r, score: sc, method: 'nombre parecido' };\n }\n return best.score >= 0.70 ? best : { record: null, score: 0, method: '' };\n}\nfunction makeMap(records, key) {\n const m = new Map();\n for (const r of records || []) if (r[key]) m.set(String(r[key]), r);\n return m;\n}\nfunction uniqueJoin(arr) {\n return [...new Set((arr || []).filter(Boolean).map(clean))].join(' | ');\n}\nfunction fmt(n) {\n return Number(n || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n}\n\nconst init = $items('Inicializar memoria')[0].json;\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.runId];\nif (!slot) throw new Error('No encontré la memoria temporal de esta ejecución.');\n\nconst payroll = slot.nomina || [];\nconst ajustesRaw = slot.ajustes || [];\nconst empleados = slot.empleados || [];\nconst planilla = slot.planillaMensual || [];\n\nif (!payroll.length || !empleados.length || !planilla.length) {\n throw new Error(`Faltan datos: nómina=${payroll.length}, empleados=${empleados.length}, planilla=${planilla.length}`);\n}\n\nconst empByAf = makeMap(empleados, 'afiliacion');\nconst planByAf = makeMap(planilla, 'afiliacion');\n\nconst resolvedRows = [];\nconst sinIgssRows = [];\n\nfor (const r of payroll) {\n let resolvedAf = r.igss_norm || '';\n let resolveMethod = resolvedAf ? 'Número de IGSS en nómina' : '';\n let corrected = false;\n\n if (!resolvedAf || (!empByAf.has(resolvedAf) && !planByAf.has(resolvedAf))) {\n const bestEmp = bestByName(r.nombre_nomina, empleados, 'nombre_contabilidad');\n if (bestEmp.record) {\n corrected = !!resolvedAf && resolvedAf !== bestEmp.record.afiliacion;\n resolvedAf = bestEmp.record.afiliacion;\n resolveMethod = `${bestEmp.method} en EMPLEADOS`;\n } else {\n const bestPlan = bestByName(r.nombre_nomina, planilla, 'nombre_planilla');\n if (bestPlan.record) {\n corrected = !!resolvedAf && resolvedAf !== bestPlan.record.afiliacion;\n resolvedAf = bestPlan.record.afiliacion;\n resolveMethod = `${bestPlan.method} en PLANILLA MENSUAL`;\n }\n }\n }\n\n if (!r.igss_norm || corrected) {\n sinIgssRows.push({\n ...r,\n resolvedAf,\n resolveMethod,\n corrected,\n accion: corrected ? 'Corregir IGSS en nómina' : 'Completar IGSS en nómina'\n });\n }\n\n resolvedRows.push({ ...r, resolvedAf, resolveMethod, corrected });\n}\n\n// Tipo de cambio para nóminas en dólar, inferido con casos donde el monto coincide contra PLANILLA MENSUAL.\nconst ratios = [];\nfor (const r of resolvedRows) {\n if (!/dolar|dólar|usd/i.test(r.moneda || '') || !r.resolvedAf) continue;\n const pl = planByAf.get(r.resolvedAf);\n if (!pl || !r.monto_afecto_original) continue;\n const ratio = pl.monto_planilla_mensual / r.monto_afecto_original;\n if (ratio >= 5 && ratio <= 10) ratios.push(ratio);\n}\nratios.sort((a,b)=>a-b);\nconst usdRate = ratios.length ? money(ratios[Math.floor(ratios.length/2)]) : 1;\n\nfor (const r of resolvedRows) {\n r.tipo_cambio_usd = /dolar|dólar|usd/i.test(r.moneda || '') ? usdRate : 1;\n r.monto_afecto_gtq = money(r.monto_afecto_original * r.tipo_cambio_usd);\n}\n\nconst groups = new Map();\nconst unresolvedRows = [];\n\nfor (const r of resolvedRows) {\n if (!r.resolvedAf) {\n unresolvedRows.push(r);\n continue;\n }\n\n if (!groups.has(r.resolvedAf)) {\n groups.set(r.resolvedAf, { afiliacion:r.resolvedAf, records:[], ajustes:[], monto:0, montoPrincipal:0, montoAjustes:0 });\n }\n const g = groups.get(r.resolvedAf);\n g.records.push(r);\n g.montoPrincipal = money(g.montoPrincipal + r.monto_afecto_gtq);\n g.monto = money(g.monto + r.monto_afecto_gtq);\n}\n\n// Resolver e incluir ajustes/complementos sin contaminar el cruce de afiliaciones.\nconst ajustesIncluidos = [];\nconst ajustesNoResueltos = [];\n\nfor (const a0 of ajustesRaw) {\n let resolvedAf = a0.igss_norm || '';\n let resolveMethod = resolvedAf ? 'Número de IGSS en ajuste' : '';\n\n if (!resolvedAf || (!empByAf.has(resolvedAf) && !planByAf.has(resolvedAf))) {\n const bestEmp = bestByName(a0.nombre_nomina, empleados, 'nombre_contabilidad');\n if (bestEmp.record) {\n resolvedAf = bestEmp.record.afiliacion;\n resolveMethod = `${bestEmp.method} en EMPLEADOS`;\n } else {\n const bestPlan = bestByName(a0.nombre_nomina, planilla, 'nombre_planilla');\n if (bestPlan.record) {\n resolvedAf = bestPlan.record.afiliacion;\n resolveMethod = `${bestPlan.method} en PLANILLA MENSUAL`;\n }\n }\n }\n\n const rate = /dolar|dólar|usd/i.test(a0.moneda || '') ? usdRate : 1;\n const montoGtq = money((a0.monto_ajuste_original || 0) * rate);\n const ajuste = { ...a0, resolvedAf, resolveMethod, monto_ajuste_gtq: montoGtq, tipo_cambio_usd: rate };\n\n if (!resolvedAf) {\n ajustesNoResueltos.push(ajuste);\n continue;\n }\n\n if (!groups.has(resolvedAf)) {\n groups.set(resolvedAf, { afiliacion:resolvedAf, records:[], ajustes:[], monto:0, montoPrincipal:0, montoAjustes:0 });\n }\n const g = groups.get(resolvedAf);\n g.ajustes.push(ajuste);\n g.montoAjustes = money(g.montoAjustes + montoGtq);\n g.monto = money(g.monto + montoGtq);\n ajustesIncluidos.push(ajuste);\n}\n\nconst matchedEmpAf = new Set([...groups.keys()].filter(af => empByAf.has(af)));\nconst matchedPlanAf = new Set([...groups.keys()].filter(af => planByAf.has(af)));\n\nconst soloContabilidad = empleados.filter(e => !matchedEmpAf.has(e.afiliacion));\nconst soloNomina = [...groups.values()].filter(g => !empByAf.has(g.afiliacion));\nconst afiliacionDiferencias = soloContabilidad.length + soloNomina.length;\n\n// Pestaña 1: Cruce Afiliaciones\nconst afiliacionRows = [];\nafiliacionRows.push(['Cruce de afiliaciones - Guatemala']);\nafiliacionRows.push([`Período: ${init.periodoLabel}`]);\nafiliacionRows.push(['Resultado', afiliacionDiferencias ? 'REVISAR DIFERENCIAS' : 'SIN DIFERENCIAS']);\nafiliacionRows.push(['']);\nafiliacionRows.push(['Resumen', 'Cantidad']);\nafiliacionRows.push(['Registros de nómina revisados (Q1 + Q2)', payroll.length]);\nafiliacionRows.push(['Empleados únicos en nómina', groups.size]);\nafiliacionRows.push(['Empleados en contabilidad', empleados.length]);\nafiliacionRows.push(['Diferencias encontradas', afiliacionDiferencias]);\nafiliacionRows.push(['']);\nafiliacionRows.push(['Lectura rápida']);\nafiliacionRows.push([afiliacionDiferencias ? '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 contabilidad.']);\nafiliacionRows.push(['']);\nafiliacionRows.push(['No se muestran los empleados que coinciden correctamente.']);\nafiliacionRows.push(['']);\nafiliacionRows.push(['Empleados en contabilidad que NO aparecen en nómina']);\nafiliacionRows.push(['Afiliación', 'Nombre completo', 'Qué significa']);\nif (soloContabilidad.length) {\n for (const e of soloContabilidad) afiliacionRows.push([e.afiliacion, e.nombre_contabilidad, 'Está en contabilidad, pero no aparece en las nóminas Q1/Q2.']);\n} else {\n afiliacionRows.push(['Sin diferencias', '', '']);\n}\nafiliacionRows.push(['']);\nafiliacionRows.push(['Empleados en nómina que NO aparecen en contabilidad']);\nafiliacionRows.push(['Afiliación / IGSS', 'Nombre completo', 'Quincena(s)', 'Proyecto(s)', 'Qué significa']);\nif (soloNomina.length) {\n for (const g of soloNomina) afiliacionRows.push([g.afiliacion, uniqueJoin(g.records.map(r=>r.nombre_nomina)), uniqueJoin(g.records.map(r=>r.q)), uniqueJoin(g.records.map(r=>r.proyecto_nomina)), 'Está en nómina, pero no existe en EMPLEADOS de contabilidad.']);\n} else {\n afiliacionRows.push(['Sin diferencias', '', '', '', '']);\n}\n\n// Pestaña 2: Sin IGSS en nómina\nconst sinIgssSheetRows = [];\nsinIgssSheetRows.push(['Sin IGSS en nómina']);\nsinIgssSheetRows.push([`Período: ${init.periodoLabel}`]);\nsinIgssSheetRows.push(['Resultado', sinIgssRows.length ? 'REGISTROS PARA COMPLETAR / CORREGIR' : 'SIN REGISTROS']);\nsinIgssSheetRows.push(['']);\nsinIgssSheetRows.push(['Resumen', 'Cantidad']);\nsinIgssSheetRows.push(['Registros con IGSS pendiente/vacío o a corregir', sinIgssRows.length]);\nsinIgssSheetRows.push(['']);\nsinIgssSheetRows.push(['Lectura rápida']);\nsinIgssSheetRows.push([sinIgssRows.length ? 'Estos empleados existen en contabilidad, pero la columna IGSS de la nómina debe completarse o corregirse.' : 'No hay registros de nómina con IGSS pendiente/vacío o a corregir.']);\nsinIgssSheetRows.push(['']);\nsinIgssSheetRows.push(['Quincena', 'Fila nómina', 'Nombre completo', 'Proyecto', 'IGSS en nómina', 'Afiliación correcta', 'Acción sugerida']);\nif (sinIgssRows.length) {\n for (const r of sinIgssRows.sort((a,b) => String(a.q).localeCompare(String(b.q)) || Number(a.rowNumber||0)-Number(b.rowNumber||0))) {\n sinIgssSheetRows.push([\n r.q,\n r.rowNumber,\n r.nombre_nomina,\n r.proyecto_nomina,\n r.igss_original || '',\n r.resolvedAf || '',\n r.accion\n ]);\n }\n} else {\n sinIgssSheetRows.push(['Sin registros', '', '', '', '', '', '']);\n}\n\n// Cruce de montos después de ajustes\nconst montoFindings = [];\nconst noComparados = [];\nlet montoOk = 0;\n\nfor (const [af,g] of groups) {\n const pl = planByAf.get(af);\n if (!pl) {\n noComparados.push({estado:'Solo en nóminas',af,g,pl:null,obs:'No existe en PLANILLA MENSUAL.'});\n continue;\n }\n const diff = money(g.monto - pl.monto_planilla_mensual);\n if (Math.abs(diff) <= 0.05) {\n montoOk++;\n } else {\n montoFindings.push({af,g,pl,diff});\n }\n}\nfor (const pl of planilla) {\n if (!matchedPlanAf.has(pl.afiliacion)) {\n noComparados.push({estado:'Solo en PLANILLA MENSUAL',af:pl.afiliacion,g:null,pl,obs:'Existe en PLANILLA MENSUAL, pero no se relacionó con las nóminas Q1/Q2.'});\n }\n}\nfor (const r of unresolvedRows) {\n noComparados.push({estado:'IGSS sin resolver',af:'',g:{records:[r],monto:r.monto_afecto_gtq},pl:null,obs:'No se pudo resolver afiliación por número ni por nombre.'});\n}\nfor (const a of ajustesNoResueltos) {\n noComparados.push({estado:'Ajuste sin resolver',af:'',g:{records:[a],monto:a.monto_ajuste_gtq},pl:null,obs:'No se pudo relacionar el ajuste/complemento por IGSS ni por nombre.'});\n}\nmontoFindings.sort((a,b)=>Math.abs(b.diff)-Math.abs(a.diff));\nconst montoResultado = (montoFindings.length || noComparados.length) ? 'REVISAR DIFERENCIAS' : 'SIN DIFERENCIAS';\n\n// Pestaña 3: Cruce Montos\nconst montosRows = [];\nmontosRows.push(['Cruce de montos afectos - Guatemala']);\nmontosRows.push([`Período: ${init.periodoLabel}`]);\nmontosRows.push(['Resultado', montoResultado]);\nmontosRows.push([`Tipo de cambio USD usado: ${usdRate === 1 ? 'No aplicado' : usdRate}`]);\nmontosRows.push(['']);\nmontosRows.push(['Resumen', 'Cantidad']);\nmontosRows.push(['Afiliaciones comparadas con monto correcto', montoOk]);\nmontosRows.push(['Diferencias reales de monto', montoFindings.length]);\nmontosRows.push(['Casos no comparados', noComparados.length]);\nmontosRows.push(['Ajustes/complementos incluidos', ajustesIncluidos.length]);\nmontosRows.push(['']);\nmontosRows.push(['Lectura rápida']);\nmontosRows.push([montoResultado === 'SIN DIFERENCIAS' ? 'No hay diferencias de monto después de sumar nómina Q1, nómina Q2 y ajustes/complementos.' : 'Hay diferencias o casos no comparados. Revisar las secciones de abajo.']);\nmontosRows.push(['']);\nmontosRows.push(['No se muestran los empleados que cuadran correctamente.']);\nmontosRows.push(['']);\nmontosRows.push(['Diferencias reales de monto']);\nmontosRows.push(['Afiliación', 'Nombre completo', 'Proyecto', 'Monto nómina Q1+Q2 + ajustes', 'Monto planilla mensual', 'Diferencia', 'Detalle', 'Qué revisar']);\nif (montoFindings.length) {\n for (const f of montoFindings) {\n const details = [\n ...f.g.records.map(r => `${r.q}: ${fmt(r.monto_afecto_gtq)}${r.tipo_cambio_usd !== 1 ? ' GTQ (USD convertido)' : ''}`),\n ...f.g.ajustes.map(a => `Ajuste ${a.q}/${a.sheetTitle}: ${fmt(a.monto_ajuste_gtq)}`)\n ].join(' | ');\n montosRows.push([\n f.af,\n f.pl?.nombre_planilla || uniqueJoin(f.g.records.map(r=>r.nombre_nomina)),\n f.pl?.proyecto_planilla || uniqueJoin(f.g.records.map(r=>r.proyecto_nomina)),\n f.g.monto,\n f.pl.monto_planilla_mensual,\n f.diff,\n details,\n 'Revisar diferencia entre nómina combinada y PLANILLA MENSUAL.'\n ]);\n }\n} else {\n montosRows.push(['Sin diferencias', '', '', '', '', '', '', '']);\n}\nmontosRows.push(['']);\nmontosRows.push(['Casos no comparados']);\nmontosRows.push(['Estado','Afiliación','Nombre completo','Monto nómina / ajuste','Monto planilla mensual','Qué significa']);\nif (noComparados.length) {\n for (const f of noComparados) {\n montosRows.push([\n f.estado,\n f.af,\n f.pl?.nombre_planilla || uniqueJoin((f.g?.records || []).map(r=>r.nombre_nomina)),\n f.g?.monto || '',\n f.pl?.monto_planilla_mensual || '',\n f.obs\n ]);\n }\n} else {\n montosRows.push(['Sin casos', '', '', '', '', '']);\n}\n\n// Pestaña 4: Ajustes incluidos\nconst ajustesRows = [];\najustesRows.push(['Ajustes incluidos']);\najustesRows.push([`Período: ${init.periodoLabel}`]);\najustesRows.push(['Resultado', ajustesIncluidos.length ? `${ajustesIncluidos.length} ajuste(s) incluidos en el cruce de montos` : 'Sin ajustes incluidos']);\najustesRows.push(['']);\najustesRows.push(['Lectura rápida']);\najustesRows.push([ajustesIncluidos.length ? 'Estos ajustes/complementos se sumaron para comparar correctamente contra PLANILLA MENSUAL.' : 'No se encontraron ajustes/complementos para sumar al cruce de montos.']);\najustesRows.push(['']);\najustesRows.push(['Quincena', 'Hoja origen', 'Fila', 'Afiliación', 'Nombre completo', 'Proyecto', 'Monto ajuste', 'Tipo', 'Observación']);\nif (ajustesIncluidos.length) {\n for (const a of ajustesIncluidos.sort((a,b) => String(a.q).localeCompare(String(b.q)) || String(a.sheetTitle).localeCompare(String(b.sheetTitle)) || Number(a.rowNumber||0)-Number(b.rowNumber||0))) {\n ajustesRows.push([\n a.q,\n a.sheetTitle,\n a.rowNumber,\n a.resolvedAf,\n a.nombre_nomina,\n a.proyecto_nomina,\n a.monto_ajuste_gtq,\n a.tipo_ajuste,\n a.observacion_ajuste\n ]);\n }\n} else {\n ajustesRows.push(['Sin ajustes', '', '', '', '', '', '', '', '']);\n}\n\nconst q1Count = payroll.filter(r=>r.q==='1Q').length;\nconst q2Count = payroll.filter(r=>r.q==='2Q').length;\n\nconst summary = {\n periodo:init.periodoLabel,\n q1Rows:q1Count,\n q2Rows:q2Count,\n empleados:empleados.length,\n planilla:planilla.length,\n empleadosUnicosNomina:groups.size,\n afiliacionSoloContabilidad:soloContabilidad.length,\n afiliacionSoloNomina:soloNomina.length,\n afiliacionDiferencias,\n sinIgss:sinIgssRows.length,\n montoOk,\n montoFindings:montoFindings.length,\n noComparados:noComparados.length,\n ajustesIncluidos:ajustesIncluidos.length,\n usdRate,\n files:slot.files || {}\n};\n\ndelete data.gtSeguridadSocial[init.runId];\n\nreturn [{\n json: {\n runId:init.runId,\n reportTitle:init.reportTitle,\n emailTo:init.emailTo || 'iaracena@gomezleemarketing.com',\n afiliacionRows: sanitizeRows(afiliacionRows),\n sinIgssRows: sanitizeRows(sinIgssSheetRows),\n montosRows: sanitizeRows(montosRows),\n ajustesRows: sanitizeRows(ajustesRows),\n summary,\n rowCounts:{\n afiliacion:afiliacionRows.length,\n sinIgss:sinIgssSheetRows.length,\n montos:montosRows.length,\n ajustes:ajustesRows.length\n }\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
7888,
|
|
144
|
|
],
|
|
"id": "d1e21e26-ece2-4322-a1ad-fcedb6a934ab",
|
|
"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 Afiliaciones' } }, { properties: { title: 'Sin IGSS en nómina' } }, { properties: { title: 'Cruce Montos' } }, { properties: { title: 'Ajustes incluidos' } } ] }) }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
8128,
|
|
144
|
|
],
|
|
"id": "a5f21136-97b7-4aff-ae35-3c71c7efb03e",
|
|
"name": "Crear Google Sheet 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 }}/values:batchUpdate",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ JSON.stringify({ data: [ { range: 'Cruce Afiliaciones!A1', majorDimension: 'ROWS', values: $('Construir reporte producción').item.json.afiliacionRows }, { range: 'Sin IGSS en nómina!A1', majorDimension: 'ROWS', values: $('Construir reporte producción').item.json.sinIgssRows }, { range: 'Cruce Montos!A1', majorDimension: 'ROWS', values: $('Construir reporte producción').item.json.montosRows }, { range: 'Ajustes incluidos!A1', majorDimension: 'ROWS', values: $('Construir reporte producción').item.json.ajustesRows } ], valueInputOption: 'RAW' }) }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
8368,
|
|
144
|
|
],
|
|
"id": "5c5afcdb-1228-4817-9cb9-b203c2ed6835",
|
|
"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": "={{ JSON.stringify({ requests: [\n { autoResizeDimensions: { dimensions: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[0].properties.sheetId, dimension: 'COLUMNS', startIndex: 0, endIndex: 12 } } },\n { autoResizeDimensions: { dimensions: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[1].properties.sheetId, dimension: 'COLUMNS', startIndex: 0, endIndex: 12 } } },\n { autoResizeDimensions: { dimensions: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[2].properties.sheetId, dimension: 'COLUMNS', startIndex: 0, endIndex: 12 } } },\n { autoResizeDimensions: { dimensions: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[3].properties.sheetId, dimension: 'COLUMNS', startIndex: 0, endIndex: 12 } } },\n { updateSheetProperties: { properties: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[0].properties.sheetId, gridProperties: { frozenRowCount: 1 } }, fields: 'gridProperties.frozenRowCount' } },\n { updateSheetProperties: { properties: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[1].properties.sheetId, gridProperties: { frozenRowCount: 1 } }, fields: 'gridProperties.frozenRowCount' } },\n { updateSheetProperties: { properties: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[2].properties.sheetId, gridProperties: { frozenRowCount: 1 } }, fields: 'gridProperties.frozenRowCount' } },\n { updateSheetProperties: { properties: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[3].properties.sheetId, gridProperties: { frozenRowCount: 1 } }, fields: 'gridProperties.frozenRowCount' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[0].properties.sheetId, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 0.91, green: 0.96, blue: 0.90 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[1].properties.sheetId, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 1.00, green: 0.96, blue: 0.80 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[2].properties.sheetId, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 0.91, green: 0.96, blue: 0.90 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[3].properties.sheetId, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 0.93, green: 0.94, blue: 0.96 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[0].properties.sheetId }, cell: { userEnteredFormat: { wrapStrategy: 'WRAP' } }, fields: 'userEnteredFormat.wrapStrategy' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[1].properties.sheetId }, cell: { userEnteredFormat: { wrapStrategy: 'WRAP' } }, fields: 'userEnteredFormat.wrapStrategy' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[2].properties.sheetId }, cell: { userEnteredFormat: { wrapStrategy: 'WRAP' } }, fields: 'userEnteredFormat.wrapStrategy' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[3].properties.sheetId }, cell: { userEnteredFormat: { wrapStrategy: 'WRAP' } }, fields: 'userEnteredFormat.wrapStrategy' } }\n] }) }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
8608,
|
|
144
|
|
],
|
|
"id": "c5cc0f8d-e1dd-4f79-9c6d-4724e46e1474",
|
|
"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": [
|
|
8848,
|
|
144
|
|
],
|
|
"id": "187f0813-12b8-4a0c-95af-05357f682885",
|
|
"name": "Compartir con Isaac",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
},
|
|
"onError": "continueRegularOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"sendTo": "iaracena@gomezleemarketing.com, ymadera@gomezleemarketing.com",
|
|
"subject": "={{ 'Cruce Seguridad Social Guatemala - ' + ($('Construir reporte producción').item.json.summary.periodo || '') }}",
|
|
"message": "={{ (() => {\nconst s = $('Construir reporte producción').item.json.summary || {};\nconst url = $('Crear Google Sheet Reporte').item.json.spreadsheetUrl || '#';\nconst afiliacionDiff = Number(s.afiliacionDiferencias || 0);\nconst montoDiff = Number(s.montoFindings || 0);\nconst noComparados = Number(s.noComparados || 0);\nconst sinIgss = Number(s.sinIgss || 0);\nconst ajustes = Number(s.ajustesIncluidos || 0);\nconst hardDiff = afiliacionDiff + montoDiff + noComparados;\nconst status = hardDiff ? 'REQUIERE REVISIÓN' : (sinIgss ? 'COMPLETADO · DATOS A CORREGIR' : 'COMPLETADO · SIN DIFERENCIAS');\nconst statusBg = hardDiff ? '#fff7ed' : (sinIgss ? '#fffbeb' : '#f0fdf4');\nconst statusBorder = hardDiff ? '#fed7aa' : (sinIgss ? '#fde68a' : '#bbf7d0');\nconst statusColor = hardDiff ? '#9a3412' : (sinIgss ? '#92400e' : '#166534');\nreturn `<div style=\"font-family:Arial,Helvetica,sans-serif;background:#f6f8fb;padding:0;margin:0\">\n <div style=\"max-width:720px;margin:0 auto;background:#ffffff;border-radius:14px;overflow:hidden;border:1px solid #e3e8ef\">\n <div style=\"border-top:5px solid #58bd45;padding:30px 34px 20px\">\n <img src=\"data:image/png;base64,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\" alt=\"GomezLee Marketing\" style=\"width:210px;max-width:70%;height:auto;display:block;margin-bottom:28px\" />\n <div style=\"margin-top:8px;color:#58bd45;font-weight:700;font-size:12px;letter-spacing:2px\">SEGURIDAD SOCIAL · GUATEMALA</div>\n <h1 style=\"margin:8px 0 8px;color:#4f7185;font-size:26px\">Cruce mensual completado</h1>\n <p style=\"margin:0;color:#5f7f92\">${s.periodo || ''} · Nóminas Q1 + Q2</p>\n </div>\n <div style=\"padding:26px 34px\">\n <div style=\"padding:16px;border-radius:12px;background:${statusBg};border:1px solid ${statusBorder};color:${statusColor};font-weight:700;margin-bottom:18px\">${status}</div>\n <div style=\"display:flex;gap:14px;flex-wrap:wrap\">\n <div style=\"flex:1;min-width:150px;border:1px solid #d8e0e7;border-top:3px solid #4f7185;border-radius:12px;padding:16px\">\n <div style=\"font-size:11px;color:#6d8da1;font-weight:700;letter-spacing:1px\">NÓMINA Q1</div>\n <div style=\"font-size:28px;color:#4f7185;font-weight:700\">${s.q1Rows||0}</div>\n </div>\n <div style=\"flex:1;min-width:150px;border:1px solid #d8e0e7;border-top:3px solid #4f7185;border-radius:12px;padding:16px\">\n <div style=\"font-size:11px;color:#6d8da1;font-weight:700;letter-spacing:1px\">NÓMINA Q2</div>\n <div style=\"font-size:28px;color:#4f7185;font-weight:700\">${s.q2Rows||0}</div>\n </div>\n <div style=\"flex:1;min-width:150px;border:1px solid #d8e0e7;border-top:3px solid ${hardDiff ? '#ff8a00' : '#58bd45'};border-radius:12px;padding:16px\">\n <div style=\"font-size:11px;color:#6d8da1;font-weight:700;letter-spacing:1px\">HALLAZGOS A REVISAR</div>\n <div style=\"font-size:28px;color:#4f7185;font-weight:700\">${hardDiff}</div>\n </div>\n </div>\n <div style=\"display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:22px\">\n <div style=\"border:1px solid #d8e0e7;border-radius:12px;padding:16px\">\n <h3 style=\"margin:0 0 12px;color:#4f7185;font-size:16px\">Afiliaciones</h3>\n <p style=\"margin:8px 0;color:#333\">Resultado <strong style=\"float:right;color:#4f7185\">${afiliacionDiff ? 'Revisar' : 'Sin diferencias'}</strong></p>\n <p style=\"margin:8px 0;color:#333\">Empleados únicos nómina <strong style=\"float:right;color:#4f7185\">${s.empleadosUnicosNomina||0}</strong></p>\n <p style=\"margin:8px 0;color:#333\">Sin IGSS en nómina <strong style=\"float:right;color:#4f7185\">${sinIgss}</strong></p>\n </div>\n <div style=\"border:1px solid #d8e0e7;border-radius:12px;padding:16px\">\n <h3 style=\"margin:0 0 12px;color:#4f7185;font-size:16px\">Montos afectos</h3>\n <p style=\"margin:8px 0;color:#333\">Diferencias reales <strong style=\"float:right;color:#4f7185\">${montoDiff}</strong></p>\n <p style=\"margin:8px 0;color:#333\">Casos no comparados <strong style=\"float:right;color:#4f7185\">${noComparados}</strong></p>\n <p style=\"margin:8px 0;color:#333\">Ajustes incluidos <strong style=\"float:right;color:#4f7185\">${ajustes}</strong></p>\n </div>\n </div>\n <div style=\"margin-top:24px\">\n <a href=\"${url}\" style=\"display:inline-block;background:#58bd45;color:white;text-decoration:none;border-radius:24px;padding:13px 22px;font-weight:700\">Abrir reporte en Google Sheets</a>\n </div>\n <div style=\"margin-top:22px;color:#6d8da1;font-size:12px;line-height:1.5\">\n <strong>Archivos procesados</strong><br>\n Nómina Q1: ${s.files?.q1||''}<br>\n Nómina Q2: ${s.files?.q2||''}<br>\n Sistema Propio IGSS: ${s.files?.sistema||''}<br>\n Planilla Consolidada: ${s.files?.consolidado||''}\n </div>\n </div>\n </div>\n</div>`;\n})() }}",
|
|
"options": {
|
|
"appendAttribution": false
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.gmail",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
9088,
|
|
144
|
|
],
|
|
"id": "b8470f8f-893b-485c-9924-bc41bb2e2673",
|
|
"name": "Enviar correo",
|
|
"webhookId": "a63f6b49-1bde-44a6-b1a6-6fc2af3f92b5",
|
|
"credentials": {
|
|
"gmailOAuth2": {
|
|
"id": "UDcO1FLJqA453V2D",
|
|
"name": "Gmail account 3"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## Inicio de Flujo\nRecibir adjuntos a través de formulario y preparación de nóminas",
|
|
"height": 320,
|
|
"width": 4512,
|
|
"color": 5
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
0,
|
|
0
|
|
],
|
|
"id": "dc202408-379b-4732-926d-1e2024b34ed6",
|
|
"name": "Sticky Note"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## Preparar archivos de contadores\n\nPreparar archivos Sistema Propio y Planilla Consolidada",
|
|
"height": 320,
|
|
"width": 2880
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
4736,
|
|
16
|
|
],
|
|
"id": "582d07c3-5cbd-427d-8b70-7daefc5847a8",
|
|
"name": "Sticky Note1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## Preparar reporte y enviar correo\n\nConstruir reporte y prepararlo en el formato adecuado. Preparar correo para envío.",
|
|
"height": 288,
|
|
"width": 1488,
|
|
"color": 4
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
7824,
|
|
32
|
|
],
|
|
"id": "ed673fb9-b847-4e54-88df-000a65506a07",
|
|
"name": "Sticky Note2"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"amount": 8
|
|
},
|
|
"type": "n8n-nodes-base.wait",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
1280,
|
|
128
|
|
],
|
|
"id": "455d6a6c-0687-4d16-9113-362b2d6de105",
|
|
"name": "Esperar conversión - Nómina 1Q",
|
|
"webhookId": "wait-esperar-conversi-n-n-mina-1q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"amount": 8
|
|
},
|
|
"type": "n8n-nodes-base.wait",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
3024,
|
|
128
|
|
],
|
|
"id": "628c2647-736e-40b7-ac16-70c27f588452",
|
|
"name": "Esperar conversión - Nómina 2Q",
|
|
"webhookId": "wait-esperar-conversi-n-n-mina-2q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"amount": 8
|
|
},
|
|
"type": "n8n-nodes-base.wait",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
5232,
|
|
144
|
|
],
|
|
"id": "d6491600-f373-4b35-9d53-d91817dbfe03",
|
|
"name": "Esperar conversión - Sistema Propio",
|
|
"webhookId": "wait-esperar-conversi-n-sistema-propio"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"amount": 8
|
|
},
|
|
"type": "n8n-nodes-base.wait",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
6720,
|
|
144
|
|
],
|
|
"id": "eabb4451-f993-4410-8e36-bb9bb547e89e",
|
|
"name": "Esperar conversión - Consolidada",
|
|
"webhookId": "wait-esperar-conversi-n-consolidada"
|
|
}
|
|
],
|
|
"connections": {
|
|
"On form submission": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Inicializar memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Inicializar memoria": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Actualizar staging - Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Actualizar staging - Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Esperar conversión - Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Listar hojas - Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Resolver hoja Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Resolver hoja Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Leer Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Leer Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Guardar Nómina 1Q en memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Guardar Nómina 1Q en memoria": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Actualizar staging - Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Actualizar staging - Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Esperar conversión - Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Listar hojas - Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Resolver hoja Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Resolver hoja Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Leer Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Leer Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Guardar Nómina 2Q en memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Guardar Nómina 2Q en memoria": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar Sistema Propio",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar Sistema Propio": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Actualizar staging - Sistema Propio",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Actualizar staging - Sistema Propio": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Esperar conversión - Sistema Propio",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Listar hojas - Sistema Propio": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Resolver hoja EMPLEADOS",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Resolver hoja EMPLEADOS": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Leer EMPLEADOS",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Leer EMPLEADOS": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Guardar EMPLEADOS en memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Guardar EMPLEADOS en memoria": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar Planilla Consolidada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar Planilla Consolidada": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Actualizar staging - Consolidada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Actualizar staging - Consolidada": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Esperar conversión - Consolidada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Listar hojas - Consolidada": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Resolver hoja PLANILLA MENSUAL",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Resolver hoja PLANILLA MENSUAL": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Leer PLANILLA MENSUAL",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Leer PLANILLA MENSUAL": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Guardar PLANILLA MENSUAL en memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Guardar PLANILLA MENSUAL en memoria": {
|
|
"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
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Esperar conversión - Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Listar hojas - Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Esperar conversión - Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Listar hojas - Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Esperar conversión - Sistema Propio": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Listar hojas - Sistema Propio",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Esperar conversión - Consolidada": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Listar hojas - Consolidada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"binaryMode": "separate"
|
|
},
|
|
"staticData": {
|
|
"global": {
|
|
"gtSeguridadSocial": {}
|
|
}
|
|
},
|
|
"meta": null,
|
|
"versionId": "b63c48cf-a6f0-4386-b237-407a02a00696",
|
|
"activeVersionId": "b63c48cf-a6f0-4386-b237-407a02a00696",
|
|
"versionCounter": 22,
|
|
"triggerCount": 1,
|
|
"shared": [
|
|
{
|
|
"updatedAt": "2026-07-03T18:08:04.266Z",
|
|
"createdAt": "2026-07-03T18:08:04.266Z",
|
|
"role": "workflow:owner",
|
|
"workflowId": "loFncgFuvBlXUf8u",
|
|
"projectId": "PJpTANzTXIFibWsW",
|
|
"project": {
|
|
"updatedAt": "2026-04-22T14:25:09.686Z",
|
|
"createdAt": "2026-04-22T14:22:54.790Z",
|
|
"id": "PJpTANzTXIFibWsW",
|
|
"name": "Isaac Aracena <iaracena@gomezleemarketing.com>",
|
|
"type": "personal",
|
|
"icon": null,
|
|
"description": null,
|
|
"creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
|
|
}
|
|
}
|
|
],
|
|
"tags": [],
|
|
"activeVersion": {
|
|
"updatedAt": "2026-07-04T14:19:01.000Z",
|
|
"createdAt": "2026-07-04T14:18:46.103Z",
|
|
"versionId": "b63c48cf-a6f0-4386-b237-407a02a00696",
|
|
"workflowId": "loFncgFuvBlXUf8u",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"formTitle": "Cruce Seguridad Social Guatemala",
|
|
"formDescription": "Sube las dos nóminas de Guatemala del mes, el archivo Sistema Propio IGSS y la Planilla Consolidada mensual. El flujo generará un Google Sheets con hallazgos de afiliación y montos.",
|
|
"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": "Año a validar",
|
|
"fieldType": "number",
|
|
"requiredField": true
|
|
},
|
|
{
|
|
"fieldLabel": "Nómina Q1",
|
|
"fieldType": "file",
|
|
"multipleFiles": false,
|
|
"acceptFileTypes": ".xlsx, .xls, .xlsm, .csv",
|
|
"requiredField": true
|
|
},
|
|
{
|
|
"fieldLabel": "Nómina Q2",
|
|
"fieldType": "file",
|
|
"multipleFiles": false,
|
|
"acceptFileTypes": ".xlsx, .xls, .xlsm, .csv",
|
|
"requiredField": true
|
|
},
|
|
{
|
|
"fieldLabel": "Sistema Propio IGSS",
|
|
"fieldType": "file",
|
|
"multipleFiles": false,
|
|
"acceptFileTypes": ".xlsx, .xls, .xlsm, .csv",
|
|
"requiredField": true
|
|
},
|
|
{
|
|
"fieldLabel": "Planilla Consolidada",
|
|
"fieldType": "file",
|
|
"multipleFiles": false,
|
|
"acceptFileTypes": ".xlsx, .xls, .xlsm, .csv",
|
|
"requiredField": true
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"ignoreBots": true
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.formTrigger",
|
|
"typeVersion": 2.3,
|
|
"position": [
|
|
80,
|
|
128
|
|
],
|
|
"id": "81aef7de-7414-49ce-adb3-cf9b7dd3b696",
|
|
"name": "On form submission",
|
|
"webhookId": "282cf895-0642-42cc-afaa-372704ba2c5f"
|
|
},
|
|
{
|
|
"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 = `GT_SEG_${ts}_${Math.random().toString(36).slice(2, 8)}`;\n\nconst mes = String(item.json['Mes a validar'] || '').padStart(2, '0');\nconst anio = String(item.json['Año a validar'] || item.json['Ano a validar'] || now.getFullYear()).trim();\nconst meses = {\n '01':'Enero','02':'Febrero','03':'Marzo','04':'Abril',\n '05':'Mayo','06':'Junio','07':'Julio','08':'Agosto',\n '09':'Septiembre','10':'Octubre','11':'Noviembre','12':'Diciembre'\n};\nconst fechaTitulo = `${pad(now.getDate())}-${pad(now.getMonth()+1)}-${now.getFullYear()}`;\nconst periodoLabel = `${meses[mes] || mes} ${anio}`;\nconst reportTitle = `Cruce Seguridad Social Guatemala - ${periodoLabel} - ${fechaTitulo}`;\n\nconst data = $getWorkflowStaticData('global');\ndata.gtSeguridadSocial = data.gtSeguridadSocial || {};\ndata.gtSeguridadSocial[runId] = {\n startedAt: now.toISOString(),\n mes,\n anio,\n periodoLabel,\n files: { q1:'', q2:'', sistema:'', consolidado:'' },\n nomina: [],\n ajustes: [],\n empleados: [],\n planillaMensual: []\n};\n\nreturn [{\n json: { ...item.json, runId, mes, anio, periodoLabel, reportTitle, emailTo: 'iaracena@gomezleemarketing.com' },\n binary: item.binary\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
320,
|
|
128
|
|
],
|
|
"id": "cfb66319-c454-46d6-923e-5d0c1ea222d3",
|
|
"name": "Inicializar memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0];\nconst bin = init.binary || {};\nconst type = 'q1';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction looksExcel(fileName, mime) { const lower = norm(fileName); const m = norm(mime); return lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.xlsm') || lower.endsWith('.csv') || m.includes('spreadsheet') || m.includes('excel') || m.includes('csv'); }\nfunction scoreCandidate(key, fileName) {\n const text = norm(`${key} ${fileName}`);\n let score = 0;\n if (type === 'q1') { if (text.includes('nomina')) score += 20; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score += 120; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'q2') { if (text.includes('nomina')) score += 20; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score += 120; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'sistema') { if (/sistema|propio|2\\.2|igss/.test(text)) score += 120; if (/consolidada|consolidado|planilla consolidada|nomina/.test(text)) score -= 80; }\n if (type === 'consolidado') { if (/consolidada|consolidado|planilla consolidada/.test(text)) score += 120; if (/sistema|propio|2\\.2|nomina/.test(text)) score -= 80; }\n return score;\n}\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '');\n if (!looksExcel(fileName, mime)) continue;\n candidates.push({ key, file, fileName, score: scoreCandidate(key, fileName) });\n}\ncandidates.sort((a,b) => b.score - a.score);\nconst chosen = candidates[0];\nif (!chosen || chosen.score <= 0) throw new Error(`No encontré el archivo requerido para ${type}. Revisa los campos del formulario y los nombres de archivo.`);\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.json.runId];\nif (slot) slot.files[type] = chosen.fileName;\nreturn [{ json: { runId: init.json.runId, fileType: type, fileName: chosen.fileName }, binary: { data: chosen.file } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
560,
|
|
128
|
|
],
|
|
"id": "1e8d8999-f196-4241-bf57-2182f5ba5f6f",
|
|
"name": "Preparar Nómina 1Q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "update",
|
|
"fileId": {
|
|
"__rl": true,
|
|
"value": "1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4",
|
|
"mode": "list",
|
|
"cachedResultName": "Nomina Guatemala",
|
|
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4/edit"
|
|
},
|
|
"changeFileContent": true,
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.googleDrive",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
928,
|
|
128
|
|
],
|
|
"id": "75adafbf-6eb4-466c-b4da-2af5402c7b76",
|
|
"name": "Actualizar staging - Nómina 1Q",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4' }}?fields=spreadsheetId,sheets(properties(title,sheetId,hidden))",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
1536,
|
|
128
|
|
],
|
|
"id": "809fb908-59f6-45f9-8a11-5a38efda1d19",
|
|
"name": "Listar hojas - Nómina 1Q",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst spreadsheetId = $json.spreadsheetId || $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4';\n\nfunction norm(s) {\n return String(s || '')\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '');\n}\nfunction shouldReadPayrollSheet(title) {\n const n = norm(title);\n\n // Primero se excluyen copias y hojas auxiliares. Abril 2026 trae\n // \"Copia de 1) Nomina General 2\"; si se permite por includes('nominageneral'),\n // duplica Q1 y genera diferencias falsas.\n const excluded = [\n 'calculos', 'resumen', 'temporales', 'auditorias', 'auditoria',\n 'bono', 'movilidad', 'viaticos', 'viatico', 'combustible',\n 'combustibles', 'tabla', 'dinamica', 'copia', 'copy', 'boletas',\n 'nvscripts', 'autocrat', 'donotdelete', 'bajas'\n ];\n if (excluded.some(x => n.includes(x))) return false;\n\n // Solo la hoja principal real. No usar includes para evitar hojas copiadas.\n if (n === '1nominageneral' || n === 'nominageneral') return true;\n\n // Cualquier otra hoja no excluida se leerá como posible ajuste/complemento,\n // pero luego el nodo de parseo valida que tenga encabezado real de nómina.\n return true;\n}\n\nconst sheets = ($json.sheets || []).map(s => s.properties || {});\nconst selected = sheets\n .filter(p => p.title && shouldReadPayrollSheet(p.title))\n .map(p => p.title);\n\nif (!selected.some(t => norm(t).includes('nominageneral'))) {\n throw new Error(`No encontré la hoja 1) Nomina General. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\n}\nif (!selected.length) {\n throw new Error(`No encontré hojas válidas para leer en la nómina. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\n}\n\nreturn [{\n json: {\n runId: init.runId,\n spreadsheetId,\n sheetTitles: selected,\n ranges: selected.map(t => `'${String(t).replace(/'/g, \"''\")}'!A:AZ`)\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1776,
|
|
128
|
|
],
|
|
"id": "4c813693-118f-4f90-87d9-336d57082397",
|
|
"name": "Resolver hoja Nómina 1Q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + $json.spreadsheetId + '/values:batchGet?' + $json.ranges.map(r => 'ranges=' + encodeURIComponent(r)).join('&') + '&valueRenderOption=UNFORMATTED_VALUE&dateTimeRenderOption=SERIAL_NUMBER' }}",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
2032,
|
|
128
|
|
],
|
|
"id": "a8d4b5d4-ff8b-43e2-91d8-16d5897e59ed",
|
|
"name": "Leer Nómina 1Q",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const qLabel = '1Q';\nconst init = $items('Inicializar memoria')[0].json;\nconst valueRanges = $json.valueRanges || [];\n\nfunction clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction removeAccents(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, ''); }\nfunction normSheet(s) { return removeAccents(s).toLowerCase().replace(/[^a-z0-9]/g, ''); }\nfunction normalizeName(s) {\n return clean(s)\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase()\n .replace(/[^A-Z0-9 ]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\nfunction toNumber(v) {\n if (v === null || v === undefined || v === '') return 0;\n if (typeof v === 'number') return Number.isFinite(v) ? v : 0;\n let s = String(v).trim().replace(/Q|\\$|USD|GTQ|,/gi, '').replace(/\\s/g, '');\n if (/^\\((.*)\\)$/.test(s)) s = '-' + s.replace(/^\\(|\\)$/g, '');\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n}\nfunction normalizeAf(v) {\n const raw = clean(v);\n if (!raw || /pendiente|n\\/a|na|sin/i.test(raw)) {\n return { value:'', status: raw ? 'Pendiente/Vacío' : 'Vacío', raw };\n }\n let digits = '';\n if (typeof v === 'number') {\n digits = Math.trunc(v).toString();\n } else {\n const s = raw.replace(/,/g, '').trim();\n if (/e\\+?/i.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) digits = Math.trunc(n).toString();\n } else {\n digits = s.replace(/\\D/g, '');\n }\n }\n if (!digits || /^0+$/.test(digits)) return { value:'', status:'Inválido', raw };\n if (digits.length < 8 || digits.length > 13) return { value:'', status:'Longitud inválida', raw };\n return { value:digits, status: /e\\+?/i.test(String(raw)) ? 'Válido científico' : 'Válido', raw };\n}\nfunction sheetNameFromRange(range) {\n let left = String(range || '').split('!')[0] || '';\n left = left.replace(/^'/, '').replace(/'$/, '').replace(/''/g, \"'\");\n return left;\n}\nfunction isMainSheet(title) {\n const n = normSheet(title);\n // Solo la hoja principal real; NO contar copias como \"Copia de 1) Nomina General 2\".\n return n === '1nominageneral' || n === 'nominageneral';\n}\nfunction looksLikePayrollSheet(values) {\n const header = (values[0] || []).map(v => removeAccents(clean(v)).toLowerCase().replace(/[^a-z0-9]/g, ''));\n return header[0] === 'proyecto'\n && header[5] === 'igss'\n && (header[7] === 'nombrecompleto' || header[7].includes('nombre'))\n && header[14].includes('salario')\n && header[19].includes('factor');\n}\nfunction parsePayrollRows(values, sheetTitle, sourceType) {\n const records = [];\n if (!looksLikePayrollSheet(values)) return records;\n let seen = false;\n for (let i = 1; i < values.length; i++) {\n const row = values[i] || [];\n const proyecto = clean(row[0]);\n const moneda = clean(row[1]);\n const igss = normalizeAf(row[5]);\n const nombre = clean(row[7]);\n\n if (seen && !proyecto && !nombre && !clean(row[5])) break;\n if (!proyecto || !nombre || /^total/i.test(proyecto) || /^total/i.test(nombre)) continue;\n seen = true;\n\n const factor = clean(row[19]);\n const factorNorm = removeAccents(factor).toLowerCase();\n const factorSi = /^(si|sí|yes|y|true|1)$/i.test(factorNorm);\n\n const salario = toNumber(row[14]);\n const vacaciones = toNumber(row[16]);\n const horasExtras = toNumber(row[18]);\n const variable = toNumber(row[21]);\n const montoAfectoOriginal = salario + vacaciones + horasExtras + (factorSi ? variable : 0);\n if (Math.abs(montoAfectoOriginal) <= 0.004 && sourceType === 'ajuste_candidato') continue;\n\n records.push({\n q: qLabel,\n sheetTitle,\n rowNumber: i + 1,\n sourceType,\n proyecto_nomina: proyecto,\n moneda,\n nombre_nomina: nombre,\n nombre_norm: normalizeName(nombre),\n igss_original: igss.raw,\n igss_norm: igss.value,\n igss_estado: igss.status,\n factor_prestacional: factor,\n salario_periodo: Number(salario.toFixed(2)),\n vacaciones: Number(vacaciones.toFixed(2)),\n horas_extras: Number(horasExtras.toFixed(2)),\n variable: Number(variable.toFixed(2)),\n variable_afecta: factorSi ? 'Sí' : 'No',\n monto_afecto_original: Number(montoAfectoOriginal.toFixed(2)),\n monto_afecto_gtq: Number(montoAfectoOriginal.toFixed(2)),\n regla_aplicada: factorSi ? 'Sueldo + vacaciones + horas extras + variable' : 'Sueldo + vacaciones + horas extras'\n });\n }\n return records;\n}\nfunction samePerson(a, b) {\n if (a.igss_norm && b.igss_norm && a.igss_norm === b.igss_norm) return true;\n return normalizeName(a.nombre_nomina) === normalizeName(b.nombre_nomina);\n}\nfunction amountEquals(a, b) {\n return Math.abs(Number(a || 0) - Number(b || 0)) <= 0.05;\n}\nfunction deriveAdjustments(baseRecords, candidateRecords) {\n const ajustes = [];\n\n function pushAdjustment(c, amount, tipo, referencia, observacion) {\n if (Math.abs(amount) <= 0.05) return;\n ajustes.push({\n ...c,\n sourceType: 'ajuste',\n monto_ajuste_original: Number(amount.toFixed(2)),\n monto_ajuste_gtq: Number(amount.toFixed(2)),\n tipo_ajuste: tipo,\n referencia: referencia || '',\n observacion_ajuste: observacion || (tipo === 'Corrección de monto'\n ? 'Se incluyó solo la diferencia entre la fila corregida y la fila principal.'\n : 'Se incluyó como complemento de nómina.')\n });\n }\n\n const used = new Set();\n const groups = new Map();\n candidateRecords.forEach((c, idx) => {\n c.__idx = idx;\n const key = `${c.sheetTitle || ''}||${c.igss_norm || c.nombre_norm || normalizeName(c.nombre_nomina)}`;\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key).push(c);\n });\n\n // Caso abril 2026: la hoja Complemento trae valor anterior y valor corregido\n // de la variable de Oliver. No se suman ambas filas completas; se suma solo\n // la diferencia entre variable corregida y variable ya incluida en la nómina.\n for (const group of groups.values()) {\n if (!group.length) continue;\n const c0 = group[0];\n const isComplementSheet = /complement|complemento/i.test(removeAccents(c0.sheetTitle || '').toLowerCase());\n if (!isComplementSheet) continue;\n\n const possibleBase = baseRecords.filter(b => samePerson(b, c0));\n const baseAny = possibleBase[0];\n if (!baseAny) continue;\n\n const baseVariable = Number(baseAny.variable || 0);\n if (Math.abs(baseVariable) <= 0.05) continue;\n\n const variableOnly = group.filter(c =>\n Math.abs(Number(c.salario_periodo || 0)) <= 0.05 &&\n Math.abs(Number(c.vacaciones || 0)) <= 0.05 &&\n Math.abs(Number(c.horas_extras || 0)) <= 0.05 &&\n Math.abs(Number(c.variable || 0)) > 0.05 &&\n amountEquals(c.monto_afecto_original, c.variable)\n );\n\n const oldValue = variableOnly.find(c => amountEquals(c.monto_afecto_original, baseVariable));\n const corrected = variableOnly\n .filter(c => c.monto_afecto_original > baseVariable + 0.05)\n .sort((a,b) => Number(b.monto_afecto_original || 0) - Number(a.monto_afecto_original || 0))[0];\n\n if (oldValue && corrected) {\n for (const c of variableOnly) used.add(c.__idx);\n const diff = Number((corrected.monto_afecto_original - baseVariable).toFixed(2));\n pushAdjustment(\n corrected,\n diff,\n 'Corrección de variable',\n `Variable principal ${baseVariable}`,\n 'Se incluyó solo la diferencia entre la variable corregida y la variable ya incluida en la nómina principal.'\n );\n }\n }\n\n for (const c of candidateRecords) {\n if (used.has(c.__idx)) continue;\n const possibleBase = baseRecords.filter(b => samePerson(b, c));\n const baseMatch = possibleBase.find(b => amountEquals(b.monto_afecto_original, c.monto_afecto_original));\n const baseAny = possibleBase[0];\n\n if (baseMatch) {\n // Esta fila es una repetición de la fila principal, no se suma como ajuste.\n continue;\n }\n\n let adjustmentAmount = c.monto_afecto_original;\n let tipo = 'Complemento';\n let baseReference = '';\n\n const isComplementSheet = /complement|complemento/i.test(removeAccents(c.sheetTitle || '').toLowerCase());\n if (!isComplementSheet && baseAny && c.monto_afecto_original > baseAny.monto_afecto_original && possibleBase.some(b => amountEquals(b.monto_afecto_original, baseAny.monto_afecto_original))) {\n // Ejemplo: hoja Oliver trae una fila corregida completa. Se suma solo la diferencia contra la fila principal.\n adjustmentAmount = Number((c.monto_afecto_original - baseAny.monto_afecto_original).toFixed(2));\n tipo = 'Corrección de monto';\n baseReference = `Monto principal ${baseAny.monto_afecto_original}`;\n }\n\n pushAdjustment(c, Number(adjustmentAmount || 0), tipo, baseReference, tipo === 'Corrección de monto'\n ? 'Se incluyó solo la diferencia entre la fila corregida y la fila principal.'\n : 'Se incluyó como complemento de nómina.');\n }\n\n return ajustes;\n}\n\nconst mainRecords = [];\nconst candidates = [];\n\nfor (const vr of valueRanges) {\n const sheetTitle = sheetNameFromRange(vr.range);\n const values = vr.values || [];\n if (!values.length) continue;\n\n if (isMainSheet(sheetTitle)) {\n mainRecords.push(...parsePayrollRows(values, sheetTitle, 'principal'));\n } else {\n candidates.push(...parsePayrollRows(values, sheetTitle, 'ajuste_candidato'));\n }\n}\n\nif (!mainRecords.length) {\n throw new Error(`No pude leer registros válidos desde 1) Nomina General para ${qLabel}.`);\n}\n\nconst ajustes = deriveAdjustments(mainRecords, candidates);\n\nconst data = $getWorkflowStaticData('global');\ndata.gtSeguridadSocial = data.gtSeguridadSocial || {};\ndata.gtSeguridadSocial[init.runId] = data.gtSeguridadSocial[init.runId] || {};\ndata.gtSeguridadSocial[init.runId].nomina.push(...mainRecords);\ndata.gtSeguridadSocial[init.runId].ajustes = data.gtSeguridadSocial[init.runId].ajustes || [];\ndata.gtSeguridadSocial[init.runId].ajustes.push(...ajustes);\n\nreturn [{\n json: {\n runId: init.runId,\n quincena: qLabel,\n payrollRows: mainRecords.length,\n invalidIgssRows: mainRecords.filter(r => !r.igss_norm).length,\n adjustmentRows: ajustes.length,\n adjustmentSheets: [...new Set(ajustes.map(a => a.sheetTitle))]\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2272,
|
|
128
|
|
],
|
|
"id": "20bbc195-d609-4009-869d-7f7b05b940ee",
|
|
"name": "Guardar Nómina 1Q en memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0];\nconst bin = init.binary || {};\nconst type = 'q2';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction looksExcel(fileName, mime) { const lower = norm(fileName); const m = norm(mime); return lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.xlsm') || lower.endsWith('.csv') || m.includes('spreadsheet') || m.includes('excel') || m.includes('csv'); }\nfunction scoreCandidate(key, fileName) {\n const text = norm(`${key} ${fileName}`);\n let score = 0;\n if (type === 'q1') { if (text.includes('nomina')) score += 20; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score += 120; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'q2') { if (text.includes('nomina')) score += 20; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score += 120; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'sistema') { if (/sistema|propio|2\\.2|igss/.test(text)) score += 120; if (/consolidada|consolidado|planilla consolidada|nomina/.test(text)) score -= 80; }\n if (type === 'consolidado') { if (/consolidada|consolidado|planilla consolidada/.test(text)) score += 120; if (/sistema|propio|2\\.2|nomina/.test(text)) score -= 80; }\n return score;\n}\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '');\n if (!looksExcel(fileName, mime)) continue;\n candidates.push({ key, file, fileName, score: scoreCandidate(key, fileName) });\n}\ncandidates.sort((a,b) => b.score - a.score);\nconst chosen = candidates[0];\nif (!chosen || chosen.score <= 0) throw new Error(`No encontré el archivo requerido para ${type}. Revisa los campos del formulario y los nombres de archivo.`);\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.json.runId];\nif (slot) slot.files[type] = chosen.fileName;\nreturn [{ json: { runId: init.json.runId, fileType: type, fileName: chosen.fileName }, binary: { data: chosen.file } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2512,
|
|
128
|
|
],
|
|
"id": "651da05c-6759-48de-99e5-2eb8cb4615e4",
|
|
"name": "Preparar Nómina 2Q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "update",
|
|
"fileId": {
|
|
"__rl": true,
|
|
"value": "1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4",
|
|
"mode": "list",
|
|
"cachedResultName": "Nomina Guatemala",
|
|
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4/edit"
|
|
},
|
|
"changeFileContent": true,
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.googleDrive",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
2768,
|
|
128
|
|
],
|
|
"id": "7d919da1-d6bb-424a-a6da-5c0e8d346f75",
|
|
"name": "Actualizar staging - Nómina 2Q",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4' }}?fields=spreadsheetId,sheets(properties(title,sheetId,hidden))",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
3296,
|
|
128
|
|
],
|
|
"id": "4fb42911-ad0a-4b08-9438-5c65ef1726ae",
|
|
"name": "Listar hojas - Nómina 2Q",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst spreadsheetId = $json.spreadsheetId || $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4';\n\nfunction norm(s) {\n return String(s || '')\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '');\n}\nfunction shouldReadPayrollSheet(title) {\n const n = norm(title);\n\n // Primero se excluyen copias y hojas auxiliares. Abril 2026 trae\n // \"Copia de 1) Nomina General 2\"; si se permite por includes('nominageneral'),\n // duplica Q1 y genera diferencias falsas.\n const excluded = [\n 'calculos', 'resumen', 'temporales', 'auditorias', 'auditoria',\n 'bono', 'movilidad', 'viaticos', 'viatico', 'combustible',\n 'combustibles', 'tabla', 'dinamica', 'copia', 'copy', 'boletas',\n 'nvscripts', 'autocrat', 'donotdelete', 'bajas'\n ];\n if (excluded.some(x => n.includes(x))) return false;\n\n // Solo la hoja principal real. No usar includes para evitar hojas copiadas.\n if (n === '1nominageneral' || n === 'nominageneral') return true;\n\n // Cualquier otra hoja no excluida se leerá como posible ajuste/complemento,\n // pero luego el nodo de parseo valida que tenga encabezado real de nómina.\n return true;\n}\n\nconst sheets = ($json.sheets || []).map(s => s.properties || {});\nconst selected = sheets\n .filter(p => p.title && shouldReadPayrollSheet(p.title))\n .map(p => p.title);\n\nif (!selected.some(t => norm(t).includes('nominageneral'))) {\n throw new Error(`No encontré la hoja 1) Nomina General. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\n}\nif (!selected.length) {\n throw new Error(`No encontré hojas válidas para leer en la nómina. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\n}\n\nreturn [{\n json: {\n runId: init.runId,\n spreadsheetId,\n sheetTitles: selected,\n ranges: selected.map(t => `'${String(t).replace(/'/g, \"''\")}'!A:AZ`)\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
3616,
|
|
128
|
|
],
|
|
"id": "a7dbed9c-8d11-4190-b934-53425ef4a7ea",
|
|
"name": "Resolver hoja Nómina 2Q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + $json.spreadsheetId + '/values:batchGet?' + $json.ranges.map(r => 'ranges=' + encodeURIComponent(r)).join('&') + '&valueRenderOption=UNFORMATTED_VALUE&dateTimeRenderOption=SERIAL_NUMBER' }}",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
3936,
|
|
128
|
|
],
|
|
"id": "4a9bc146-8d26-4e33-b49d-aa9e1ef4ce35",
|
|
"name": "Leer Nómina 2Q",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const qLabel = '2Q';\nconst init = $items('Inicializar memoria')[0].json;\nconst valueRanges = $json.valueRanges || [];\n\nfunction clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction removeAccents(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, ''); }\nfunction normSheet(s) { return removeAccents(s).toLowerCase().replace(/[^a-z0-9]/g, ''); }\nfunction normalizeName(s) {\n return clean(s)\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase()\n .replace(/[^A-Z0-9 ]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\nfunction toNumber(v) {\n if (v === null || v === undefined || v === '') return 0;\n if (typeof v === 'number') return Number.isFinite(v) ? v : 0;\n let s = String(v).trim().replace(/Q|\\$|USD|GTQ|,/gi, '').replace(/\\s/g, '');\n if (/^\\((.*)\\)$/.test(s)) s = '-' + s.replace(/^\\(|\\)$/g, '');\n const n = Number(s);\n return Number.isFinite(n) ? n : 0;\n}\nfunction normalizeAf(v) {\n const raw = clean(v);\n if (!raw || /pendiente|n\\/a|na|sin/i.test(raw)) {\n return { value:'', status: raw ? 'Pendiente/Vacío' : 'Vacío', raw };\n }\n let digits = '';\n if (typeof v === 'number') {\n digits = Math.trunc(v).toString();\n } else {\n const s = raw.replace(/,/g, '').trim();\n if (/e\\+?/i.test(s)) {\n const n = Number(s);\n if (Number.isFinite(n)) digits = Math.trunc(n).toString();\n } else {\n digits = s.replace(/\\D/g, '');\n }\n }\n if (!digits || /^0+$/.test(digits)) return { value:'', status:'Inválido', raw };\n if (digits.length < 8 || digits.length > 13) return { value:'', status:'Longitud inválida', raw };\n return { value:digits, status: /e\\+?/i.test(String(raw)) ? 'Válido científico' : 'Válido', raw };\n}\nfunction sheetNameFromRange(range) {\n let left = String(range || '').split('!')[0] || '';\n left = left.replace(/^'/, '').replace(/'$/, '').replace(/''/g, \"'\");\n return left;\n}\nfunction isMainSheet(title) {\n const n = normSheet(title);\n // Solo la hoja principal real; NO contar copias como \"Copia de 1) Nomina General 2\".\n return n === '1nominageneral' || n === 'nominageneral';\n}\nfunction looksLikePayrollSheet(values) {\n const header = (values[0] || []).map(v => removeAccents(clean(v)).toLowerCase().replace(/[^a-z0-9]/g, ''));\n return header[0] === 'proyecto'\n && header[5] === 'igss'\n && (header[7] === 'nombrecompleto' || header[7].includes('nombre'))\n && header[14].includes('salario')\n && header[19].includes('factor');\n}\nfunction parsePayrollRows(values, sheetTitle, sourceType) {\n const records = [];\n if (!looksLikePayrollSheet(values)) return records;\n let seen = false;\n for (let i = 1; i < values.length; i++) {\n const row = values[i] || [];\n const proyecto = clean(row[0]);\n const moneda = clean(row[1]);\n const igss = normalizeAf(row[5]);\n const nombre = clean(row[7]);\n\n if (seen && !proyecto && !nombre && !clean(row[5])) break;\n if (!proyecto || !nombre || /^total/i.test(proyecto) || /^total/i.test(nombre)) continue;\n seen = true;\n\n const factor = clean(row[19]);\n const factorNorm = removeAccents(factor).toLowerCase();\n const factorSi = /^(si|sí|yes|y|true|1)$/i.test(factorNorm);\n\n const salario = toNumber(row[14]);\n const vacaciones = toNumber(row[16]);\n const horasExtras = toNumber(row[18]);\n const variable = toNumber(row[21]);\n const montoAfectoOriginal = salario + vacaciones + horasExtras + (factorSi ? variable : 0);\n if (Math.abs(montoAfectoOriginal) <= 0.004 && sourceType === 'ajuste_candidato') continue;\n\n records.push({\n q: qLabel,\n sheetTitle,\n rowNumber: i + 1,\n sourceType,\n proyecto_nomina: proyecto,\n moneda,\n nombre_nomina: nombre,\n nombre_norm: normalizeName(nombre),\n igss_original: igss.raw,\n igss_norm: igss.value,\n igss_estado: igss.status,\n factor_prestacional: factor,\n salario_periodo: Number(salario.toFixed(2)),\n vacaciones: Number(vacaciones.toFixed(2)),\n horas_extras: Number(horasExtras.toFixed(2)),\n variable: Number(variable.toFixed(2)),\n variable_afecta: factorSi ? 'Sí' : 'No',\n monto_afecto_original: Number(montoAfectoOriginal.toFixed(2)),\n monto_afecto_gtq: Number(montoAfectoOriginal.toFixed(2)),\n regla_aplicada: factorSi ? 'Sueldo + vacaciones + horas extras + variable' : 'Sueldo + vacaciones + horas extras'\n });\n }\n return records;\n}\nfunction samePerson(a, b) {\n if (a.igss_norm && b.igss_norm && a.igss_norm === b.igss_norm) return true;\n return normalizeName(a.nombre_nomina) === normalizeName(b.nombre_nomina);\n}\nfunction amountEquals(a, b) {\n return Math.abs(Number(a || 0) - Number(b || 0)) <= 0.05;\n}\nfunction deriveAdjustments(baseRecords, candidateRecords) {\n const ajustes = [];\n\n function pushAdjustment(c, amount, tipo, referencia, observacion) {\n if (Math.abs(amount) <= 0.05) return;\n ajustes.push({\n ...c,\n sourceType: 'ajuste',\n monto_ajuste_original: Number(amount.toFixed(2)),\n monto_ajuste_gtq: Number(amount.toFixed(2)),\n tipo_ajuste: tipo,\n referencia: referencia || '',\n observacion_ajuste: observacion || (tipo === 'Corrección de monto'\n ? 'Se incluyó solo la diferencia entre la fila corregida y la fila principal.'\n : 'Se incluyó como complemento de nómina.')\n });\n }\n\n const used = new Set();\n const groups = new Map();\n candidateRecords.forEach((c, idx) => {\n c.__idx = idx;\n const key = `${c.sheetTitle || ''}||${c.igss_norm || c.nombre_norm || normalizeName(c.nombre_nomina)}`;\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key).push(c);\n });\n\n // Caso abril 2026: la hoja Complemento trae valor anterior y valor corregido\n // de la variable de Oliver. No se suman ambas filas completas; se suma solo\n // la diferencia entre variable corregida y variable ya incluida en la nómina.\n for (const group of groups.values()) {\n if (!group.length) continue;\n const c0 = group[0];\n const isComplementSheet = /complement|complemento/i.test(removeAccents(c0.sheetTitle || '').toLowerCase());\n if (!isComplementSheet) continue;\n\n const possibleBase = baseRecords.filter(b => samePerson(b, c0));\n const baseAny = possibleBase[0];\n if (!baseAny) continue;\n\n const baseVariable = Number(baseAny.variable || 0);\n if (Math.abs(baseVariable) <= 0.05) continue;\n\n const variableOnly = group.filter(c =>\n Math.abs(Number(c.salario_periodo || 0)) <= 0.05 &&\n Math.abs(Number(c.vacaciones || 0)) <= 0.05 &&\n Math.abs(Number(c.horas_extras || 0)) <= 0.05 &&\n Math.abs(Number(c.variable || 0)) > 0.05 &&\n amountEquals(c.monto_afecto_original, c.variable)\n );\n\n const oldValue = variableOnly.find(c => amountEquals(c.monto_afecto_original, baseVariable));\n const corrected = variableOnly\n .filter(c => c.monto_afecto_original > baseVariable + 0.05)\n .sort((a,b) => Number(b.monto_afecto_original || 0) - Number(a.monto_afecto_original || 0))[0];\n\n if (oldValue && corrected) {\n for (const c of variableOnly) used.add(c.__idx);\n const diff = Number((corrected.monto_afecto_original - baseVariable).toFixed(2));\n pushAdjustment(\n corrected,\n diff,\n 'Corrección de variable',\n `Variable principal ${baseVariable}`,\n 'Se incluyó solo la diferencia entre la variable corregida y la variable ya incluida en la nómina principal.'\n );\n }\n }\n\n for (const c of candidateRecords) {\n if (used.has(c.__idx)) continue;\n const possibleBase = baseRecords.filter(b => samePerson(b, c));\n const baseMatch = possibleBase.find(b => amountEquals(b.monto_afecto_original, c.monto_afecto_original));\n const baseAny = possibleBase[0];\n\n if (baseMatch) {\n // Esta fila es una repetición de la fila principal, no se suma como ajuste.\n continue;\n }\n\n let adjustmentAmount = c.monto_afecto_original;\n let tipo = 'Complemento';\n let baseReference = '';\n\n const isComplementSheet = /complement|complemento/i.test(removeAccents(c.sheetTitle || '').toLowerCase());\n if (!isComplementSheet && baseAny && c.monto_afecto_original > baseAny.monto_afecto_original && possibleBase.some(b => amountEquals(b.monto_afecto_original, baseAny.monto_afecto_original))) {\n // Ejemplo: hoja Oliver trae una fila corregida completa. Se suma solo la diferencia contra la fila principal.\n adjustmentAmount = Number((c.monto_afecto_original - baseAny.monto_afecto_original).toFixed(2));\n tipo = 'Corrección de monto';\n baseReference = `Monto principal ${baseAny.monto_afecto_original}`;\n }\n\n pushAdjustment(c, Number(adjustmentAmount || 0), tipo, baseReference, tipo === 'Corrección de monto'\n ? 'Se incluyó solo la diferencia entre la fila corregida y la fila principal.'\n : 'Se incluyó como complemento de nómina.');\n }\n\n return ajustes;\n}\n\nconst mainRecords = [];\nconst candidates = [];\n\nfor (const vr of valueRanges) {\n const sheetTitle = sheetNameFromRange(vr.range);\n const values = vr.values || [];\n if (!values.length) continue;\n\n if (isMainSheet(sheetTitle)) {\n mainRecords.push(...parsePayrollRows(values, sheetTitle, 'principal'));\n } else {\n candidates.push(...parsePayrollRows(values, sheetTitle, 'ajuste_candidato'));\n }\n}\n\nif (!mainRecords.length) {\n throw new Error(`No pude leer registros válidos desde 1) Nomina General para ${qLabel}.`);\n}\n\nconst ajustes = deriveAdjustments(mainRecords, candidates);\n\nconst data = $getWorkflowStaticData('global');\ndata.gtSeguridadSocial = data.gtSeguridadSocial || {};\ndata.gtSeguridadSocial[init.runId] = data.gtSeguridadSocial[init.runId] || {};\ndata.gtSeguridadSocial[init.runId].nomina.push(...mainRecords);\ndata.gtSeguridadSocial[init.runId].ajustes = data.gtSeguridadSocial[init.runId].ajustes || [];\ndata.gtSeguridadSocial[init.runId].ajustes.push(...ajustes);\n\nreturn [{\n json: {\n runId: init.runId,\n quincena: qLabel,\n payrollRows: mainRecords.length,\n invalidIgssRows: mainRecords.filter(r => !r.igss_norm).length,\n adjustmentRows: ajustes.length,\n adjustmentSheets: [...new Set(ajustes.map(a => a.sheetTitle))]\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
4240,
|
|
128
|
|
],
|
|
"id": "05be903c-d13c-4388-a59b-4bb4e61d7f31",
|
|
"name": "Guardar Nómina 2Q en memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0];\nconst bin = init.binary || {};\nconst type = 'sistema';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction looksExcel(fileName, mime) { const lower = norm(fileName); const m = norm(mime); return lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.xlsm') || lower.endsWith('.csv') || m.includes('spreadsheet') || m.includes('excel') || m.includes('csv'); }\nfunction scoreCandidate(key, fileName) {\n const text = norm(`${key} ${fileName}`);\n let score = 0;\n if (type === 'q1') { if (text.includes('nomina')) score += 20; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score += 120; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'q2') { if (text.includes('nomina')) score += 20; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score += 120; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'sistema') { if (/sistema|propio|2\\.2|igss/.test(text)) score += 120; if (/consolidada|consolidado|planilla consolidada|nomina/.test(text)) score -= 80; }\n if (type === 'consolidado') { if (/consolidada|consolidado|planilla consolidada/.test(text)) score += 120; if (/sistema|propio|2\\.2|nomina/.test(text)) score -= 80; }\n return score;\n}\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '');\n if (!looksExcel(fileName, mime)) continue;\n candidates.push({ key, file, fileName, score: scoreCandidate(key, fileName) });\n}\ncandidates.sort((a,b) => b.score - a.score);\nconst chosen = candidates[0];\nif (!chosen || chosen.score <= 0) throw new Error(`No encontré el archivo requerido para ${type}. Revisa los campos del formulario y los nombres de archivo.`);\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.json.runId];\nif (slot) slot.files[type] = chosen.fileName;\nreturn [{ json: { runId: init.json.runId, fileType: type, fileName: chosen.fileName }, binary: { data: chosen.file } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
4816,
|
|
144
|
|
],
|
|
"id": "68a4c8c9-bbb2-4c6f-babe-e5eb4d4000eb",
|
|
"name": "Preparar Sistema Propio"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "update",
|
|
"fileId": {
|
|
"__rl": true,
|
|
"value": "1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4",
|
|
"mode": "list",
|
|
"cachedResultName": "Nomina Guatemala",
|
|
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4/edit"
|
|
},
|
|
"changeFileContent": true,
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.googleDrive",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
5024,
|
|
144
|
|
],
|
|
"id": "80de44b1-e5b1-4c5a-a7cf-91a61f398b1a",
|
|
"name": "Actualizar staging - Sistema Propio",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4' }}?fields=spreadsheetId,sheets(properties(title,sheetId,hidden))",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
5456,
|
|
144
|
|
],
|
|
"id": "4b11aaac-9dcf-48cc-8226-43f4832f2127",
|
|
"name": "Listar hojas - Sistema Propio",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst target = 'empleados';\nconst spreadsheetId = $json.spreadsheetId || $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]/g, ''); }\nconst wanted = { nomina: ['1nominageneral','nominageneral'], empleados: ['empleados'], planilla: ['planillamensual'] }[target];\nconst sheets = ($json.sheets || []).map(s => s.properties || {});\nlet match = null;\nfor (const w of wanted) { match = sheets.find(p => norm(p.title) === w || norm(p.title).includes(w)); if (match) break; }\nif (!match) throw new Error(`No encontré la hoja esperada para ${target}. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\nreturn [{ json: { runId: init.runId, spreadsheetId, sheetTitle: match.title, sheetId: match.sheetId, target } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
5680,
|
|
144
|
|
],
|
|
"id": "11fc59a9-9bf5-46ea-a82d-563f7ae6893a",
|
|
"name": "Resolver hoja EMPLEADOS"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + $json.spreadsheetId + '/values/' + encodeURIComponent(\"'\" + $json.sheetTitle.replace(/'/g, \"''\") + \"'!A:AZ\") + '?valueRenderOption=UNFORMATTED_VALUE&dateTimeRenderOption=SERIAL_NUMBER' }}",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
5904,
|
|
144
|
|
],
|
|
"id": "40ce68e1-04ce-43b6-975b-9b48c2734073",
|
|
"name": "Leer EMPLEADOS",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst values = $json.values || [];\nfunction clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction normalizeName(s) { return clean(s).normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toUpperCase().replace(/[^A-Z0-9 ]/g, ' ').replace(/\\s+/g, ' ').trim(); }\nfunction normalizeAf(v) { const raw = clean(v); if (!raw) return ''; let digits = ''; if (typeof v === 'number') digits = Math.trunc(v).toString(); else { const s = raw.replace(/,/g, '').trim(); if (/e\\+?/i.test(s)) { const n = Number(s); if (Number.isFinite(n)) digits = Math.trunc(n).toString(); } else digits = s.replace(/\\D/g, ''); } return digits && digits.length >= 8 && digits.length <= 13 && !/^0+$/.test(digits) ? digits : ''; }\nfunction toNumber(v) { const n = Number(String(v ?? '').replace(/Q|\\$|USD|GTQ|,/gi, '').trim()); return Number.isFinite(n) ? n : 0; }\nconst records = [];\nfor (let i = 1; i < values.length; i++) { const row = values[i] || []; const afiliacion = normalizeAf(row[2]); if (!afiliacion) continue; const nombre = [row[4], row[6], row[8], row[10], row[12]].map(clean).filter(Boolean).join(' '); records.push({ afiliacion, nombre_contabilidad: nombre, nombre_norm: normalizeName(nombre), sueldo_devengado: Number(toNumber(row[14]).toFixed(2)) }); }\nconst by = new Map(); for (const r of records) if (!by.has(r.afiliacion)) by.set(r.afiliacion, r);\nconst finalRecords = [...by.values()];\nif (!finalRecords.length) throw new Error('No pude leer afiliaciones desde EMPLEADOS columna C.');\nconst data = $getWorkflowStaticData('global'); data.gtSeguridadSocial = data.gtSeguridadSocial || {}; data.gtSeguridadSocial[init.runId] = data.gtSeguridadSocial[init.runId] || {}; data.gtSeguridadSocial[init.runId].empleados = finalRecords;\nreturn [{ json: { runId: init.runId, empleadosRows: finalRecords.length } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
6144,
|
|
144
|
|
],
|
|
"id": "c20c8ed1-102f-42ac-b368-7b8277c8e5a9",
|
|
"name": "Guardar EMPLEADOS en memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0];\nconst bin = init.binary || {};\nconst type = 'consolidado';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase(); }\nfunction looksExcel(fileName, mime) { const lower = norm(fileName); const m = norm(mime); return lower.endsWith('.xlsx') || lower.endsWith('.xls') || lower.endsWith('.xlsm') || lower.endsWith('.csv') || m.includes('spreadsheet') || m.includes('excel') || m.includes('csv'); }\nfunction scoreCandidate(key, fileName) {\n const text = norm(`${key} ${fileName}`);\n let score = 0;\n if (type === 'q1') { if (text.includes('nomina')) score += 20; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score += 120; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'q2') { if (text.includes('nomina')) score += 20; if (/\\b2q\\b|q2|segunda|mayo 30|30 2026/.test(text)) score += 120; if (/\\b1q\\b|q1|primera|mayo 15|15 2026/.test(text)) score -= 150; if (/sistema|consolidada|consolidado/.test(text)) score -= 200; }\n if (type === 'sistema') { if (/sistema|propio|2\\.2|igss/.test(text)) score += 120; if (/consolidada|consolidado|planilla consolidada|nomina/.test(text)) score -= 80; }\n if (type === 'consolidado') { if (/consolidada|consolidado|planilla consolidada/.test(text)) score += 120; if (/sistema|propio|2\\.2|nomina/.test(text)) score -= 80; }\n return score;\n}\nconst candidates = [];\nfor (const [key, file] of Object.entries(bin)) {\n const fileName = String(file.fileName || key);\n const mime = String(file.mimeType || '');\n if (!looksExcel(fileName, mime)) continue;\n candidates.push({ key, file, fileName, score: scoreCandidate(key, fileName) });\n}\ncandidates.sort((a,b) => b.score - a.score);\nconst chosen = candidates[0];\nif (!chosen || chosen.score <= 0) throw new Error(`No encontré el archivo requerido para ${type}. Revisa los campos del formulario y los nombres de archivo.`);\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.json.runId];\nif (slot) slot.files[type] = chosen.fileName;\nreturn [{ json: { runId: init.json.runId, fileType: type, fileName: chosen.fileName }, binary: { data: chosen.file } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
6320,
|
|
144
|
|
],
|
|
"id": "b791dc1c-9487-42ab-a3ed-ebac85b4df46",
|
|
"name": "Preparar Planilla Consolidada"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "update",
|
|
"fileId": {
|
|
"__rl": true,
|
|
"value": "1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4",
|
|
"mode": "list",
|
|
"cachedResultName": "Nomina Guatemala",
|
|
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4/edit"
|
|
},
|
|
"changeFileContent": true,
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.googleDrive",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
6528,
|
|
144
|
|
],
|
|
"id": "b9e4bd53-f993-42db-8fbd-e130872113b7",
|
|
"name": "Actualizar staging - Consolidada",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4' }}?fields=spreadsheetId,sheets(properties(title,sheetId,hidden))",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
6880,
|
|
144
|
|
],
|
|
"id": "612e0c20-72a0-4b9d-9ee3-15f76a82c191",
|
|
"name": "Listar hojas - Consolidada",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst target = 'planilla';\nconst spreadsheetId = $json.spreadsheetId || $json.id || '1za7ZdPAGsEbfktl5tuC6LbEZ8f7tiTCi_BszQeA-EO4';\nfunction norm(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase().replace(/[^a-z0-9]/g, ''); }\nconst wanted = { nomina: ['1nominageneral','nominageneral'], empleados: ['empleados'], planilla: ['planillamensual'] }[target];\nconst sheets = ($json.sheets || []).map(s => s.properties || {});\nlet match = null;\nfor (const w of wanted) { match = sheets.find(p => norm(p.title) === w || norm(p.title).includes(w)); if (match) break; }\nif (!match) throw new Error(`No encontré la hoja esperada para ${target}. Hojas disponibles: ${sheets.map(s => s.title).join(', ')}`);\nreturn [{ json: { runId: init.runId, spreadsheetId, sheetTitle: match.title, sheetId: match.sheetId, target } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
7072,
|
|
144
|
|
],
|
|
"id": "a7459273-64d3-4ca3-8966-e505f1f74bf2",
|
|
"name": "Resolver hoja PLANILLA MENSUAL"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + $json.spreadsheetId + '/values/' + encodeURIComponent(\"'\" + $json.sheetTitle.replace(/'/g, \"''\") + \"'!A:AZ\") + '?valueRenderOption=UNFORMATTED_VALUE&dateTimeRenderOption=SERIAL_NUMBER' }}",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
7248,
|
|
144
|
|
],
|
|
"id": "ce9dda4f-a310-4d15-aa02-832da8b04e65",
|
|
"name": "Leer PLANILLA MENSUAL",
|
|
"credentials": {
|
|
"googleSheetsOAuth2Api": {
|
|
"id": "K0hDZh3a85MpOHCs",
|
|
"name": "Google Sheets account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const init = $items('Inicializar memoria')[0].json;\nconst values = $json.values || [];\nfunction clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction normalizeName(s) { return clean(s).normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toUpperCase().replace(/[^A-Z0-9 ]/g, ' ').replace(/\\s+/g, ' ').trim(); }\nfunction toNumber(v) { if (v === null || v === undefined || v === '') return 0; if (typeof v === 'number') return Number.isFinite(v) ? v : 0; const n = Number(String(v).replace(/Q|\\$|USD|GTQ|,/gi, '').trim()); return Number.isFinite(n) ? n : 0; }\nfunction normalizeAf(v) { const raw = clean(v); if (!raw) return ''; let digits = ''; if (typeof v === 'number') digits = Math.trunc(v).toString(); else { const s = raw.replace(/,/g, '').trim(); if (/e\\+?/i.test(s)) { const n = Number(s); if (Number.isFinite(n)) digits = Math.trunc(n).toString(); } else digits = s.replace(/\\D/g, ''); } return digits && digits.length >= 8 && digits.length <= 13 && !/^0+$/.test(digits) ? digits : ''; }\nconst records = [];\nfor (let i = 0; i < values.length; i++) { const row = values[i] || []; const afiliacion = normalizeAf(row[1]); if (!afiliacion) continue; const nombre = clean(row[2]); if (!nombre || /^total/i.test(nombre)) continue; records.push({ afiliacion, nombre_planilla: nombre, nombre_norm: normalizeName(nombre), proyecto_planilla: clean(row[4]), monto_planilla_mensual: Number(toNumber(row[25]).toFixed(2)), liquido_planilla: Number(toNumber(row[23]).toFixed(2)) }); }\nconst by = new Map();\nfor (const r of records) { if (!by.has(r.afiliacion)) by.set(r.afiliacion, { ...r }); else { const ex = by.get(r.afiliacion); ex.monto_planilla_mensual = Number((ex.monto_planilla_mensual + r.monto_planilla_mensual).toFixed(2)); ex.liquido_planilla = Number((ex.liquido_planilla + r.liquido_planilla).toFixed(2)); } }\nconst finalRecords = [...by.values()];\nif (!finalRecords.length) throw new Error('No pude leer afiliaciones y monto desde PLANILLA MENSUAL.');\nconst data = $getWorkflowStaticData('global'); data.gtSeguridadSocial = data.gtSeguridadSocial || {}; data.gtSeguridadSocial[init.runId] = data.gtSeguridadSocial[init.runId] || {}; data.gtSeguridadSocial[init.runId].planillaMensual = finalRecords;\nreturn [{ json: { runId: init.runId, planillaRows: finalRecords.length } }];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
7424,
|
|
144
|
|
],
|
|
"id": "eb1a7fda-e46a-47c1-aaf6-c8a726353511",
|
|
"name": "Guardar PLANILLA MENSUAL en memoria"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "function clean(s) { return String(s ?? '').replace(/\\s+/g, ' ').trim(); }\nfunction money(n) { return Number((Number(n) || 0).toFixed(2)); }\nfunction blankCell(v) { return v === '' ? null : v; }\nfunction sanitizeRows(rows) { return (rows || []).map(row => (row || []).map(blankCell)); }\nfunction removeAccents(s) { return String(s || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, ''); }\nfunction normalizeName(s) {\n return clean(s)\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .toUpperCase()\n .replace(/[^A-Z0-9 ]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\nfunction tokens(name) {\n const stop = new Set(['DE','DEL','LA','LAS','LOS','Y','DA','DAS','DO','VDA']);\n return normalizeName(name).split(' ').filter(t => t.length > 1 && !stop.has(t));\n}\nfunction nameScore(a, b) {\n const A = tokens(a), B = tokens(b);\n if (!A.length || !B.length) return 0;\n const setB = new Set(B);\n const inter = A.filter(x => setB.has(x)).length;\n if (inter < 2) return 0;\n return inter / Math.min(A.length, B.length);\n}\nfunction bestByName(name, list, field) {\n const exact = list.find(r => normalizeName(r[field]) === normalizeName(name));\n if (exact) return { record: exact, score: 1, method: 'nombre exacto' };\n let best = { record: null, score: 0, method: '' };\n for (const r of list) {\n const sc = nameScore(name, r[field] || '');\n if (sc > best.score) best = { record: r, score: sc, method: 'nombre parecido' };\n }\n return best.score >= 0.70 ? best : { record: null, score: 0, method: '' };\n}\nfunction makeMap(records, key) {\n const m = new Map();\n for (const r of records || []) if (r[key]) m.set(String(r[key]), r);\n return m;\n}\nfunction uniqueJoin(arr) {\n return [...new Set((arr || []).filter(Boolean).map(clean))].join(' | ');\n}\nfunction fmt(n) {\n return Number(n || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n}\n\nconst init = $items('Inicializar memoria')[0].json;\nconst data = $getWorkflowStaticData('global');\nconst slot = data.gtSeguridadSocial?.[init.runId];\nif (!slot) throw new Error('No encontré la memoria temporal de esta ejecución.');\n\nconst payroll = slot.nomina || [];\nconst ajustesRaw = slot.ajustes || [];\nconst empleados = slot.empleados || [];\nconst planilla = slot.planillaMensual || [];\n\nif (!payroll.length || !empleados.length || !planilla.length) {\n throw new Error(`Faltan datos: nómina=${payroll.length}, empleados=${empleados.length}, planilla=${planilla.length}`);\n}\n\nconst empByAf = makeMap(empleados, 'afiliacion');\nconst planByAf = makeMap(planilla, 'afiliacion');\n\nconst resolvedRows = [];\nconst sinIgssRows = [];\n\nfor (const r of payroll) {\n let resolvedAf = r.igss_norm || '';\n let resolveMethod = resolvedAf ? 'Número de IGSS en nómina' : '';\n let corrected = false;\n\n if (!resolvedAf || (!empByAf.has(resolvedAf) && !planByAf.has(resolvedAf))) {\n const bestEmp = bestByName(r.nombre_nomina, empleados, 'nombre_contabilidad');\n if (bestEmp.record) {\n corrected = !!resolvedAf && resolvedAf !== bestEmp.record.afiliacion;\n resolvedAf = bestEmp.record.afiliacion;\n resolveMethod = `${bestEmp.method} en EMPLEADOS`;\n } else {\n const bestPlan = bestByName(r.nombre_nomina, planilla, 'nombre_planilla');\n if (bestPlan.record) {\n corrected = !!resolvedAf && resolvedAf !== bestPlan.record.afiliacion;\n resolvedAf = bestPlan.record.afiliacion;\n resolveMethod = `${bestPlan.method} en PLANILLA MENSUAL`;\n }\n }\n }\n\n if (!r.igss_norm || corrected) {\n sinIgssRows.push({\n ...r,\n resolvedAf,\n resolveMethod,\n corrected,\n accion: corrected ? 'Corregir IGSS en nómina' : 'Completar IGSS en nómina'\n });\n }\n\n resolvedRows.push({ ...r, resolvedAf, resolveMethod, corrected });\n}\n\n// Tipo de cambio para nóminas en dólar, inferido con casos donde el monto coincide contra PLANILLA MENSUAL.\nconst ratios = [];\nfor (const r of resolvedRows) {\n if (!/dolar|dólar|usd/i.test(r.moneda || '') || !r.resolvedAf) continue;\n const pl = planByAf.get(r.resolvedAf);\n if (!pl || !r.monto_afecto_original) continue;\n const ratio = pl.monto_planilla_mensual / r.monto_afecto_original;\n if (ratio >= 5 && ratio <= 10) ratios.push(ratio);\n}\nratios.sort((a,b)=>a-b);\nconst usdRate = ratios.length ? money(ratios[Math.floor(ratios.length/2)]) : 1;\n\nfor (const r of resolvedRows) {\n r.tipo_cambio_usd = /dolar|dólar|usd/i.test(r.moneda || '') ? usdRate : 1;\n r.monto_afecto_gtq = money(r.monto_afecto_original * r.tipo_cambio_usd);\n}\n\nconst groups = new Map();\nconst unresolvedRows = [];\n\nfor (const r of resolvedRows) {\n if (!r.resolvedAf) {\n unresolvedRows.push(r);\n continue;\n }\n\n if (!groups.has(r.resolvedAf)) {\n groups.set(r.resolvedAf, { afiliacion:r.resolvedAf, records:[], ajustes:[], monto:0, montoPrincipal:0, montoAjustes:0 });\n }\n const g = groups.get(r.resolvedAf);\n g.records.push(r);\n g.montoPrincipal = money(g.montoPrincipal + r.monto_afecto_gtq);\n g.monto = money(g.monto + r.monto_afecto_gtq);\n}\n\n// Resolver e incluir ajustes/complementos sin contaminar el cruce de afiliaciones.\nconst ajustesIncluidos = [];\nconst ajustesNoResueltos = [];\n\nfor (const a0 of ajustesRaw) {\n let resolvedAf = a0.igss_norm || '';\n let resolveMethod = resolvedAf ? 'Número de IGSS en ajuste' : '';\n\n if (!resolvedAf || (!empByAf.has(resolvedAf) && !planByAf.has(resolvedAf))) {\n const bestEmp = bestByName(a0.nombre_nomina, empleados, 'nombre_contabilidad');\n if (bestEmp.record) {\n resolvedAf = bestEmp.record.afiliacion;\n resolveMethod = `${bestEmp.method} en EMPLEADOS`;\n } else {\n const bestPlan = bestByName(a0.nombre_nomina, planilla, 'nombre_planilla');\n if (bestPlan.record) {\n resolvedAf = bestPlan.record.afiliacion;\n resolveMethod = `${bestPlan.method} en PLANILLA MENSUAL`;\n }\n }\n }\n\n const rate = /dolar|dólar|usd/i.test(a0.moneda || '') ? usdRate : 1;\n const montoGtq = money((a0.monto_ajuste_original || 0) * rate);\n const ajuste = { ...a0, resolvedAf, resolveMethod, monto_ajuste_gtq: montoGtq, tipo_cambio_usd: rate };\n\n if (!resolvedAf) {\n ajustesNoResueltos.push(ajuste);\n continue;\n }\n\n if (!groups.has(resolvedAf)) {\n groups.set(resolvedAf, { afiliacion:resolvedAf, records:[], ajustes:[], monto:0, montoPrincipal:0, montoAjustes:0 });\n }\n const g = groups.get(resolvedAf);\n g.ajustes.push(ajuste);\n g.montoAjustes = money(g.montoAjustes + montoGtq);\n g.monto = money(g.monto + montoGtq);\n ajustesIncluidos.push(ajuste);\n}\n\nconst matchedEmpAf = new Set([...groups.keys()].filter(af => empByAf.has(af)));\nconst matchedPlanAf = new Set([...groups.keys()].filter(af => planByAf.has(af)));\n\nconst soloContabilidad = empleados.filter(e => !matchedEmpAf.has(e.afiliacion));\nconst soloNomina = [...groups.values()].filter(g => !empByAf.has(g.afiliacion));\nconst afiliacionDiferencias = soloContabilidad.length + soloNomina.length;\n\n// Pestaña 1: Cruce Afiliaciones\nconst afiliacionRows = [];\nafiliacionRows.push(['Cruce de afiliaciones - Guatemala']);\nafiliacionRows.push([`Período: ${init.periodoLabel}`]);\nafiliacionRows.push(['Resultado', afiliacionDiferencias ? 'REVISAR DIFERENCIAS' : 'SIN DIFERENCIAS']);\nafiliacionRows.push(['']);\nafiliacionRows.push(['Resumen', 'Cantidad']);\nafiliacionRows.push(['Registros de nómina revisados (Q1 + Q2)', payroll.length]);\nafiliacionRows.push(['Empleados únicos en nómina', groups.size]);\nafiliacionRows.push(['Empleados en contabilidad', empleados.length]);\nafiliacionRows.push(['Diferencias encontradas', afiliacionDiferencias]);\nafiliacionRows.push(['']);\nafiliacionRows.push(['Lectura rápida']);\nafiliacionRows.push([afiliacionDiferencias ? '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 contabilidad.']);\nafiliacionRows.push(['']);\nafiliacionRows.push(['No se muestran los empleados que coinciden correctamente.']);\nafiliacionRows.push(['']);\nafiliacionRows.push(['Empleados en contabilidad que NO aparecen en nómina']);\nafiliacionRows.push(['Afiliación', 'Nombre completo', 'Qué significa']);\nif (soloContabilidad.length) {\n for (const e of soloContabilidad) afiliacionRows.push([e.afiliacion, e.nombre_contabilidad, 'Está en contabilidad, pero no aparece en las nóminas Q1/Q2.']);\n} else {\n afiliacionRows.push(['Sin diferencias', '', '']);\n}\nafiliacionRows.push(['']);\nafiliacionRows.push(['Empleados en nómina que NO aparecen en contabilidad']);\nafiliacionRows.push(['Afiliación / IGSS', 'Nombre completo', 'Quincena(s)', 'Proyecto(s)', 'Qué significa']);\nif (soloNomina.length) {\n for (const g of soloNomina) afiliacionRows.push([g.afiliacion, uniqueJoin(g.records.map(r=>r.nombre_nomina)), uniqueJoin(g.records.map(r=>r.q)), uniqueJoin(g.records.map(r=>r.proyecto_nomina)), 'Está en nómina, pero no existe en EMPLEADOS de contabilidad.']);\n} else {\n afiliacionRows.push(['Sin diferencias', '', '', '', '']);\n}\n\n// Pestaña 2: Sin IGSS en nómina\nconst sinIgssSheetRows = [];\nsinIgssSheetRows.push(['Sin IGSS en nómina']);\nsinIgssSheetRows.push([`Período: ${init.periodoLabel}`]);\nsinIgssSheetRows.push(['Resultado', sinIgssRows.length ? 'REGISTROS PARA COMPLETAR / CORREGIR' : 'SIN REGISTROS']);\nsinIgssSheetRows.push(['']);\nsinIgssSheetRows.push(['Resumen', 'Cantidad']);\nsinIgssSheetRows.push(['Registros con IGSS pendiente/vacío o a corregir', sinIgssRows.length]);\nsinIgssSheetRows.push(['']);\nsinIgssSheetRows.push(['Lectura rápida']);\nsinIgssSheetRows.push([sinIgssRows.length ? 'Estos empleados existen en contabilidad, pero la columna IGSS de la nómina debe completarse o corregirse.' : 'No hay registros de nómina con IGSS pendiente/vacío o a corregir.']);\nsinIgssSheetRows.push(['']);\nsinIgssSheetRows.push(['Quincena', 'Fila nómina', 'Nombre completo', 'Proyecto', 'IGSS en nómina', 'Afiliación correcta', 'Acción sugerida']);\nif (sinIgssRows.length) {\n for (const r of sinIgssRows.sort((a,b) => String(a.q).localeCompare(String(b.q)) || Number(a.rowNumber||0)-Number(b.rowNumber||0))) {\n sinIgssSheetRows.push([\n r.q,\n r.rowNumber,\n r.nombre_nomina,\n r.proyecto_nomina,\n r.igss_original || '',\n r.resolvedAf || '',\n r.accion\n ]);\n }\n} else {\n sinIgssSheetRows.push(['Sin registros', '', '', '', '', '', '']);\n}\n\n// Cruce de montos después de ajustes\nconst montoFindings = [];\nconst noComparados = [];\nlet montoOk = 0;\n\nfor (const [af,g] of groups) {\n const pl = planByAf.get(af);\n if (!pl) {\n noComparados.push({estado:'Solo en nóminas',af,g,pl:null,obs:'No existe en PLANILLA MENSUAL.'});\n continue;\n }\n const diff = money(g.monto - pl.monto_planilla_mensual);\n if (Math.abs(diff) <= 0.05) {\n montoOk++;\n } else {\n montoFindings.push({af,g,pl,diff});\n }\n}\nfor (const pl of planilla) {\n if (!matchedPlanAf.has(pl.afiliacion)) {\n noComparados.push({estado:'Solo en PLANILLA MENSUAL',af:pl.afiliacion,g:null,pl,obs:'Existe en PLANILLA MENSUAL, pero no se relacionó con las nóminas Q1/Q2.'});\n }\n}\nfor (const r of unresolvedRows) {\n noComparados.push({estado:'IGSS sin resolver',af:'',g:{records:[r],monto:r.monto_afecto_gtq},pl:null,obs:'No se pudo resolver afiliación por número ni por nombre.'});\n}\nfor (const a of ajustesNoResueltos) {\n noComparados.push({estado:'Ajuste sin resolver',af:'',g:{records:[a],monto:a.monto_ajuste_gtq},pl:null,obs:'No se pudo relacionar el ajuste/complemento por IGSS ni por nombre.'});\n}\nmontoFindings.sort((a,b)=>Math.abs(b.diff)-Math.abs(a.diff));\nconst montoResultado = (montoFindings.length || noComparados.length) ? 'REVISAR DIFERENCIAS' : 'SIN DIFERENCIAS';\n\n// Pestaña 3: Cruce Montos\nconst montosRows = [];\nmontosRows.push(['Cruce de montos afectos - Guatemala']);\nmontosRows.push([`Período: ${init.periodoLabel}`]);\nmontosRows.push(['Resultado', montoResultado]);\nmontosRows.push([`Tipo de cambio USD usado: ${usdRate === 1 ? 'No aplicado' : usdRate}`]);\nmontosRows.push(['']);\nmontosRows.push(['Resumen', 'Cantidad']);\nmontosRows.push(['Afiliaciones comparadas con monto correcto', montoOk]);\nmontosRows.push(['Diferencias reales de monto', montoFindings.length]);\nmontosRows.push(['Casos no comparados', noComparados.length]);\nmontosRows.push(['Ajustes/complementos incluidos', ajustesIncluidos.length]);\nmontosRows.push(['']);\nmontosRows.push(['Lectura rápida']);\nmontosRows.push([montoResultado === 'SIN DIFERENCIAS' ? 'No hay diferencias de monto después de sumar nómina Q1, nómina Q2 y ajustes/complementos.' : 'Hay diferencias o casos no comparados. Revisar las secciones de abajo.']);\nmontosRows.push(['']);\nmontosRows.push(['No se muestran los empleados que cuadran correctamente.']);\nmontosRows.push(['']);\nmontosRows.push(['Diferencias reales de monto']);\nmontosRows.push(['Afiliación', 'Nombre completo', 'Proyecto', 'Monto nómina Q1+Q2 + ajustes', 'Monto planilla mensual', 'Diferencia', 'Detalle', 'Qué revisar']);\nif (montoFindings.length) {\n for (const f of montoFindings) {\n const details = [\n ...f.g.records.map(r => `${r.q}: ${fmt(r.monto_afecto_gtq)}${r.tipo_cambio_usd !== 1 ? ' GTQ (USD convertido)' : ''}`),\n ...f.g.ajustes.map(a => `Ajuste ${a.q}/${a.sheetTitle}: ${fmt(a.monto_ajuste_gtq)}`)\n ].join(' | ');\n montosRows.push([\n f.af,\n f.pl?.nombre_planilla || uniqueJoin(f.g.records.map(r=>r.nombre_nomina)),\n f.pl?.proyecto_planilla || uniqueJoin(f.g.records.map(r=>r.proyecto_nomina)),\n f.g.monto,\n f.pl.monto_planilla_mensual,\n f.diff,\n details,\n 'Revisar diferencia entre nómina combinada y PLANILLA MENSUAL.'\n ]);\n }\n} else {\n montosRows.push(['Sin diferencias', '', '', '', '', '', '', '']);\n}\nmontosRows.push(['']);\nmontosRows.push(['Casos no comparados']);\nmontosRows.push(['Estado','Afiliación','Nombre completo','Monto nómina / ajuste','Monto planilla mensual','Qué significa']);\nif (noComparados.length) {\n for (const f of noComparados) {\n montosRows.push([\n f.estado,\n f.af,\n f.pl?.nombre_planilla || uniqueJoin((f.g?.records || []).map(r=>r.nombre_nomina)),\n f.g?.monto || '',\n f.pl?.monto_planilla_mensual || '',\n f.obs\n ]);\n }\n} else {\n montosRows.push(['Sin casos', '', '', '', '', '']);\n}\n\n// Pestaña 4: Ajustes incluidos\nconst ajustesRows = [];\najustesRows.push(['Ajustes incluidos']);\najustesRows.push([`Período: ${init.periodoLabel}`]);\najustesRows.push(['Resultado', ajustesIncluidos.length ? `${ajustesIncluidos.length} ajuste(s) incluidos en el cruce de montos` : 'Sin ajustes incluidos']);\najustesRows.push(['']);\najustesRows.push(['Lectura rápida']);\najustesRows.push([ajustesIncluidos.length ? 'Estos ajustes/complementos se sumaron para comparar correctamente contra PLANILLA MENSUAL.' : 'No se encontraron ajustes/complementos para sumar al cruce de montos.']);\najustesRows.push(['']);\najustesRows.push(['Quincena', 'Hoja origen', 'Fila', 'Afiliación', 'Nombre completo', 'Proyecto', 'Monto ajuste', 'Tipo', 'Observación']);\nif (ajustesIncluidos.length) {\n for (const a of ajustesIncluidos.sort((a,b) => String(a.q).localeCompare(String(b.q)) || String(a.sheetTitle).localeCompare(String(b.sheetTitle)) || Number(a.rowNumber||0)-Number(b.rowNumber||0))) {\n ajustesRows.push([\n a.q,\n a.sheetTitle,\n a.rowNumber,\n a.resolvedAf,\n a.nombre_nomina,\n a.proyecto_nomina,\n a.monto_ajuste_gtq,\n a.tipo_ajuste,\n a.observacion_ajuste\n ]);\n }\n} else {\n ajustesRows.push(['Sin ajustes', '', '', '', '', '', '', '', '']);\n}\n\nconst q1Count = payroll.filter(r=>r.q==='1Q').length;\nconst q2Count = payroll.filter(r=>r.q==='2Q').length;\n\nconst summary = {\n periodo:init.periodoLabel,\n q1Rows:q1Count,\n q2Rows:q2Count,\n empleados:empleados.length,\n planilla:planilla.length,\n empleadosUnicosNomina:groups.size,\n afiliacionSoloContabilidad:soloContabilidad.length,\n afiliacionSoloNomina:soloNomina.length,\n afiliacionDiferencias,\n sinIgss:sinIgssRows.length,\n montoOk,\n montoFindings:montoFindings.length,\n noComparados:noComparados.length,\n ajustesIncluidos:ajustesIncluidos.length,\n usdRate,\n files:slot.files || {}\n};\n\ndelete data.gtSeguridadSocial[init.runId];\n\nreturn [{\n json: {\n runId:init.runId,\n reportTitle:init.reportTitle,\n emailTo:init.emailTo || 'iaracena@gomezleemarketing.com',\n afiliacionRows: sanitizeRows(afiliacionRows),\n sinIgssRows: sanitizeRows(sinIgssSheetRows),\n montosRows: sanitizeRows(montosRows),\n ajustesRows: sanitizeRows(ajustesRows),\n summary,\n rowCounts:{\n afiliacion:afiliacionRows.length,\n sinIgss:sinIgssSheetRows.length,\n montos:montosRows.length,\n ajustes:ajustesRows.length\n }\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
7888,
|
|
144
|
|
],
|
|
"id": "d1e21e26-ece2-4322-a1ad-fcedb6a934ab",
|
|
"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 Afiliaciones' } }, { properties: { title: 'Sin IGSS en nómina' } }, { properties: { title: 'Cruce Montos' } }, { properties: { title: 'Ajustes incluidos' } } ] }) }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
8128,
|
|
144
|
|
],
|
|
"id": "a5f21136-97b7-4aff-ae35-3c71c7efb03e",
|
|
"name": "Crear Google Sheet 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 }}/values:batchUpdate",
|
|
"authentication": "predefinedCredentialType",
|
|
"nodeCredentialType": "googleSheetsOAuth2Api",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ JSON.stringify({ data: [ { range: 'Cruce Afiliaciones!A1', majorDimension: 'ROWS', values: $('Construir reporte producción').item.json.afiliacionRows }, { range: 'Sin IGSS en nómina!A1', majorDimension: 'ROWS', values: $('Construir reporte producción').item.json.sinIgssRows }, { range: 'Cruce Montos!A1', majorDimension: 'ROWS', values: $('Construir reporte producción').item.json.montosRows }, { range: 'Ajustes incluidos!A1', majorDimension: 'ROWS', values: $('Construir reporte producción').item.json.ajustesRows } ], valueInputOption: 'RAW' }) }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
8368,
|
|
144
|
|
],
|
|
"id": "5c5afcdb-1228-4817-9cb9-b203c2ed6835",
|
|
"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": "={{ JSON.stringify({ requests: [\n { autoResizeDimensions: { dimensions: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[0].properties.sheetId, dimension: 'COLUMNS', startIndex: 0, endIndex: 12 } } },\n { autoResizeDimensions: { dimensions: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[1].properties.sheetId, dimension: 'COLUMNS', startIndex: 0, endIndex: 12 } } },\n { autoResizeDimensions: { dimensions: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[2].properties.sheetId, dimension: 'COLUMNS', startIndex: 0, endIndex: 12 } } },\n { autoResizeDimensions: { dimensions: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[3].properties.sheetId, dimension: 'COLUMNS', startIndex: 0, endIndex: 12 } } },\n { updateSheetProperties: { properties: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[0].properties.sheetId, gridProperties: { frozenRowCount: 1 } }, fields: 'gridProperties.frozenRowCount' } },\n { updateSheetProperties: { properties: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[1].properties.sheetId, gridProperties: { frozenRowCount: 1 } }, fields: 'gridProperties.frozenRowCount' } },\n { updateSheetProperties: { properties: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[2].properties.sheetId, gridProperties: { frozenRowCount: 1 } }, fields: 'gridProperties.frozenRowCount' } },\n { updateSheetProperties: { properties: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[3].properties.sheetId, gridProperties: { frozenRowCount: 1 } }, fields: 'gridProperties.frozenRowCount' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[0].properties.sheetId, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 0.91, green: 0.96, blue: 0.90 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[1].properties.sheetId, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 1.00, green: 0.96, blue: 0.80 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[2].properties.sheetId, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 0.91, green: 0.96, blue: 0.90 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[3].properties.sheetId, startRowIndex: 0, endRowIndex: 1 }, cell: { userEnteredFormat: { textFormat: { bold: true, fontSize: 14 }, backgroundColor: { red: 0.93, green: 0.94, blue: 0.96 } } }, fields: 'userEnteredFormat.textFormat,userEnteredFormat.backgroundColor' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[0].properties.sheetId }, cell: { userEnteredFormat: { wrapStrategy: 'WRAP' } }, fields: 'userEnteredFormat.wrapStrategy' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[1].properties.sheetId }, cell: { userEnteredFormat: { wrapStrategy: 'WRAP' } }, fields: 'userEnteredFormat.wrapStrategy' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[2].properties.sheetId }, cell: { userEnteredFormat: { wrapStrategy: 'WRAP' } }, fields: 'userEnteredFormat.wrapStrategy' } },\n { repeatCell: { range: { sheetId: $('Crear Google Sheet Reporte').item.json.sheets[3].properties.sheetId }, cell: { userEnteredFormat: { wrapStrategy: 'WRAP' } }, fields: 'userEnteredFormat.wrapStrategy' } }\n] }) }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.3,
|
|
"position": [
|
|
8608,
|
|
144
|
|
],
|
|
"id": "c5cc0f8d-e1dd-4f79-9c6d-4724e46e1474",
|
|
"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": [
|
|
8848,
|
|
144
|
|
],
|
|
"id": "187f0813-12b8-4a0c-95af-05357f682885",
|
|
"name": "Compartir con Isaac",
|
|
"credentials": {
|
|
"googleDriveOAuth2Api": {
|
|
"id": "g23xdGLZRzBGqKgH",
|
|
"name": "Isaac - Google Drive"
|
|
}
|
|
},
|
|
"onError": "continueRegularOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"sendTo": "iaracena@gomezleemarketing.com, ymadera@gomezleemarketing.com",
|
|
"subject": "={{ 'Cruce Seguridad Social Guatemala - ' + ($('Construir reporte producción').item.json.summary.periodo || '') }}",
|
|
"message": "={{ (() => {\nconst s = $('Construir reporte producción').item.json.summary || {};\nconst url = $('Crear Google Sheet Reporte').item.json.spreadsheetUrl || '#';\nconst afiliacionDiff = Number(s.afiliacionDiferencias || 0);\nconst montoDiff = Number(s.montoFindings || 0);\nconst noComparados = Number(s.noComparados || 0);\nconst sinIgss = Number(s.sinIgss || 0);\nconst ajustes = Number(s.ajustesIncluidos || 0);\nconst hardDiff = afiliacionDiff + montoDiff + noComparados;\nconst status = hardDiff ? 'REQUIERE REVISIÓN' : (sinIgss ? 'COMPLETADO · DATOS A CORREGIR' : 'COMPLETADO · SIN DIFERENCIAS');\nconst statusBg = hardDiff ? '#fff7ed' : (sinIgss ? '#fffbeb' : '#f0fdf4');\nconst statusBorder = hardDiff ? '#fed7aa' : (sinIgss ? '#fde68a' : '#bbf7d0');\nconst statusColor = hardDiff ? '#9a3412' : (sinIgss ? '#92400e' : '#166534');\nreturn `<div style=\"font-family:Arial,Helvetica,sans-serif;background:#f6f8fb;padding:0;margin:0\">\n <div style=\"max-width:720px;margin:0 auto;background:#ffffff;border-radius:14px;overflow:hidden;border:1px solid #e3e8ef\">\n <div style=\"border-top:5px solid #58bd45;padding:30px 34px 20px\">\n <img src=\"data:image/png;base64,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\" alt=\"GomezLee Marketing\" style=\"width:210px;max-width:70%;height:auto;display:block;margin-bottom:28px\" />\n <div style=\"margin-top:8px;color:#58bd45;font-weight:700;font-size:12px;letter-spacing:2px\">SEGURIDAD SOCIAL · GUATEMALA</div>\n <h1 style=\"margin:8px 0 8px;color:#4f7185;font-size:26px\">Cruce mensual completado</h1>\n <p style=\"margin:0;color:#5f7f92\">${s.periodo || ''} · Nóminas Q1 + Q2</p>\n </div>\n <div style=\"padding:26px 34px\">\n <div style=\"padding:16px;border-radius:12px;background:${statusBg};border:1px solid ${statusBorder};color:${statusColor};font-weight:700;margin-bottom:18px\">${status}</div>\n <div style=\"display:flex;gap:14px;flex-wrap:wrap\">\n <div style=\"flex:1;min-width:150px;border:1px solid #d8e0e7;border-top:3px solid #4f7185;border-radius:12px;padding:16px\">\n <div style=\"font-size:11px;color:#6d8da1;font-weight:700;letter-spacing:1px\">NÓMINA Q1</div>\n <div style=\"font-size:28px;color:#4f7185;font-weight:700\">${s.q1Rows||0}</div>\n </div>\n <div style=\"flex:1;min-width:150px;border:1px solid #d8e0e7;border-top:3px solid #4f7185;border-radius:12px;padding:16px\">\n <div style=\"font-size:11px;color:#6d8da1;font-weight:700;letter-spacing:1px\">NÓMINA Q2</div>\n <div style=\"font-size:28px;color:#4f7185;font-weight:700\">${s.q2Rows||0}</div>\n </div>\n <div style=\"flex:1;min-width:150px;border:1px solid #d8e0e7;border-top:3px solid ${hardDiff ? '#ff8a00' : '#58bd45'};border-radius:12px;padding:16px\">\n <div style=\"font-size:11px;color:#6d8da1;font-weight:700;letter-spacing:1px\">HALLAZGOS A REVISAR</div>\n <div style=\"font-size:28px;color:#4f7185;font-weight:700\">${hardDiff}</div>\n </div>\n </div>\n <div style=\"display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:22px\">\n <div style=\"border:1px solid #d8e0e7;border-radius:12px;padding:16px\">\n <h3 style=\"margin:0 0 12px;color:#4f7185;font-size:16px\">Afiliaciones</h3>\n <p style=\"margin:8px 0;color:#333\">Resultado <strong style=\"float:right;color:#4f7185\">${afiliacionDiff ? 'Revisar' : 'Sin diferencias'}</strong></p>\n <p style=\"margin:8px 0;color:#333\">Empleados únicos nómina <strong style=\"float:right;color:#4f7185\">${s.empleadosUnicosNomina||0}</strong></p>\n <p style=\"margin:8px 0;color:#333\">Sin IGSS en nómina <strong style=\"float:right;color:#4f7185\">${sinIgss}</strong></p>\n </div>\n <div style=\"border:1px solid #d8e0e7;border-radius:12px;padding:16px\">\n <h3 style=\"margin:0 0 12px;color:#4f7185;font-size:16px\">Montos afectos</h3>\n <p style=\"margin:8px 0;color:#333\">Diferencias reales <strong style=\"float:right;color:#4f7185\">${montoDiff}</strong></p>\n <p style=\"margin:8px 0;color:#333\">Casos no comparados <strong style=\"float:right;color:#4f7185\">${noComparados}</strong></p>\n <p style=\"margin:8px 0;color:#333\">Ajustes incluidos <strong style=\"float:right;color:#4f7185\">${ajustes}</strong></p>\n </div>\n </div>\n <div style=\"margin-top:24px\">\n <a href=\"${url}\" style=\"display:inline-block;background:#58bd45;color:white;text-decoration:none;border-radius:24px;padding:13px 22px;font-weight:700\">Abrir reporte en Google Sheets</a>\n </div>\n <div style=\"margin-top:22px;color:#6d8da1;font-size:12px;line-height:1.5\">\n <strong>Archivos procesados</strong><br>\n Nómina Q1: ${s.files?.q1||''}<br>\n Nómina Q2: ${s.files?.q2||''}<br>\n Sistema Propio IGSS: ${s.files?.sistema||''}<br>\n Planilla Consolidada: ${s.files?.consolidado||''}\n </div>\n </div>\n </div>\n</div>`;\n})() }}",
|
|
"options": {
|
|
"appendAttribution": false
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.gmail",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
9088,
|
|
144
|
|
],
|
|
"id": "b8470f8f-893b-485c-9924-bc41bb2e2673",
|
|
"name": "Enviar correo",
|
|
"webhookId": "a63f6b49-1bde-44a6-b1a6-6fc2af3f92b5",
|
|
"credentials": {
|
|
"gmailOAuth2": {
|
|
"id": "UDcO1FLJqA453V2D",
|
|
"name": "Gmail account 3"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## Inicio de Flujo\nRecibir adjuntos a través de formulario y preparación de nóminas",
|
|
"height": 320,
|
|
"width": 4512,
|
|
"color": 5
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
0,
|
|
0
|
|
],
|
|
"id": "dc202408-379b-4732-926d-1e2024b34ed6",
|
|
"name": "Sticky Note"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## Preparar archivos de contadores\n\nPreparar archivos Sistema Propio y Planilla Consolidada",
|
|
"height": 320,
|
|
"width": 2880
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
4736,
|
|
16
|
|
],
|
|
"id": "582d07c3-5cbd-427d-8b70-7daefc5847a8",
|
|
"name": "Sticky Note1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## Preparar reporte y enviar correo\n\nConstruir reporte y prepararlo en el formato adecuado. Preparar correo para envío.",
|
|
"height": 288,
|
|
"width": 1488,
|
|
"color": 4
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
7824,
|
|
32
|
|
],
|
|
"id": "ed673fb9-b847-4e54-88df-000a65506a07",
|
|
"name": "Sticky Note2"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"amount": 8
|
|
},
|
|
"type": "n8n-nodes-base.wait",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
1280,
|
|
128
|
|
],
|
|
"id": "455d6a6c-0687-4d16-9113-362b2d6de105",
|
|
"name": "Esperar conversión - Nómina 1Q",
|
|
"webhookId": "wait-esperar-conversi-n-n-mina-1q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"amount": 8
|
|
},
|
|
"type": "n8n-nodes-base.wait",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
3024,
|
|
128
|
|
],
|
|
"id": "628c2647-736e-40b7-ac16-70c27f588452",
|
|
"name": "Esperar conversión - Nómina 2Q",
|
|
"webhookId": "wait-esperar-conversi-n-n-mina-2q"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"amount": 8
|
|
},
|
|
"type": "n8n-nodes-base.wait",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
5232,
|
|
144
|
|
],
|
|
"id": "d6491600-f373-4b35-9d53-d91817dbfe03",
|
|
"name": "Esperar conversión - Sistema Propio",
|
|
"webhookId": "wait-esperar-conversi-n-sistema-propio"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"amount": 8
|
|
},
|
|
"type": "n8n-nodes-base.wait",
|
|
"typeVersion": 1.1,
|
|
"position": [
|
|
6720,
|
|
144
|
|
],
|
|
"id": "eabb4451-f993-4410-8e36-bb9bb547e89e",
|
|
"name": "Esperar conversión - Consolidada",
|
|
"webhookId": "wait-esperar-conversi-n-consolidada"
|
|
}
|
|
],
|
|
"connections": {
|
|
"On form submission": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Inicializar memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Inicializar memoria": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Actualizar staging - Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Actualizar staging - Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Esperar conversión - Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Listar hojas - Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Resolver hoja Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Resolver hoja Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Leer Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Leer Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Guardar Nómina 1Q en memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Guardar Nómina 1Q en memoria": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Actualizar staging - Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Actualizar staging - Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Esperar conversión - Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Listar hojas - Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Resolver hoja Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Resolver hoja Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Leer Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Leer Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Guardar Nómina 2Q en memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Guardar Nómina 2Q en memoria": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar Sistema Propio",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar Sistema Propio": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Actualizar staging - Sistema Propio",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Actualizar staging - Sistema Propio": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Esperar conversión - Sistema Propio",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Listar hojas - Sistema Propio": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Resolver hoja EMPLEADOS",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Resolver hoja EMPLEADOS": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Leer EMPLEADOS",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Leer EMPLEADOS": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Guardar EMPLEADOS en memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Guardar EMPLEADOS en memoria": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar Planilla Consolidada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar Planilla Consolidada": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Actualizar staging - Consolidada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Actualizar staging - Consolidada": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Esperar conversión - Consolidada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Listar hojas - Consolidada": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Resolver hoja PLANILLA MENSUAL",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Resolver hoja PLANILLA MENSUAL": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Leer PLANILLA MENSUAL",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Leer PLANILLA MENSUAL": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Guardar PLANILLA MENSUAL en memoria",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Guardar PLANILLA MENSUAL en memoria": {
|
|
"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
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Esperar conversión - Nómina 1Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Listar hojas - Nómina 1Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Esperar conversión - Nómina 2Q": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Listar hojas - Nómina 2Q",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Esperar conversión - Sistema Propio": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Listar hojas - Sistema Propio",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Esperar conversión - Consolidada": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Listar hojas - Consolidada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"authors": "Isaac Aracena",
|
|
"name": "Version b63c48cf",
|
|
"description": "",
|
|
"autosaved": true,
|
|
"workflowPublishHistory": [
|
|
{
|
|
"createdAt": "2026-07-04T14:19:01.207Z",
|
|
"id": 2448,
|
|
"workflowId": "loFncgFuvBlXUf8u",
|
|
"versionId": "b63c48cf-a6f0-4386-b237-407a02a00696",
|
|
"event": "activated",
|
|
"userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
|
|
}
|
|
]
|
|
}
|
|
} |