diff --git a/nomina-panama-v2.json b/nomina-panama-v2.json new file mode 100644 index 0000000..4917320 --- /dev/null +++ b/nomina-panama-v2.json @@ -0,0 +1,1713 @@ +{ + "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" + } + ] + } +} \ No newline at end of file