diff --git a/Finiquitos GT Julio 2026 - MASIVO.json b/Finiquitos GT Julio 2026 - MASIVO.json new file mode 100644 index 0000000..ee8542f --- /dev/null +++ b/Finiquitos GT Julio 2026 - MASIVO.json @@ -0,0 +1,223 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + -224, + -32 + ], + "id": "26af28df-70b2-41dc-9e62-4b35f13cbeeb", + "name": "Manual Trigger" + }, + { + "parameters": { + "url": "https://sheets.googleapis.com/v4/spreadsheets/1Ym9L_-uTXDZlFOUyq5vIDdnkyVx1nkBrlLmbXo2NI3I/values/Consolidado!A:Z?majorDimension=ROWS&valueRenderOption=FORMATTED_VALUE", + "authentication": "predefinedCredentialType", + "nodeCredentialType": "googleOAuth2Api", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -48, + -32 + ], + "id": "21c02e9c-1e02-42b2-8b0f-cf1c64a6c73f", + "name": "Leer Consolidado", + "credentials": { + "googleOAuth2Api": { + "id": "eHseMeH39kRcXgOF", + "name": "Google account 2" + } + } + }, + { + "parameters": { + "jsCode": "const respuesta = $input.first().json || {};\nconst values = respuesta.values;\n\nif (!Array.isArray(values) || values.length < 2) {\n throw new Error(\n 'La hoja Consolidado no contiene encabezados y empleados.'\n );\n}\n\n/*\n * CONFIGURACIÓN DE PRUEBA\n *\n * LIMITE = 3:\n * - Anderson será detectado por su carpeta existente y se omitirá.\n * - Los dos empleados siguientes serán procesados.\n *\n * Cuando todo quede validado:\n * LIMITE = 9999\n */\nconst CONFIG = {\n DESDE_FILA: 55,\n HASTA_FILA: 55,\n LIMITE: 1\n};\n\nfunction normalizar(valor) {\n return String(valor ?? '')\n .trim()\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-z0-9\\s]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nconst encabezadosOriginales =\n values[0].map(valor => String(valor ?? '').trim());\n\nconst encabezadosNormalizados =\n encabezadosOriginales.map(normalizar);\n\nfunction buscarColumna(...nombres) {\n for (const nombre of nombres.map(normalizar)) {\n const index =\n encabezadosNormalizados.indexOf(nombre);\n\n if (index !== -1) {\n return index;\n }\n }\n\n return -1;\n}\n\nconst columnaNombre = buscarColumna(\n 'Nombre',\n 'Nombre completo',\n 'Colaborador'\n);\n\nif (columnaNombre === -1) {\n throw new Error(\n 'No encontré la columna Nombre en Consolidado.'\n );\n}\n\nconst empleados = [];\n\nfor (let index = 1; index < values.length; index++) {\n const numeroFila = index + 1;\n\n if (\n numeroFila < CONFIG.DESDE_FILA ||\n numeroFila > CONFIG.HASTA_FILA\n ) {\n continue;\n }\n\n const fila = values[index] || [];\n\n const empleado = String(\n fila[columnaNombre] ?? ''\n ).trim();\n\n if (!empleado) {\n continue;\n }\n\n const nombreNormalizado =\n normalizar(empleado);\n\n if (\n nombreNormalizado === 'total' ||\n nombreNormalizado.startsWith('total ')\n ) {\n continue;\n }\n\n const primerNombre =\n empleado.split(/\\s+/)[0];\n\n empleados.push({\n json: {\n filaConsolidado: numeroFila,\n empleado,\n primerNombre,\n nombreNormalizado,\n\n // Estos datos permiten que el subworkflow\n // no tenga que leer nuevamente el Sheet.\n encabezadosOriginales,\n filaOriginal: fila\n }\n });\n\n if (empleados.length >= CONFIG.LIMITE) {\n break;\n }\n}\n\nif (!empleados.length) {\n throw new Error(\n 'No se encontraron empleados para procesar.'\n );\n}\n\nreturn empleados;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 160, + -32 + ], + "id": "8d146923-f1e5-45c7-884c-0bafd16310ea", + "name": "Preparar lista de empleados" + }, + { + "parameters": { + "options": {} + }, + "type": "n8n-nodes-base.splitInBatches", + "typeVersion": 3, + "position": [ + 544, + -48 + ], + "id": "bb541f0d-0380-4881-bf6d-fe7adbebf8d5", + "name": "Procesar empleados uno por uno" + }, + { + "parameters": { + "workflowId": { + "__rl": true, + "value": "KBV3AEEpys8KqPLX", + "mode": "list", + "cachedResultUrl": "/workflow/KBV3AEEpys8KqPLX", + "cachedResultName": "Finiquitos GT - Procesar 1 empleado" + }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [], + "attemptToConvertTypes": false, + "convertFieldsToString": true + }, + "options": { + "waitForSubWorkflow": true + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.3, + "position": [ + 752, + 160 + ], + "id": "44ec4e36-032a-434e-be0a-637d94067fce", + "name": "Ejecutar procesador individual", + "onError": "continueErrorOutput" + }, + { + "parameters": { + "amount": 1 + }, + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + 1024, + 384 + ], + "id": "a98ccd8d-1565-4716-b10c-b14d2d1191b9", + "name": "Esperar entre empleados", + "webhookId": "db50e51b-0a0e-44a4-aca0-712c7d899e6a" + }, + { + "parameters": { + "jsCode": "const items = $input.all().map(\n item => item.json || {}\n);\n\nconst resumen = {\n totalRecibidos: items.length,\n procesadosOk: 0,\n omitidosCarpetaExistente: 0,\n requierenRevision: 0,\n pendientesDocumentoCheque: 0,\n resultados: items\n};\n\nfor (const item of items) {\n if (item.estado === 'PROCESADO_OK') {\n resumen.procesadosOk++;\n }\n\n if (\n item.estado ===\n 'OMITIDO_CARPETA_EXISTENTE'\n ) {\n resumen.omitidosCarpetaExistente++;\n }\n\n if (\n item.estado !== 'PROCESADO_OK' &&\n item.estado !==\n 'OMITIDO_CARPETA_EXISTENTE'\n ) {\n resumen.requierenRevision++;\n }\n\n if (\n item.pendienteDocumentoCheque === true\n ) {\n resumen.pendientesDocumentoCheque++;\n }\n}\n\nreturn [\n {\n json: resumen\n }\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 752, + -144 + ], + "id": "19073876-c76d-481b-9ebf-0cffa3e0086a", + "name": "Resumen ejecución" + }, + { + "parameters": { + "content": "# 👥 PROCESAMIENTO MASIVO DE FINIQUITOS — GUATEMALA\n\nOrquesta la generación de finiquitos para todos los empleados incluidos en el consolidado de julio de 2026.\n\nFuncionamiento:\n\n1. La ejecución se inicia manualmente.\n2. Se consulta el consolidado con la lista de empleados.\n3. Se limpian y preparan los registros que deben procesarse.\n4. Los empleados se recorren de forma individual y controlada.\n5. Para cada persona se ejecuta el workflow encargado de generar su expediente y documentos.\n6. Después de cada intento se aplica una espera antes de continuar con el siguiente empleado.\n7. Cuando termina la lista completa, se construye un resumen general de la ejecución.\n\nEl procesador individual puede encargarse de:\n\n- Validar al empleado.\n- Consultar su información en BambooHR.\n- Crear su carpeta en Google Drive.\n- Descargar y guardar el DPI.\n- Generar voucher, finiquito y formato de cheque.\n- Actualizar el consolidado.\n- Devolver el resultado del empleado procesado.\n\nReglas:\n\n- Procesar solamente los empleados preparados desde el consolidado.\n- Ejecutar un empleado a la vez para evitar sobrecarga y conflictos.\n- Continuar con el siguiente registro aunque un empleado devuelva error.\n- Conservar por separado los resultados exitosos y fallidos.\n- Evitar procesar dos veces al mismo empleado dentro de la ejecución.\n- No generar el resumen hasta que todos los registros hayan sido recorridos.\n- El resumen final debe indicar procesados, exitosos, fallidos y motivos de error disponibles.", + "height": 880, + "width": 2352, + "color": "#535719" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1024, + -256 + ], + "id": "8120088f-4f6c-44dd-8caf-d551c47d6bae", + "name": "Sticky Note" + } + ], + "connections": { + "Manual Trigger": { + "main": [ + [ + { + "node": "Leer Consolidado", + "type": "main", + "index": 0 + } + ] + ] + }, + "Leer Consolidado": { + "main": [ + [ + { + "node": "Preparar lista de empleados", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar lista de empleados": { + "main": [ + [ + { + "node": "Procesar empleados uno por uno", + "type": "main", + "index": 0 + } + ] + ] + }, + "Procesar empleados uno por uno": { + "main": [ + [ + { + "node": "Resumen ejecución", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Ejecutar procesador individual", + "type": "main", + "index": 0 + } + ] + ] + }, + "Ejecutar procesador individual": { + "main": [ + [ + { + "node": "Esperar entre empleados", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Esperar entre empleados", + "type": "main", + "index": 0 + } + ] + ] + }, + "Esperar entre empleados": { + "main": [ + [ + { + "node": "Procesar empleados uno por uno", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "meta": { + "instanceId": "b4b77b17af092830e794eef639ce2f6d7daccf7eddc075060b03b3b6545aac70" + } +} \ No newline at end of file