Files
nomina-panam-v2/nomina-panam-v2.json

1713 lines
68 KiB
JSON

{
"updatedAt": "2026-06-05T18:52:22.870Z",
"createdAt": "2026-05-19T19:59:05.795Z",
"id": "l8koc0x9iIFqKn7P",
"name": "Nomina Panam谩 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": "0e82ae7f-8d48-4322-b3e3-9d12808b531e",
"activeVersionId": "0e82ae7f-8d48-4322-b3e3-9d12808b531e",
"versionCounter": 165,
"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 <iaracena@gomezleemarketing.com>",
"type": "personal",
"icon": null,
"description": null,
"creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
}
}
],
"tags": [],
"activeVersion": {
"updatedAt": "2026-06-05T18:52:25.000Z",
"createdAt": "2026-06-05T18:52:22.872Z",
"versionId": "0e82ae7f-8d48-4322-b3e3-9d12808b531e",
"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 0e82ae7f",
"description": "",
"autosaved": false,
"workflowPublishHistory": [
{
"createdAt": "2026-06-05T18:52:25.483Z",
"id": 1160,
"workflowId": "l8koc0x9iIFqKn7P",
"versionId": "0e82ae7f-8d48-4322-b3e3-9d12808b531e",
"event": "activated",
"userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
}
]
}
}