{ "updatedAt": "2026-06-05T19:23:24.939Z", "createdAt": "2026-05-19T19:59:05.795Z", "id": "l8koc0x9iIFqKn7P", "name": "Nomina Panama V2", "description": null, "active": true, "isArchived": false, "nodes": [ { "parameters": {}, "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ -256, 416 ], "id": "f91d41a1-5eb4-4810-a7f8-f7d8cbdb7117", "name": "Inicio Manual" }, { "parameters": { "operation": "clear", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": 389127788, "mode": "list", "cachedResultName": "DETALLE_EXTRAIDO", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=389127788" }, "clear": "specificRange", "range": "A2:ZZ50000" }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -192, 0 ], "id": "cfd6f04d-efce-46e6-89a1-7bbde07cbb1f", "name": "Limpiar DETALLE_EXTRAIDO", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "clear", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": 1863286680, "mode": "list", "cachedResultName": "REVISION", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=1863286680" }, "clear": "specificRange", "range": "A2:ZZ50000" }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 16, 0 ], "id": "024610d8-5d02-4d57-898d-5317ede98081", "name": "Limpiar REVISION", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "clear", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": "gid=0", "mode": "list", "cachedResultName": "RESUMEN_2025", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=0" }, "clear": "specificRange", "range": "A2:ZZ50000" }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 224, 0 ], "id": "ba1ba8a2-5211-4ffe-a261-f93e10c8fdc4", "name": "Limpiar RESUMEN_2025", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "assignments": { "assignments": [ { "id": "209f180b-e0f6-4e3d-8005-c451a6be87c3", "name": "folder_2025_id", "value": "1KceYMLySpA9bbVnitzl5Y5YBBsLM3Dlq", "type": "string" }, { "id": "bdf727db-d037-429f-adfd-a15a0622599f", "name": "spreadsheet_id", "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "type": "string" } ] }, "options": {} }, "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ 448, 0 ], "id": "4d745e8f-adfd-4a70-84a7-5e03ea9e2cbb", "name": "Config" }, { "parameters": { "resource": "fileFolder", "searchMethod": "query", "queryString": "='1KceYMLySpA9bbVnitzl5Y5YBBsLM3Dlq' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false", "returnAll": true, "filter": {}, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 656, 0 ], "id": "b95a2dcd-a070-4de4-8373-265892f8a1f1", "name": "Listar meses 2025", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "resource": "fileFolder", "searchMethod": "query", "queryString": "={{ \"'\" + $json.id + \"' in parents and mimeType = 'application/pdf' and trashed = false\" }}", "returnAll": true, "filter": {}, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 1072, 0 ], "id": "39fc6525-d4eb-4937-af9f-6b5c495fd32f", "name": "Listar PDFs", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "resource": "fileFolder", "searchMethod": "query", "queryString": "='{{$json.id}}' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false", "returnAll": true, "filter": {}, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 864, 0 ], "id": "6fdde348-bc72-417e-99f6-4780bb3560c3", "name": "Listar quincenas", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "assignments": { "assignments": [ { "id": "1f256065-3538-495e-8515-e75c0af3c89c", "name": "pdf_id", "value": "={{$json.id}}", "type": "string" }, { "id": "293459ea-7b1f-443a-8aed-1126295deea8", "name": "pdf_name", "value": "={{$json.name}}", "type": "string" }, { "id": "90f1da7c-2bf8-4e79-86fe-b635e0c7edb5", "name": "pdf_mimeType", "value": "={{$json.mimeType}}", "type": "string" } ] }, "options": {} }, "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ 1280, 0 ], "id": "9f651201-76d5-41ea-ac42-8d2b137163af", "name": "Metadata PDF" }, { "parameters": { "operation": "download", "fileId": { "__rl": true, "value": "={{$json.pdf_id}}", "mode": "id" }, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 1488, 0 ], "id": "8ea393e6-179b-4348-a864-1ef2ae1d79f2", "name": "Descargar PDF", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "operation": "pdf", "options": {} }, "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ 1680, 0 ], "id": "c97cf3b9-ae31-4a46-9511-48a2d0e58a21", "name": "Extraer texto PDF" }, { "parameters": { "jsCode": "const COLS = [\n \"sal_por_hora\",\n \"horas_reg\",\n \"horas_1_25\",\n \"horas_1_50\",\n \"horas_1_75\",\n \"horas_2_625\",\n \"horas_aus_tard\",\n \"horas_cm\",\n \"ajuste_no_pagas\",\n \"horas_ajuste\",\n \"salario_regular\",\n \"salario_comision\",\n \"sobretiempo\",\n \"otros_ingresos\",\n \"salario_bruto\",\n \"gto_rep\",\n \"ss\",\n \"se\",\n \"isr\",\n \"isr_salarios\",\n \"otras_deduc\",\n \"total_desc\",\n \"total_neto\",\n \"salarios_especie\",\n];\n\nconst NUMBER_ANY_RE = /-?\\d{1,3}(?:,\\d{3})*\\.\\d{2,6}|-?\\d+\\.\\d{2,6}/g;\nconst NAME_RE = /[A-Z脕脡脥脫脷脺脩][A-Z脕脡脥脫脷脺脩 .'\\-]+,\\s*[A-Z脕脡脥脫脷脺脩][A-Z脕脡脥脫脷脺脩 .'\\-]+/gu;\n\nfunction toNumber(value) {\n const n = Number(String(value || \"0\").replace(/,/g, \"\"));\n return Number.isFinite(n) ? n : 0;\n}\n\nfunction round2(n) {\n return Math.round((Number(n || 0) + Number.EPSILON) * 100) / 100;\n}\n\nfunction normalizeName(name) {\n return String(name || \"\")\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toUpperCase()\n .replace(/[^A-Z脩, ]/g, \" \")\n .replace(/,/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction pickPdfText(json) {\n for (const key of [\"text\", \"data\", \"content\", \"extractedText\", \"pdfText\"]) {\n if (typeof json[key] === \"string\" && json[key].includes(\"Planilla\")) {\n return json[key];\n }\n }\n\n for (const value of Object.values(json)) {\n if (typeof value === \"string\" && value.includes(\"Planilla\")) {\n return value;\n }\n }\n\n return \"\";\n}\n\nfunction parsePeriodo(periodo) {\n const result = {\n periodo_inicio: \"\",\n periodo_fin: \"\",\n anio: \"\",\n mes_numero: \"\",\n mes_nombre: \"\",\n quincena: \"\",\n };\n\n const match = String(periodo || \"\").match(\n /(\\d{2})\\/(\\d{2})\\/(\\d{4}).*?(\\d{2})\\/(\\d{2})\\/(\\d{4})/\n );\n\n if (!match) return result;\n\n const [, d1, m1, y1, d2, m2, y2] = match;\n\n const meses = {\n \"01\": \"Enero\",\n \"02\": \"Febrero\",\n \"03\": \"Marzo\",\n \"04\": \"Abril\",\n \"05\": \"Mayo\",\n \"06\": \"Junio\",\n \"07\": \"Julio\",\n \"08\": \"Agosto\",\n \"09\": \"Septiembre\",\n \"10\": \"Octubre\",\n \"11\": \"Noviembre\",\n \"12\": \"Diciembre\",\n };\n\n result.periodo_inicio = `${d1}/${m1}/${y1}`;\n result.periodo_fin = `${d2}/${m2}/${y2}`;\n result.anio = y1;\n result.mes_numero = m1;\n result.mes_nombre = meses[m1] || m1;\n result.quincena = Number(d1) <= 15 ? \"Quincena 1\" : \"Quincena 2\";\n\n return result;\n}\n\nfunction extractMeta(text) {\n let empresa = \"\";\n let planillaNumero = \"\";\n let periodo = \"\";\n\n const lines = text\n .split(/\\r?\\n/)\n .map(x => String(x || \"\").replace(/\\s+/g, \" \").trim())\n .filter(Boolean);\n\n for (const line of lines) {\n if (!empresa && /^(GOMEZLEE|GLM-)/i.test(line)) {\n empresa = line;\n }\n\n const planillaMatch = line.match(/^Planilla N煤mero\\s*(.*)$/i);\n if (planillaMatch) {\n planillaNumero = planillaMatch[1].trim();\n }\n\n const periodoMatch = line.match(/^Periodo del\\s*(.*)$/i);\n if (periodoMatch) {\n periodo = periodoMatch[1].trim();\n }\n }\n\n return { empresa, planillaNumero, periodo };\n}\n\nfunction getDepartmentBefore(text, index) {\n const marker = \"DEPARTAMENTO:\";\n const pos = text.lastIndexOf(marker, index);\n\n if (pos === -1) return \"\";\n\n const end = text.indexOf(\"\\n\", pos);\n const raw = end === -1 ? text.slice(pos) : text.slice(pos, end);\n\n return raw.replace(/^DEPARTAMENTO:/i, \"\").replace(/\\s+/g, \" \").trim();\n}\n\nfunction getLastNumbersBefore(text, index, count) {\n const before = text.slice(Math.max(0, index - 5000), index);\n const matches = [...before.matchAll(NUMBER_ANY_RE)].map(m => toNumber(m[0]));\n\n return matches.slice(-count);\n}\n\nfunction getCodeAndSalaryAfter(text, index) {\n const after = text.slice(index, index + 120);\n\n const codeMatch = after.match(/^\\s*([A-Z0-9]{1,12})\\b/u);\n\n if (!codeMatch) {\n return {\n codigo: \"\",\n salariosEspecie: 0,\n };\n }\n\n const codigo = codeMatch[1].trim();\n const afterCode = after.slice(codeMatch[0].length);\n const salaryMatch = afterCode.match(NUMBER_ANY_RE);\n\n return {\n codigo,\n salariosEspecie: salaryMatch ? toNumber(salaryMatch[0]) : 0,\n };\n}\n\nfunction makeRecord({\n archivoNombre,\n archivoId,\n empresa,\n planillaNumero,\n periodo,\n departamento,\n codigo,\n nombre,\n numbers,\n}) {\n const periodoData = parsePeriodo(periodo);\n\n const record = {\n tipo: \"DETALLE\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n empresa,\n planilla_numero: planillaNumero,\n periodo,\n ...periodoData,\n departamento,\n codigo,\n nombre,\n empleado_key: normalizeName(nombre),\n };\n\n COLS.forEach((col, index) => {\n record[col] = round2(numbers[index] || 0);\n });\n\n return record;\n}\n\nconst output = [];\nconst inputItems = $input.all();\n\nlet metadataItems = [];\ntry {\n metadataItems = $(\"Metadata PDF\").all();\n} catch (e) {\n metadataItems = [];\n}\n\nfor (let itemIndex = 0; itemIndex < inputItems.length; itemIndex++) {\n const item = inputItems[itemIndex];\n const json = item.json || {};\n const metadata = metadataItems[itemIndex]?.json || {};\n\n const text = pickPdfText(json);\n\n const archivoNombre =\n json.pdf_name ||\n metadata.pdf_name ||\n json.name ||\n json.fileName ||\n json.filename ||\n \"PDF sin nombre\";\n\n const archivoId =\n json.pdf_id ||\n metadata.pdf_id ||\n json.id ||\n json.fileId ||\n \"\";\n\n if (!text) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n motivo: \"No se encontr贸 texto extra铆do del PDF\",\n },\n });\n continue;\n }\n\n const meta = extractMeta(text);\n\n let parsedEmployees = 0;\n\n const matches = [...text.matchAll(NAME_RE)];\n\n for (const match of matches) {\n const nombre = String(match[0] || \"\").replace(/\\s+/g, \" \").trim();\n\n if (!nombre.includes(\",\")) continue;\n\n const nameStart = match.index;\n const nameEnd = match.index + match[0].length;\n\n const departamento = getDepartmentBefore(text, nameStart);\n\n const numericBlock = getLastNumbersBefore(text, nameStart, 22);\n\n if (numericBlock.length !== 22) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n planilla_numero: meta.planillaNumero,\n periodo: meta.periodo,\n departamento,\n linea: nombre,\n motivo: `No se encontraron 22 valores num茅ricos antes del empleado. Detectados: ${numericBlock.length}`,\n },\n });\n continue;\n }\n\n const { codigo, salariosEspecie } = getCodeAndSalaryAfter(text, nameEnd);\n\n if (!codigo) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n planilla_numero: meta.planillaNumero,\n periodo: meta.periodo,\n departamento,\n linea: nombre,\n motivo: \"No se pudo detectar el c贸digo del empleado despu茅s del nombre\",\n },\n });\n continue;\n }\n\n // El bloque viene al rev茅s:\n // total_neto, total_desc, otras_deduc, ..., horas_reg, sal_por_hora.\n const forward = [...numericBlock].reverse();\n\n // En el texto extra铆do normalmente no aparece I.S.R. Salarios cuando es 0.\n // Lo insertamos en la posici贸n correcta.\n const fullNumbers = [\n ...forward.slice(0, 19),\n 0,\n ...forward.slice(19),\n salariosEspecie,\n ];\n\n if (fullNumbers.length !== 24) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n planilla_numero: meta.planillaNumero,\n periodo: meta.periodo,\n departamento,\n linea: `${nombre} / ${codigo}`,\n motivo: `Cantidad final de columnas inv谩lida: ${fullNumbers.length}`,\n },\n });\n continue;\n }\n\n output.push({\n json: makeRecord({\n archivoNombre,\n archivoId,\n empresa: meta.empresa,\n planillaNumero: meta.planillaNumero,\n periodo: meta.periodo,\n departamento,\n codigo,\n nombre,\n numbers: fullNumbers,\n }),\n });\n\n parsedEmployees++;\n }\n\n if (parsedEmployees === 0) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n planilla_numero: meta.planillaNumero,\n periodo: meta.periodo,\n motivo: `No se detect贸 ning煤n empleado en este PDF. Posibles nombres detectados por regex: ${matches.length}`,\n },\n });\n }\n}\n\nreturn output;" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1872, 0 ], "id": "4042e300-1055-41f8-b5dc-14ed1f1137a9", "name": "Parsear planilla PDF" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "6608d91d-bf7c-4875-a375-fdb356de6832", "leftValue": "={{$json.tipo}}", "rightValue": "DETALLE", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 2160, -80 ], "id": "9c3c289c-fec8-4263-bf85-1f6913807d86", "name": "Es DETALLE" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "638e07d0-4074-44ce-95d5-9d2799eabe71", "leftValue": "={{$json.tipo}}", "rightValue": "REVISION", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 2448, 144 ], "id": "fd648dbc-65b4-420c-b47f-a1597c8d959b", "name": "Es REVISION" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": 389127788, "mode": "list", "cachedResultName": "DETALLE_EXTRAIDO", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=389127788" }, "columns": { "mappingMode": "autoMapInputData", "value": {}, "matchingColumns": [], "schema": [], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 2416, -256 ], "id": "cbc8d5ca-024a-450b-b3d7-a598cd7d0c28", "name": "Guardar DETALLE_EXTRAIDO", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": 1863286680, "mode": "list", "cachedResultName": "REVISION", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=1863286680" }, "columns": { "mappingMode": "autoMapInputData", "value": {}, "matchingColumns": [], "schema": [ { "id": "tipo", "displayName": "tipo", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "archivo_nombre", "displayName": "archivo_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "archivo_id", "displayName": "archivo_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "planilla_numero", "displayName": "planilla_numero", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "periodo", "displayName": "periodo", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "departamento", "displayName": "departamento", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "linea", "displayName": "linea", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "motivo", "displayName": "motivo", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 2656, 48 ], "id": "1f44cf9d-9dfb-4c43-be3e-a8f8de805c1a", "name": "Guardar REVISION", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const SUM_COLS = [\n \"horas_reg\",\n \"horas_1_25\",\n \"horas_1_50\",\n \"horas_1_75\",\n \"horas_2_625\",\n \"horas_aus_tard\",\n \"horas_cm\",\n \"ajuste_no_pagas\",\n \"horas_ajuste\",\n \"salario_regular\",\n \"salario_comision\",\n \"sobretiempo\",\n \"otros_ingresos\",\n \"salario_bruto\",\n \"gto_rep\",\n \"ss\",\n \"se\",\n \"isr\",\n \"isr_salarios\",\n \"otras_deduc\",\n \"total_desc\",\n \"total_neto\",\n \"salarios_especie\",\n];\n\nfunction round2(n) {\n return Math.round((Number(n || 0) + Number.EPSILON) * 100) / 100;\n}\n\nconst groups = new Map();\n\nfor (const item of $input.all()) {\n const row = item.json;\n\n if (row.tipo !== \"DETALLE\") continue;\n\n const key = row.empleado_key || row.nombre;\n\n if (!groups.has(key)) {\n const base = {\n empleado_key: key,\n nombre: row.nombre,\n codigos_detectados: new Set(),\n empresas_detectadas: new Set(),\n departamentos_detectados: new Set(),\n meses_detectados: new Set(),\n quincenas_detectadas: new Set(),\n cantidad_registros: 0,\n sal_por_hora_min: null,\n sal_por_hora_max: null,\n sal_por_hora_ultimo: null,\n };\n\n for (const col of SUM_COLS) {\n base[`total_${col}`] = 0;\n }\n\n groups.set(key, base);\n }\n\n const g = groups.get(key);\n\n g.cantidad_registros += 1;\n\n if (row.codigo) g.codigos_detectados.add(row.codigo);\n if (row.empresa) g.empresas_detectadas.add(row.empresa);\n if (row.departamento) g.departamentos_detectados.add(row.departamento);\n if (row.mes_nombre) g.meses_detectados.add(row.mes_nombre);\n if (row.quincena) g.quincenas_detectadas.add(`${row.mes_nombre || \"\"} ${row.quincena || \"\"}`.trim());\n\n const rate = Number(row.sal_por_hora || 0);\n if (rate > 0) {\n g.sal_por_hora_min = g.sal_por_hora_min === null ? rate : Math.min(g.sal_por_hora_min, rate);\n g.sal_por_hora_max = g.sal_por_hora_max === null ? rate : Math.max(g.sal_por_hora_max, rate);\n g.sal_por_hora_ultimo = rate;\n }\n\n for (const col of SUM_COLS) {\n g[`total_${col}`] += Number(row[col] || 0);\n }\n}\n\nconst output = [];\n\nfor (const g of groups.values()) {\n const row = {\n empleado_key: g.empleado_key,\n nombre: g.nombre,\n codigos_detectados: Array.from(g.codigos_detectados).join(\", \"),\n empresas_detectadas: Array.from(g.empresas_detectadas).join(\" | \"),\n departamentos_detectados: Array.from(g.departamentos_detectados).join(\" | \"),\n meses_detectados: Array.from(g.meses_detectados).join(\", \"),\n quincenas_detectadas: Array.from(g.quincenas_detectadas).join(\" | \"),\n cantidad_registros: g.cantidad_registros,\n sal_por_hora_min: round2(g.sal_por_hora_min || 0),\n sal_por_hora_max: round2(g.sal_por_hora_max || 0),\n sal_por_hora_ultimo: round2(g.sal_por_hora_ultimo || 0),\n };\n\n for (const col of SUM_COLS) {\n row[`total_${col}`] = round2(g[`total_${col}`]);\n }\n\n output.push({ json: row });\n}\n\noutput.sort((a, b) => String(a.json.nombre).localeCompare(String(b.json.nombre)));\n\nreturn output;" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2800, -144 ], "id": "c4861f22-1404-4dbd-a278-86735b88760c", "name": "Consolidar RESUMEN_2025" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": "gid=0", "mode": "list", "cachedResultName": "RESUMEN_2025", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=0" }, "columns": { "mappingMode": "autoMapInputData", "value": {}, "matchingColumns": [], "schema": [], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 3008, -144 ], "id": "a89067df-8244-44ff-8a3c-a29281575968", "name": "Guardar RESUMEN_2025", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "content": "Inicializa la corrida, limpia las hojas de salida y localiza los PDFs de n贸mina seg煤n mes/quincena configurada.", "height": 256, "width": 1840, "color": 4 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -448, -80 ], "id": "474e02da-06ce-40ac-832c-61485b13fd35", "name": "Sticky Note" }, { "parameters": { "content": "Descarga cada PDF, extrae el texto y lo convierte en registros estructurados para clasificaci贸n.", "height": 272, "width": 592, "color": 6 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1440, -80 ], "id": "b20ae3fc-f59e-4ab2-9b63-806e82e6f9c3", "name": "Sticky Note1" }, { "parameters": { "content": "Clasifica los registros entre detalle, revisi贸n y resumen. Luego guarda cada resultado en su hoja correspondiente. ", "height": 640, "width": 1120 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 2096, -336 ], "id": "50591553-aa56-4871-af8a-6278913182bd", "name": "Sticky Note2" }, { "parameters": { "path": "6d137795-fb6c-403a-ad32-b55ca064418b", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ -400, 0 ], "id": "9b8851b6-29a0-43c5-9ae0-0f83202d2f7d", "name": "Webhook", "webhookId": "6d137795-fb6c-403a-ad32-b55ca064418b" } ], "connections": { "Inicio Manual": { "main": [ [] ] }, "Limpiar DETALLE_EXTRAIDO": { "main": [ [ { "node": "Limpiar REVISION", "type": "main", "index": 0 } ] ] }, "Limpiar REVISION": { "main": [ [ { "node": "Limpiar RESUMEN_2025", "type": "main", "index": 0 } ] ] }, "Limpiar RESUMEN_2025": { "main": [ [ { "node": "Config", "type": "main", "index": 0 } ] ] }, "Config": { "main": [ [ { "node": "Listar meses 2025", "type": "main", "index": 0 } ] ] }, "Listar meses 2025": { "main": [ [ { "node": "Listar quincenas", "type": "main", "index": 0 } ] ] }, "Listar quincenas": { "main": [ [ { "node": "Listar PDFs", "type": "main", "index": 0 } ] ] }, "Listar PDFs": { "main": [ [ { "node": "Metadata PDF", "type": "main", "index": 0 } ] ] }, "Metadata PDF": { "main": [ [ { "node": "Descargar PDF", "type": "main", "index": 0 } ] ] }, "Descargar PDF": { "main": [ [ { "node": "Extraer texto PDF", "type": "main", "index": 0 } ] ] }, "Extraer texto PDF": { "main": [ [ { "node": "Parsear planilla PDF", "type": "main", "index": 0 } ] ] }, "Parsear planilla PDF": { "main": [ [ { "node": "Es DETALLE", "type": "main", "index": 0 }, { "node": "Es REVISION", "type": "main", "index": 0 } ] ] }, "Es DETALLE": { "main": [ [ { "node": "Guardar DETALLE_EXTRAIDO", "type": "main", "index": 0 }, { "node": "Consolidar RESUMEN_2025", "type": "main", "index": 0 } ] ] }, "Es REVISION": { "main": [ [ { "node": "Guardar REVISION", "type": "main", "index": 0 } ] ] }, "Consolidar RESUMEN_2025": { "main": [ [ { "node": "Guardar RESUMEN_2025", "type": "main", "index": 0 } ] ] }, "Webhook": { "main": [ [ { "node": "Limpiar DETALLE_EXTRAIDO", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "binaryMode": "separate" }, "staticData": null, "meta": { "templateCredsSetupCompleted": true }, "versionId": "de60abc3-a901-4638-bcee-bcf23b016760", "activeVersionId": "de60abc3-a901-4638-bcee-bcf23b016760", "versionCounter": 169, "triggerCount": 1, "shared": [ { "updatedAt": "2026-05-19T19:59:05.802Z", "createdAt": "2026-05-19T19:59:05.802Z", "role": "workflow:owner", "workflowId": "l8koc0x9iIFqKn7P", "projectId": "PJpTANzTXIFibWsW", "project": { "updatedAt": "2026-04-22T14:25:09.686Z", "createdAt": "2026-04-22T14:22:54.790Z", "id": "PJpTANzTXIFibWsW", "name": "Isaac Aracena ", "type": "personal", "icon": null, "description": null, "creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" } } ], "tags": [], "activeVersion": { "updatedAt": "2026-06-05T19:23:26.000Z", "createdAt": "2026-06-05T19:23:24.941Z", "versionId": "de60abc3-a901-4638-bcee-bcf23b016760", "workflowId": "l8koc0x9iIFqKn7P", "nodes": [ { "parameters": {}, "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ -256, 416 ], "id": "f91d41a1-5eb4-4810-a7f8-f7d8cbdb7117", "name": "Inicio Manual" }, { "parameters": { "operation": "clear", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": 389127788, "mode": "list", "cachedResultName": "DETALLE_EXTRAIDO", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=389127788" }, "clear": "specificRange", "range": "A2:ZZ50000" }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ -192, 0 ], "id": "cfd6f04d-efce-46e6-89a1-7bbde07cbb1f", "name": "Limpiar DETALLE_EXTRAIDO", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "clear", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": 1863286680, "mode": "list", "cachedResultName": "REVISION", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=1863286680" }, "clear": "specificRange", "range": "A2:ZZ50000" }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 16, 0 ], "id": "024610d8-5d02-4d57-898d-5317ede98081", "name": "Limpiar REVISION", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "clear", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": "gid=0", "mode": "list", "cachedResultName": "RESUMEN_2025", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=0" }, "clear": "specificRange", "range": "A2:ZZ50000" }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 224, 0 ], "id": "ba1ba8a2-5211-4ffe-a261-f93e10c8fdc4", "name": "Limpiar RESUMEN_2025", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "assignments": { "assignments": [ { "id": "209f180b-e0f6-4e3d-8005-c451a6be87c3", "name": "folder_2025_id", "value": "1KceYMLySpA9bbVnitzl5Y5YBBsLM3Dlq", "type": "string" }, { "id": "bdf727db-d037-429f-adfd-a15a0622599f", "name": "spreadsheet_id", "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "type": "string" } ] }, "options": {} }, "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ 448, 0 ], "id": "4d745e8f-adfd-4a70-84a7-5e03ea9e2cbb", "name": "Config" }, { "parameters": { "resource": "fileFolder", "searchMethod": "query", "queryString": "='1KceYMLySpA9bbVnitzl5Y5YBBsLM3Dlq' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false", "returnAll": true, "filter": {}, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 656, 0 ], "id": "b95a2dcd-a070-4de4-8373-265892f8a1f1", "name": "Listar meses 2025", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "resource": "fileFolder", "searchMethod": "query", "queryString": "={{ \"'\" + $json.id + \"' in parents and mimeType = 'application/pdf' and trashed = false\" }}", "returnAll": true, "filter": {}, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 1072, 0 ], "id": "39fc6525-d4eb-4937-af9f-6b5c495fd32f", "name": "Listar PDFs", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "resource": "fileFolder", "searchMethod": "query", "queryString": "='{{$json.id}}' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false", "returnAll": true, "filter": {}, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 864, 0 ], "id": "6fdde348-bc72-417e-99f6-4780bb3560c3", "name": "Listar quincenas", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "assignments": { "assignments": [ { "id": "1f256065-3538-495e-8515-e75c0af3c89c", "name": "pdf_id", "value": "={{$json.id}}", "type": "string" }, { "id": "293459ea-7b1f-443a-8aed-1126295deea8", "name": "pdf_name", "value": "={{$json.name}}", "type": "string" }, { "id": "90f1da7c-2bf8-4e79-86fe-b635e0c7edb5", "name": "pdf_mimeType", "value": "={{$json.mimeType}}", "type": "string" } ] }, "options": {} }, "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ 1280, 0 ], "id": "9f651201-76d5-41ea-ac42-8d2b137163af", "name": "Metadata PDF" }, { "parameters": { "operation": "download", "fileId": { "__rl": true, "value": "={{$json.pdf_id}}", "mode": "id" }, "options": {} }, "type": "n8n-nodes-base.googleDrive", "typeVersion": 3, "position": [ 1488, 0 ], "id": "8ea393e6-179b-4348-a864-1ef2ae1d79f2", "name": "Descargar PDF", "credentials": { "googleDriveOAuth2Api": { "id": "g23xdGLZRzBGqKgH", "name": "Isaac - Google Drive" } } }, { "parameters": { "operation": "pdf", "options": {} }, "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ 1680, 0 ], "id": "c97cf3b9-ae31-4a46-9511-48a2d0e58a21", "name": "Extraer texto PDF" }, { "parameters": { "jsCode": "const COLS = [\n \"sal_por_hora\",\n \"horas_reg\",\n \"horas_1_25\",\n \"horas_1_50\",\n \"horas_1_75\",\n \"horas_2_625\",\n \"horas_aus_tard\",\n \"horas_cm\",\n \"ajuste_no_pagas\",\n \"horas_ajuste\",\n \"salario_regular\",\n \"salario_comision\",\n \"sobretiempo\",\n \"otros_ingresos\",\n \"salario_bruto\",\n \"gto_rep\",\n \"ss\",\n \"se\",\n \"isr\",\n \"isr_salarios\",\n \"otras_deduc\",\n \"total_desc\",\n \"total_neto\",\n \"salarios_especie\",\n];\n\nconst NUMBER_ANY_RE = /-?\\d{1,3}(?:,\\d{3})*\\.\\d{2,6}|-?\\d+\\.\\d{2,6}/g;\nconst NAME_RE = /[A-Z脕脡脥脫脷脺脩][A-Z脕脡脥脫脷脺脩 .'\\-]+,\\s*[A-Z脕脡脥脫脷脺脩][A-Z脕脡脥脫脷脺脩 .'\\-]+/gu;\n\nfunction toNumber(value) {\n const n = Number(String(value || \"0\").replace(/,/g, \"\"));\n return Number.isFinite(n) ? n : 0;\n}\n\nfunction round2(n) {\n return Math.round((Number(n || 0) + Number.EPSILON) * 100) / 100;\n}\n\nfunction normalizeName(name) {\n return String(name || \"\")\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toUpperCase()\n .replace(/[^A-Z脩, ]/g, \" \")\n .replace(/,/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction pickPdfText(json) {\n for (const key of [\"text\", \"data\", \"content\", \"extractedText\", \"pdfText\"]) {\n if (typeof json[key] === \"string\" && json[key].includes(\"Planilla\")) {\n return json[key];\n }\n }\n\n for (const value of Object.values(json)) {\n if (typeof value === \"string\" && value.includes(\"Planilla\")) {\n return value;\n }\n }\n\n return \"\";\n}\n\nfunction parsePeriodo(periodo) {\n const result = {\n periodo_inicio: \"\",\n periodo_fin: \"\",\n anio: \"\",\n mes_numero: \"\",\n mes_nombre: \"\",\n quincena: \"\",\n };\n\n const match = String(periodo || \"\").match(\n /(\\d{2})\\/(\\d{2})\\/(\\d{4}).*?(\\d{2})\\/(\\d{2})\\/(\\d{4})/\n );\n\n if (!match) return result;\n\n const [, d1, m1, y1, d2, m2, y2] = match;\n\n const meses = {\n \"01\": \"Enero\",\n \"02\": \"Febrero\",\n \"03\": \"Marzo\",\n \"04\": \"Abril\",\n \"05\": \"Mayo\",\n \"06\": \"Junio\",\n \"07\": \"Julio\",\n \"08\": \"Agosto\",\n \"09\": \"Septiembre\",\n \"10\": \"Octubre\",\n \"11\": \"Noviembre\",\n \"12\": \"Diciembre\",\n };\n\n result.periodo_inicio = `${d1}/${m1}/${y1}`;\n result.periodo_fin = `${d2}/${m2}/${y2}`;\n result.anio = y1;\n result.mes_numero = m1;\n result.mes_nombre = meses[m1] || m1;\n result.quincena = Number(d1) <= 15 ? \"Quincena 1\" : \"Quincena 2\";\n\n return result;\n}\n\nfunction extractMeta(text) {\n let empresa = \"\";\n let planillaNumero = \"\";\n let periodo = \"\";\n\n const lines = text\n .split(/\\r?\\n/)\n .map(x => String(x || \"\").replace(/\\s+/g, \" \").trim())\n .filter(Boolean);\n\n for (const line of lines) {\n if (!empresa && /^(GOMEZLEE|GLM-)/i.test(line)) {\n empresa = line;\n }\n\n const planillaMatch = line.match(/^Planilla N煤mero\\s*(.*)$/i);\n if (planillaMatch) {\n planillaNumero = planillaMatch[1].trim();\n }\n\n const periodoMatch = line.match(/^Periodo del\\s*(.*)$/i);\n if (periodoMatch) {\n periodo = periodoMatch[1].trim();\n }\n }\n\n return { empresa, planillaNumero, periodo };\n}\n\nfunction getDepartmentBefore(text, index) {\n const marker = \"DEPARTAMENTO:\";\n const pos = text.lastIndexOf(marker, index);\n\n if (pos === -1) return \"\";\n\n const end = text.indexOf(\"\\n\", pos);\n const raw = end === -1 ? text.slice(pos) : text.slice(pos, end);\n\n return raw.replace(/^DEPARTAMENTO:/i, \"\").replace(/\\s+/g, \" \").trim();\n}\n\nfunction getLastNumbersBefore(text, index, count) {\n const before = text.slice(Math.max(0, index - 5000), index);\n const matches = [...before.matchAll(NUMBER_ANY_RE)].map(m => toNumber(m[0]));\n\n return matches.slice(-count);\n}\n\nfunction getCodeAndSalaryAfter(text, index) {\n const after = text.slice(index, index + 120);\n\n const codeMatch = after.match(/^\\s*([A-Z0-9]{1,12})\\b/u);\n\n if (!codeMatch) {\n return {\n codigo: \"\",\n salariosEspecie: 0,\n };\n }\n\n const codigo = codeMatch[1].trim();\n const afterCode = after.slice(codeMatch[0].length);\n const salaryMatch = afterCode.match(NUMBER_ANY_RE);\n\n return {\n codigo,\n salariosEspecie: salaryMatch ? toNumber(salaryMatch[0]) : 0,\n };\n}\n\nfunction makeRecord({\n archivoNombre,\n archivoId,\n empresa,\n planillaNumero,\n periodo,\n departamento,\n codigo,\n nombre,\n numbers,\n}) {\n const periodoData = parsePeriodo(periodo);\n\n const record = {\n tipo: \"DETALLE\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n empresa,\n planilla_numero: planillaNumero,\n periodo,\n ...periodoData,\n departamento,\n codigo,\n nombre,\n empleado_key: normalizeName(nombre),\n };\n\n COLS.forEach((col, index) => {\n record[col] = round2(numbers[index] || 0);\n });\n\n return record;\n}\n\nconst output = [];\nconst inputItems = $input.all();\n\nlet metadataItems = [];\ntry {\n metadataItems = $(\"Metadata PDF\").all();\n} catch (e) {\n metadataItems = [];\n}\n\nfor (let itemIndex = 0; itemIndex < inputItems.length; itemIndex++) {\n const item = inputItems[itemIndex];\n const json = item.json || {};\n const metadata = metadataItems[itemIndex]?.json || {};\n\n const text = pickPdfText(json);\n\n const archivoNombre =\n json.pdf_name ||\n metadata.pdf_name ||\n json.name ||\n json.fileName ||\n json.filename ||\n \"PDF sin nombre\";\n\n const archivoId =\n json.pdf_id ||\n metadata.pdf_id ||\n json.id ||\n json.fileId ||\n \"\";\n\n if (!text) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n motivo: \"No se encontr贸 texto extra铆do del PDF\",\n },\n });\n continue;\n }\n\n const meta = extractMeta(text);\n\n let parsedEmployees = 0;\n\n const matches = [...text.matchAll(NAME_RE)];\n\n for (const match of matches) {\n const nombre = String(match[0] || \"\").replace(/\\s+/g, \" \").trim();\n\n if (!nombre.includes(\",\")) continue;\n\n const nameStart = match.index;\n const nameEnd = match.index + match[0].length;\n\n const departamento = getDepartmentBefore(text, nameStart);\n\n const numericBlock = getLastNumbersBefore(text, nameStart, 22);\n\n if (numericBlock.length !== 22) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n planilla_numero: meta.planillaNumero,\n periodo: meta.periodo,\n departamento,\n linea: nombre,\n motivo: `No se encontraron 22 valores num茅ricos antes del empleado. Detectados: ${numericBlock.length}`,\n },\n });\n continue;\n }\n\n const { codigo, salariosEspecie } = getCodeAndSalaryAfter(text, nameEnd);\n\n if (!codigo) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n planilla_numero: meta.planillaNumero,\n periodo: meta.periodo,\n departamento,\n linea: nombre,\n motivo: \"No se pudo detectar el c贸digo del empleado despu茅s del nombre\",\n },\n });\n continue;\n }\n\n // El bloque viene al rev茅s:\n // total_neto, total_desc, otras_deduc, ..., horas_reg, sal_por_hora.\n const forward = [...numericBlock].reverse();\n\n // En el texto extra铆do normalmente no aparece I.S.R. Salarios cuando es 0.\n // Lo insertamos en la posici贸n correcta.\n const fullNumbers = [\n ...forward.slice(0, 19),\n 0,\n ...forward.slice(19),\n salariosEspecie,\n ];\n\n if (fullNumbers.length !== 24) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n planilla_numero: meta.planillaNumero,\n periodo: meta.periodo,\n departamento,\n linea: `${nombre} / ${codigo}`,\n motivo: `Cantidad final de columnas inv谩lida: ${fullNumbers.length}`,\n },\n });\n continue;\n }\n\n output.push({\n json: makeRecord({\n archivoNombre,\n archivoId,\n empresa: meta.empresa,\n planillaNumero: meta.planillaNumero,\n periodo: meta.periodo,\n departamento,\n codigo,\n nombre,\n numbers: fullNumbers,\n }),\n });\n\n parsedEmployees++;\n }\n\n if (parsedEmployees === 0) {\n output.push({\n json: {\n tipo: \"REVISION\",\n archivo_nombre: archivoNombre,\n archivo_id: archivoId,\n planilla_numero: meta.planillaNumero,\n periodo: meta.periodo,\n motivo: `No se detect贸 ning煤n empleado en este PDF. Posibles nombres detectados por regex: ${matches.length}`,\n },\n });\n }\n}\n\nreturn output;" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1872, 0 ], "id": "4042e300-1055-41f8-b5dc-14ed1f1137a9", "name": "Parsear planilla PDF" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "6608d91d-bf7c-4875-a375-fdb356de6832", "leftValue": "={{$json.tipo}}", "rightValue": "DETALLE", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 2160, -80 ], "id": "9c3c289c-fec8-4263-bf85-1f6913807d86", "name": "Es DETALLE" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "638e07d0-4074-44ce-95d5-9d2799eabe71", "leftValue": "={{$json.tipo}}", "rightValue": "REVISION", "operator": { "type": "string", "operation": "equals", "name": "filter.operator.equals" } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 2448, 144 ], "id": "fd648dbc-65b4-420c-b47f-a1597c8d959b", "name": "Es REVISION" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": 389127788, "mode": "list", "cachedResultName": "DETALLE_EXTRAIDO", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=389127788" }, "columns": { "mappingMode": "autoMapInputData", "value": {}, "matchingColumns": [], "schema": [], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 2416, -256 ], "id": "cbc8d5ca-024a-450b-b3d7-a598cd7d0c28", "name": "Guardar DETALLE_EXTRAIDO", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": 1863286680, "mode": "list", "cachedResultName": "REVISION", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=1863286680" }, "columns": { "mappingMode": "autoMapInputData", "value": {}, "matchingColumns": [], "schema": [ { "id": "tipo", "displayName": "tipo", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "archivo_nombre", "displayName": "archivo_nombre", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "archivo_id", "displayName": "archivo_id", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "planilla_numero", "displayName": "planilla_numero", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "periodo", "displayName": "periodo", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "departamento", "displayName": "departamento", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "linea", "displayName": "linea", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "motivo", "displayName": "motivo", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 2656, 48 ], "id": "1f44cf9d-9dfb-4c43-be3e-a8f8de805c1a", "name": "Guardar REVISION", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "jsCode": "const SUM_COLS = [\n \"horas_reg\",\n \"horas_1_25\",\n \"horas_1_50\",\n \"horas_1_75\",\n \"horas_2_625\",\n \"horas_aus_tard\",\n \"horas_cm\",\n \"ajuste_no_pagas\",\n \"horas_ajuste\",\n \"salario_regular\",\n \"salario_comision\",\n \"sobretiempo\",\n \"otros_ingresos\",\n \"salario_bruto\",\n \"gto_rep\",\n \"ss\",\n \"se\",\n \"isr\",\n \"isr_salarios\",\n \"otras_deduc\",\n \"total_desc\",\n \"total_neto\",\n \"salarios_especie\",\n];\n\nfunction round2(n) {\n return Math.round((Number(n || 0) + Number.EPSILON) * 100) / 100;\n}\n\nconst groups = new Map();\n\nfor (const item of $input.all()) {\n const row = item.json;\n\n if (row.tipo !== \"DETALLE\") continue;\n\n const key = row.empleado_key || row.nombre;\n\n if (!groups.has(key)) {\n const base = {\n empleado_key: key,\n nombre: row.nombre,\n codigos_detectados: new Set(),\n empresas_detectadas: new Set(),\n departamentos_detectados: new Set(),\n meses_detectados: new Set(),\n quincenas_detectadas: new Set(),\n cantidad_registros: 0,\n sal_por_hora_min: null,\n sal_por_hora_max: null,\n sal_por_hora_ultimo: null,\n };\n\n for (const col of SUM_COLS) {\n base[`total_${col}`] = 0;\n }\n\n groups.set(key, base);\n }\n\n const g = groups.get(key);\n\n g.cantidad_registros += 1;\n\n if (row.codigo) g.codigos_detectados.add(row.codigo);\n if (row.empresa) g.empresas_detectadas.add(row.empresa);\n if (row.departamento) g.departamentos_detectados.add(row.departamento);\n if (row.mes_nombre) g.meses_detectados.add(row.mes_nombre);\n if (row.quincena) g.quincenas_detectadas.add(`${row.mes_nombre || \"\"} ${row.quincena || \"\"}`.trim());\n\n const rate = Number(row.sal_por_hora || 0);\n if (rate > 0) {\n g.sal_por_hora_min = g.sal_por_hora_min === null ? rate : Math.min(g.sal_por_hora_min, rate);\n g.sal_por_hora_max = g.sal_por_hora_max === null ? rate : Math.max(g.sal_por_hora_max, rate);\n g.sal_por_hora_ultimo = rate;\n }\n\n for (const col of SUM_COLS) {\n g[`total_${col}`] += Number(row[col] || 0);\n }\n}\n\nconst output = [];\n\nfor (const g of groups.values()) {\n const row = {\n empleado_key: g.empleado_key,\n nombre: g.nombre,\n codigos_detectados: Array.from(g.codigos_detectados).join(\", \"),\n empresas_detectadas: Array.from(g.empresas_detectadas).join(\" | \"),\n departamentos_detectados: Array.from(g.departamentos_detectados).join(\" | \"),\n meses_detectados: Array.from(g.meses_detectados).join(\", \"),\n quincenas_detectadas: Array.from(g.quincenas_detectadas).join(\" | \"),\n cantidad_registros: g.cantidad_registros,\n sal_por_hora_min: round2(g.sal_por_hora_min || 0),\n sal_por_hora_max: round2(g.sal_por_hora_max || 0),\n sal_por_hora_ultimo: round2(g.sal_por_hora_ultimo || 0),\n };\n\n for (const col of SUM_COLS) {\n row[`total_${col}`] = round2(g[`total_${col}`]);\n }\n\n output.push({ json: row });\n}\n\noutput.sort((a, b) => String(a.json.nombre).localeCompare(String(b.json.nombre)));\n\nreturn output;" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2800, -144 ], "id": "c4861f22-1404-4dbd-a278-86735b88760c", "name": "Consolidar RESUMEN_2025" }, { "parameters": { "operation": "append", "documentId": { "__rl": true, "value": "1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU", "mode": "id" }, "sheetName": { "__rl": true, "value": "gid=0", "mode": "list", "cachedResultName": "RESUMEN_2025", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AiDZjSRR5MB_7Ib1mbWmaYCQmbebP95SIigXsD2ffqU/edit#gid=0" }, "columns": { "mappingMode": "autoMapInputData", "value": {}, "matchingColumns": [], "schema": [], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.7, "position": [ 3008, -144 ], "id": "a89067df-8244-44ff-8a3c-a29281575968", "name": "Guardar RESUMEN_2025", "credentials": { "googleSheetsOAuth2Api": { "id": "K0hDZh3a85MpOHCs", "name": "Google Sheets account 2" } } }, { "parameters": { "content": "Inicializa la corrida, limpia las hojas de salida y localiza los PDFs de n贸mina seg煤n mes/quincena configurada.", "height": 256, "width": 1840, "color": 4 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -448, -80 ], "id": "474e02da-06ce-40ac-832c-61485b13fd35", "name": "Sticky Note" }, { "parameters": { "content": "Descarga cada PDF, extrae el texto y lo convierte en registros estructurados para clasificaci贸n.", "height": 272, "width": 592, "color": 6 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1440, -80 ], "id": "b20ae3fc-f59e-4ab2-9b63-806e82e6f9c3", "name": "Sticky Note1" }, { "parameters": { "content": "Clasifica los registros entre detalle, revisi贸n y resumen. Luego guarda cada resultado en su hoja correspondiente. ", "height": 640, "width": 1120 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 2096, -336 ], "id": "50591553-aa56-4871-af8a-6278913182bd", "name": "Sticky Note2" }, { "parameters": { "path": "6d137795-fb6c-403a-ad32-b55ca064418b", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ -400, 0 ], "id": "9b8851b6-29a0-43c5-9ae0-0f83202d2f7d", "name": "Webhook", "webhookId": "6d137795-fb6c-403a-ad32-b55ca064418b" } ], "connections": { "Inicio Manual": { "main": [ [] ] }, "Limpiar DETALLE_EXTRAIDO": { "main": [ [ { "node": "Limpiar REVISION", "type": "main", "index": 0 } ] ] }, "Limpiar REVISION": { "main": [ [ { "node": "Limpiar RESUMEN_2025", "type": "main", "index": 0 } ] ] }, "Limpiar RESUMEN_2025": { "main": [ [ { "node": "Config", "type": "main", "index": 0 } ] ] }, "Config": { "main": [ [ { "node": "Listar meses 2025", "type": "main", "index": 0 } ] ] }, "Listar meses 2025": { "main": [ [ { "node": "Listar quincenas", "type": "main", "index": 0 } ] ] }, "Listar quincenas": { "main": [ [ { "node": "Listar PDFs", "type": "main", "index": 0 } ] ] }, "Listar PDFs": { "main": [ [ { "node": "Metadata PDF", "type": "main", "index": 0 } ] ] }, "Metadata PDF": { "main": [ [ { "node": "Descargar PDF", "type": "main", "index": 0 } ] ] }, "Descargar PDF": { "main": [ [ { "node": "Extraer texto PDF", "type": "main", "index": 0 } ] ] }, "Extraer texto PDF": { "main": [ [ { "node": "Parsear planilla PDF", "type": "main", "index": 0 } ] ] }, "Parsear planilla PDF": { "main": [ [ { "node": "Es DETALLE", "type": "main", "index": 0 }, { "node": "Es REVISION", "type": "main", "index": 0 } ] ] }, "Es DETALLE": { "main": [ [ { "node": "Guardar DETALLE_EXTRAIDO", "type": "main", "index": 0 }, { "node": "Consolidar RESUMEN_2025", "type": "main", "index": 0 } ] ] }, "Es REVISION": { "main": [ [ { "node": "Guardar REVISION", "type": "main", "index": 0 } ] ] }, "Consolidar RESUMEN_2025": { "main": [ [ { "node": "Guardar RESUMEN_2025", "type": "main", "index": 0 } ] ] }, "Webhook": { "main": [ [ { "node": "Limpiar DETALLE_EXTRAIDO", "type": "main", "index": 0 } ] ] } }, "authors": "Isaac Aracena", "name": "Version de60abc3", "description": "", "autosaved": false, "workflowPublishHistory": [ { "createdAt": "2026-06-05T19:23:26.084Z", "id": 1164, "workflowId": "l8koc0x9iIFqKn7P", "versionId": "de60abc3-a901-4638-bcee-bcf23b016760", "event": "activated", "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" } ] } }