From 1920487fab0ac12de79e872aa19e8815908477ff Mon Sep 17 00:00:00 2001 From: Isaac_Aracena Date: Sun, 23 Aug 2026 04:01:01 +0000 Subject: [PATCH] =?UTF-8?q?Update=20backup:=20Portal=20de=20Verificaci?= =?UTF-8?q?=C3=B3n=20de=20N=C3=B3mina=20-=20Bono=2014=20Guatemala?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ificacion-de-nomina-bono-14-guatemala.json | 656 +++++++++--------- 1 file changed, 328 insertions(+), 328 deletions(-) diff --git a/portal-de-verificacion-de-nomina-bono-14-guatemala.json b/portal-de-verificacion-de-nomina-bono-14-guatemala.json index d08ba42..4bbf7ef 100644 --- a/portal-de-verificacion-de-nomina-bono-14-guatemala.json +++ b/portal-de-verificacion-de-nomina-bono-14-guatemala.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-08-14T12:11:58.924Z", + "updatedAt": "2026-08-19T15:20:43.531Z", "createdAt": "2026-08-12T12:10:09.908Z", "id": "ox03EQlSmgjUCQuK", "name": "Portal de Verificación de Nómina - Bono 14 Guatemala", @@ -17,10 +17,10 @@ "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ - 464, - 2736 + 1376, + 3120 ], - "id": "e3fa9074-3d8c-4986-a921-750c71cc5eee", + "id": "67b62f15-2b30-4b21-b32b-4637f3f15c1a", "name": "Webhook", "webhookId": "83f02b5a-2322-42a3-9ca9-263622bd7a34" }, @@ -31,10 +31,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 672, - 2736 + 1584, + 3120 ], - "id": "57bcbea7-e1f7-44be-a754-6758d0ff48b9", + "id": "ce70d108-f876-4c71-83a8-21d84d6a81ae", "name": "Preparar entrada app" }, { @@ -56,10 +56,10 @@ "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ - 11888, - 3120 + 12800, + 3504 ], - "id": "fb71b850-d364-4c3e-aa2d-f29684c272fd", + "id": "c8a057a0-324e-49d5-9ada-d74174be60c3", "name": "Respond to Webhook" }, { @@ -69,10 +69,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 1344, - 1920 + 2256, + 2304 ], - "id": "c3fcbd08-860d-4940-8d0e-55142327d07e", + "id": "a4db3060-7fdd-48c2-89c8-7a9ba31a4992", "name": "Parsear CSV banco Bono 14 GT" }, { @@ -84,10 +84,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 7152, - 3280 + 8064, + 3664 ], - "id": "e8a66686-6888-4b30-9f0e-38e8d896d57e", + "id": "8fd1542a-7679-4a4c-a535-3f6ff731837e", "name": "Merge" }, { @@ -97,10 +97,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 7824, - 3120 + 8736, + 3504 ], - "id": "9ab13ae8-f5f1-439a-84cf-875b06ba593c", + "id": "e795db2e-7fb8-41b8-be02-f86901ae6fc8", "name": "Cruzar Bono 14 vs Banco" }, { @@ -115,10 +115,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1344, - 2112 + 2256, + 2496 ], - "id": "b7121eac-c58a-4378-bf17-8866ffc818a0", + "id": "4d882ac2-bb8b-4c08-907b-bda48da4cf98", "name": "Extract Bono14 - P&G", "retryOnFail": false }, @@ -134,10 +134,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1344, - 2352 + 2256, + 2736 ], - "id": "dd787c02-74cb-453f-8611-b515b026c6ab", + "id": "44039f6d-45f5-40b5-9681-c82dd1aeb23e", "name": "Extract Bono14 - Motorola", "retryOnFail": false, "onError": "continueRegularOutput" @@ -154,10 +154,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1344, - 2592 + 2256, + 2976 ], - "id": "9dd753e9-e9e7-47e9-8dce-c9ceef7accfd", + "id": "17e8a8ba-2fc1-442b-b7fd-0e3ecf89ca83", "name": "Extract Bono14 - Whirlpool", "retryOnFail": false, "onError": "continueRegularOutput" @@ -174,10 +174,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 2784 + 2272, + 3168 ], - "id": "ef502b8a-3ea6-42a9-b911-70d5e72ea877", + "id": "a66c9e89-6dce-4fc6-aa39-52f5a6199b43", "name": "Extract Bono14 - PMI", "retryOnFail": false, "onError": "continueRegularOutput" @@ -194,10 +194,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 2992 + 2272, + 3376 ], - "id": "db4c4942-830e-47c2-a8e2-982252a1ebc8", + "id": "0b07c236-68e9-4860-9a23-040f647c119f", "name": "Extract Bono14 - Nestle", "retryOnFail": false, "onError": "continueRegularOutput" @@ -214,10 +214,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 3200 + 2272, + 3584 ], - "id": "14b7b95a-d583-4788-a8b0-7afab135d0e4", + "id": "3305f9a2-702c-4c9a-ae23-ee50cb7fb199", "name": "Extract Bono14 - GLM Q", "retryOnFail": false, "onError": "continueRegularOutput" @@ -234,10 +234,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1376, - 3536 + 2288, + 3920 ], - "id": "2c5de156-7bf8-4d69-b71d-bcb38f98b332", + "id": "9a7202fe-4f18-4b05-bfbd-f864370c3146", "name": "Extract Bono14 - GLM USD", "retryOnFail": false, "onError": "continueRegularOutput" @@ -254,10 +254,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 3872 + 2272, + 4256 ], - "id": "24b36bbe-2a0b-4058-a4c6-a47f5103991e", + "id": "2b1aa77d-4ca7-43f0-aa7b-6a8f9137e3f4", "name": "Extract Bono14 - Resumen 1", "retryOnFail": false, "onError": "continueRegularOutput" @@ -274,10 +274,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 4160 + 2272, + 4544 ], - "id": "cd86a313-2e21-435c-9725-31098528cae1", + "id": "116bbb2b-aff9-4b5a-b235-2bb0e286e023", "name": "Extract Bono14 - Resumen 2", "retryOnFail": false, "onError": "continueRegularOutput" @@ -289,10 +289,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 5568, - 3888 + 6480, + 4272 ], - "id": "926de4e1-8954-4f6f-830f-2e6f724f4c6e", + "id": "00a4b9dd-308f-4cba-a769-45f0d28bf193", "name": "Normalizar Bono 14" }, { @@ -300,10 +300,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 2368, - 2416 + 3280, + 2800 ], - "id": "ae4ba7a6-9e0c-492f-adff-5dd5bdb20692", + "id": "f8f1dc21-b749-47ff-b781-9dadf6710993", "name": "Merge Hojas 01-02" }, { @@ -311,10 +311,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 2560, - 2624 + 3472, + 3008 ], - "id": "14da77f8-838f-4bdf-90ed-4786990b145c", + "id": "91c9410a-016b-4954-9879-f53472ee471a", "name": "Merge Hojas 03" }, { @@ -322,10 +322,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 2848, - 2752 + 3760, + 3136 ], - "id": "eb0de9de-c940-45b9-8f5c-b427f9fa198c", + "id": "1eecede6-94a1-4d6a-b5d7-9979d77eb181", "name": "Merge Hojas 04" }, { @@ -333,10 +333,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 3264, - 2880 + 4176, + 3264 ], - "id": "0910a078-76c0-4010-8daa-ffd11381cfa8", + "id": "731a7298-29d5-45af-aca4-0601ee281275", "name": "Merge Hojas 05" }, { @@ -344,10 +344,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 3568, - 3024 + 4480, + 3408 ], - "id": "eda17990-ef1d-4b96-a17c-3c68a3617ee6", + "id": "e263b8a6-fa74-4539-9e91-3667fba26033", "name": "Merge Hojas 06" }, { @@ -355,10 +355,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 3904, - 3296 + 4816, + 3680 ], - "id": "efe11e4e-9fe0-4836-b815-beeaedec2b3c", + "id": "d0d8c656-4ae9-4758-a6bf-a5e20ace59b1", "name": "Merge Hojas 07" }, { @@ -366,10 +366,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 4160, - 3424 + 5072, + 3808 ], - "id": "7ff3bcf8-0bfd-43da-bc6b-96c4bd236034", + "id": "a94df28b-8587-455a-9754-e3b4fbaebe3d", "name": "Merge Hojas 08" }, { @@ -377,10 +377,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 4464, - 3808 + 5376, + 4192 ], - "id": "3751c607-4ef5-423e-b611-592bb9bad758", + "id": "3c95b12a-cb75-4e48-9add-ab8bf735095e", "name": "Merge Hojas 09" }, { @@ -395,10 +395,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 4416 + 2272, + 4800 ], - "id": "9e0d0649-cecb-4ddf-b499-1c0ccd84cc09", + "id": "da0a0abb-38c6-4dd3-8c61-fd614cf5da27", "name": "Extract Bono14 - Resumen 3", "retryOnFail": false, "onError": "continueRegularOutput" @@ -408,23 +408,23 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 4768, - 3920 + 5680, + 4304 ], - "id": "1a6b94fa-bee4-4c72-8adc-389a0084a0b2", + "id": "67800564-372c-4921-b8aa-8be532a9c433", "name": "Merge Hojas " }, { "parameters": { - "jsCode": "const data = $input.first().json || {};\n\nfunction normalizeText(value) {\n return String(value ?? '')\n .replace(/\\uFEFF/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction roundMoney(value) {\n return Math.round((Number(value) || 0) * 100) / 100;\n}\n\nfunction firstValue(value) {\n if (Array.isArray(value)) {\n return value.filter(Boolean).join(' / ');\n }\n return normalizeText(value);\n}\n\nfunction currencyOf(row) {\n return normalizeText(row.currency || 'QTZ').toUpperCase() || 'QTZ';\n}\n\nfunction mainReportSense(row, difference) {\n const category = normalizeText(row.category).toLowerCase();\n const subcategory = normalizeText(row.subcategory).toLowerCase();\n\n if (category === 'posible_cuenta_mal_digitada') return 'Revisar cuenta';\n\n if (\n subcategory === 'nomina_con_cuenta_sin_pago_banco' ||\n (Number(row.bank_amount ?? row.bankAmount ?? 0) === 0 &&\n Number(row.payroll_amount ?? row.payrollAmount ?? 0) > 0)\n ) {\n return 'No aparece pagado en banco';\n }\n\n if (difference > 0) return 'Se pagó de menos';\n if (difference < 0) return 'Se pagó de más';\n return 'Revisar';\n}\n\nconst metadata = data.metadata || {};\nconst summary = data.summary || {};\nconst rows = Array.isArray(data.rows) ? data.rows : [];\nconst bankWithoutBamboo = Array.isArray(data.bankWithoutBamboo)\n ? data.bankWithoutBamboo\n : [];\n\nconst year = Number(metadata.year || 0);\nconst periodLabel =\n metadata.period_label ||\n `Bono 14 ${year}`;\n\nconst spreadsheetTitle =\n `Cruce de Cuentas GLM GT - Bono 14 ${year}`;\n\nconst mainReportSubtitle =\n `Bono 14 vs. Banco · Guatemala · ${periodLabel}`;\nconst bancoSinBambooSubtitle =\n `Pagos de Bono 14 en banco sin empleado identificado en BambooHR · Guatemala · ${periodLabel}`;\nconst cuentaMalDigitadaSubtitle =\n `Posible Cuenta Mal Digitada · Bono 14 · Guatemala · ${periodLabel}`;\n\nconst cuentaMalDigitadaCases = rows.filter(\n (row) => row.category === 'posible_cuenta_mal_digitada'\n);\nconst hasCuentaMalDigitada = cuentaMalDigitadaCases.length > 0;\n\nconst sheetIds = {\n nominaVsBanco: 101,\n bancoSinNomina: 102,\n bancoSinBamboo: 103,\n diferenciasNombreBanco: 104,\n cuentaMalDigitada: 105,\n resumen: 106,\n};\n\nconst sheetTitles = {\n nominaVsBanco: '01 Bono 14 vs Banco',\n bancoSinNomina: '02 Banco sin Bono 14',\n bancoSinBamboo: '03 Banco sin Bamboo',\n diferenciasNombreBanco: '04 Diferencias nombre banco',\n cuentaMalDigitada: '05 Cuenta Mal Digitada',\n resumen: hasCuentaMalDigitada ? '06 Resumen' : '05 Resumen',\n};\n\nconst mainHeader = [\n '#',\n 'Empleado',\n 'Cuenta',\n 'Moneda',\n 'Monto Bono 14',\n 'Monto Banco',\n 'Diferencia',\n 'Sentido',\n 'Estado',\n 'Resolución',\n];\n\nconst mainRows = rows\n .filter((row) => row.category === 'discrepancia')\n .map((row, index) => {\n const payrollAmount = roundMoney(row.payroll_amount ?? row.payrollAmount ?? 0);\n const bankAmount = roundMoney(row.bank_amount ?? row.bankAmount ?? 0);\n const difference = roundMoney(\n row.difference ?? (payrollAmount - bankAmount)\n );\n\n return [\n index + 1,\n normalizeText(row.employee_name || row.employee || ''),\n normalizeText(\n row.bank_account ||\n row.bankAccount ||\n row.payroll_account ||\n row.payrollAccount ||\n row.account ||\n ''\n ),\n currencyOf(row),\n payrollAmount,\n bankAmount,\n difference,\n mainReportSense(row, difference),\n normalizeText(row.status || 'Riesgo').toUpperCase(),\n '',\n ];\n });\n\nconst mainValues = [\n ['GOMEZLEE MARKETING', '', '', '', '', '', '', '', '', ''],\n [mainReportSubtitle, '', '', '', '', '', '', '', '', ''],\n ['', '', '', '', '', '', '', '', '', ''],\n mainHeader,\n ...mainRows,\n];\n\nconst bancoSinNominaHeader = [\n 'Empleado Banco',\n 'Cuenta Banco',\n 'Moneda',\n 'Monto Banco',\n 'Estado',\n 'Observación',\n 'Resolución',\n];\nconst bancoSinNominaRows = rows\n .filter((row) => row.category === 'banco_sin_nomina')\n .map((row) => [\n normalizeText(row.employee_name || row.employee || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? 0),\n normalizeText(row.status || ''),\n normalizeText(row.observation || ''),\n '',\n ]);\n\nconst bancoSinBambooHeader = [\n '#',\n 'Nombre en banco',\n 'Nombre del cuentahabiente',\n 'Cuenta',\n 'Moneda',\n 'Monto en banco',\n 'Número de envío',\n 'Estado',\n 'Resolución',\n];\nconst bancoSinBambooRows = bankWithoutBamboo.map((row, index) => [\n index + 1,\n normalizeText(row.bank_name_file || row.employee_name || ''),\n normalizeText(row.bank_account_holder || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? row.amount ?? 0),\n firstValue(row.shipment_numbers || row.shipment_number || ''),\n 'PENDIENTE REVISIÓN',\n '',\n]);\nconst bancoSinBambooValues = [\n ['GOMEZLEE MARKETING', '', '', '', '', '', '', '', ''],\n [bancoSinBambooSubtitle, '', '', '', '', '', '', '', ''],\n ['', '', '', '', '', '', '', '', ''],\n bancoSinBambooHeader,\n ...bancoSinBambooRows,\n];\n\nconst diferenciasNombreHeader = [\n 'Nombre en Archivo',\n 'Nombre del Cuentahabiente',\n 'Cuenta Destino',\n 'Moneda',\n 'Monto',\n 'Número de envío',\n 'Número de plan',\n 'Archivo',\n 'Estado',\n 'Observación',\n 'Resolución',\n];\nconst diferenciasNombreRows = rows\n .filter((row) => row.category === 'diferencia_nombre_banco')\n .map((row) => [\n normalizeText(row.bank_name_file || row.employee_name || ''),\n normalizeText(row.bank_account_holder || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? 0),\n normalizeText(row.shipment_number || ''),\n normalizeText(row.plan_number || ''),\n normalizeText(row.source_file || ''),\n normalizeText(row.status || ''),\n normalizeText(row.observation || ''),\n '',\n ]);\n\nfunction accountValues(value) {\n const values = Array.isArray(value)\n ? value\n : String(value ?? '').split(/\\s*(?:\\/|;|,|\\by\\b)\\s*/i);\n\n return values\n .map((item) => String(item ?? '').replace(/\\.0$/g, '').replace(/\\D/g, '').trim())\n .filter((item) => item.length >= 7 && !/^0+$/.test(item));\n}\n\nfunction payrollAccountsForWrongAccount(row) {\n const candidates = [\n row.payroll_account,\n row.payrollAccount,\n ...(Array.isArray(row.source_rows)\n ? row.source_rows.flatMap((sourceRow) => [\n sourceRow.account,\n sourceRow.payroll_account,\n sourceRow.payrollAccount,\n ])\n : []),\n ];\n return Array.from(new Set(candidates.flatMap(accountValues)));\n}\n\nfunction bankAccountForWrongAccount(row) {\n return firstValue(row.bank_account || row.bankAccount || row.account || '');\n}\n\nfunction moneyLabel(value, currency) {\n const amount = Math.abs(roundMoney(value)).toLocaleString('en-US', {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n return `${currency === 'USD' ? 'USD ' : 'Q'}${amount}`;\n}\n\nfunction wrongAccountTotalStatus(row) {\n const payrollAmount = roundMoney(row.payroll_amount ?? row.payrollAmount ?? 0);\n const bankAmount = roundMoney(row.bank_amount ?? row.bankAmount ?? 0);\n const difference = roundMoney(payrollAmount - bankAmount);\n const currency = currencyOf(row);\n\n if (Math.abs(difference) <= 0.02) {\n return 'El total del Bono 14 coincide con el total del banco.';\n }\n if (difference > 0) {\n return `El total del Bono 14 supera el total del banco por ${moneyLabel(difference, currency)}.`;\n }\n return `El total pagado por el banco supera el Bono 14 por ${moneyLabel(difference, currency)}.`;\n}\n\nfunction wrongAccountFinding(row) {\n const existing = normalizeText(row.observation || '');\n if (existing) return existing;\n\n const payrollAccounts = payrollAccountsForWrongAccount(row);\n const bankAccount = bankAccountForWrongAccount(row);\n return (\n `El empleado presenta una posible inconsistencia entre la cuenta del Excel de Bono 14 ` +\n `(${payrollAccounts.join(' y ') || 'sin cuenta identificada'}) y la cuenta utilizada por el banco ` +\n `(${bankAccount || 'sin cuenta identificada'}).`\n );\n}\n\nconst cuentaMalDigitadaHeader = ['#', 'Campo', 'Detalle', 'Resolución'];\nconst cuentaMalDigitadaRows = [];\n\nfor (let index = 0; index < cuentaMalDigitadaCases.length; index++) {\n const row = cuentaMalDigitadaCases[index];\n const payrollAccounts = payrollAccountsForWrongAccount(row);\n const bankAccount = bankAccountForWrongAccount(row);\n const fields = [\n ['Empleado', normalizeText(row.employee_name || row.employee || '')],\n ['Moneda', currencyOf(row)],\n [\n 'Cuentas registradas en el Excel de Bono 14',\n payrollAccounts.join(' y ') || 'No se identificó una cuenta válida en el Excel de Bono 14.',\n ],\n [\n 'Cuenta utilizada por el banco',\n bankAccount || 'No se identificó una cuenta válida en el banco.',\n ],\n ['Estado del total', wrongAccountTotalStatus(row)],\n ['Hallazgo', wrongAccountFinding(row)],\n [\n 'Clasificación',\n 'Posible cuenta mal digitada — revisar y unificar la cuenta del Bono 14 con la utilizada por el banco.',\n ],\n ];\n\n fields.forEach((field, fieldIndex) => {\n cuentaMalDigitadaRows.push([\n fieldIndex === 0 ? index + 1 : '',\n field[0],\n field[1],\n '',\n ]);\n });\n}\n\nconst cuentaMalDigitadaValues = [\n ['GOMEZLEE MARKETING', '', '', ''],\n [cuentaMalDigitadaSubtitle, '', '', ''],\n ['', '', '', ''],\n cuentaMalDigitadaHeader,\n ...cuentaMalDigitadaRows,\n];\n\nconst resumenHeader = ['Indicador', 'Valor'];\nconst resumenRows = [\n ['Reporte', `Bono 14 ${year}`],\n ['Período de devengo', periodLabel],\n ['Coincidencias', Number(summary.coincidencias || 0)],\n ['Discrepancias', Number(summary.discrepancias || 0)],\n ['Banco sin Bono 14', Number(summary.bancoSinNomina || 0)],\n ['Banco sin Bamboo', Number(summary.bancoSinBamboo || 0)],\n ['Bono 14 sin cuenta', Number(summary.nominaSinCuenta || 0)],\n ['Diferencias nombre banco', Number(summary.diferenciasNombreBanco || 0)],\n ['Posibles cuentas mal digitadas', Number(summary.posiblesCuentasMalDigitadas || 0)],\n ['Transacciones bancarias', Number(summary.transaccionesBanco || 0)],\n ['Total Bono 14 QTZ', roundMoney(summary.totalNominaQTZ || summary.totalNomina || 0)],\n ['Total Banco QTZ', roundMoney(summary.totalBancoQTZ || summary.totalBanco || 0)],\n ['Diferencia QTZ', roundMoney(summary.diferenciaQTZ || summary.diferenciaTotal || 0)],\n ['Total Bono 14 USD', roundMoney(summary.totalNominaUSD || 0)],\n ['Total Banco USD', roundMoney(summary.totalBancoUSD || 0)],\n ['Diferencia USD', roundMoney(summary.diferenciaUSD || 0)],\n];\n\nfunction buildSheetValues(header, bodyRows) {\n return [header, ...bodyRows];\n}\n\nconst valueData = [\n { range: `'${sheetTitles.nominaVsBanco}'!A1:J`, values: mainValues },\n {\n range: `'${sheetTitles.bancoSinNomina}'!A1:G`,\n values: buildSheetValues(bancoSinNominaHeader, bancoSinNominaRows),\n },\n { range: `'${sheetTitles.bancoSinBamboo}'!A1:I`, values: bancoSinBambooValues },\n {\n range: `'${sheetTitles.diferenciasNombreBanco}'!A1:K`,\n values: buildSheetValues(diferenciasNombreHeader, diferenciasNombreRows),\n },\n ...(hasCuentaMalDigitada\n ? [{ range: `'${sheetTitles.cuentaMalDigitada}'!A1:D`, values: cuentaMalDigitadaValues }]\n : []),\n {\n range: `'${sheetTitles.resumen}'!A1:B`,\n values: buildSheetValues(resumenHeader, resumenRows),\n },\n];\n\nfunction color(red, green, blue) {\n return { red, green, blue };\n}\n\nconst BLUE = color(0.29, 0.49, 0.58);\nconst GREEN = color(0.42, 0.76, 0.29);\nconst WHITE = color(1, 1, 1);\nconst LIGHT_RED = color(1, 0.92, 0.92);\nconst LIGHT_YELLOW = color(1, 0.97, 0.84);\nconst LIGHT_GREEN = color(0.91, 0.96, 0.91);\nconst LIGHT_BLUE = color(0.93, 0.96, 0.98);\nconst DARK = color(0.29, 0.29, 0.29);\n\nfunction repeatFormat(sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex, userEnteredFormat, fields) {\n return {\n repeatCell: {\n range: { sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex },\n cell: { userEnteredFormat },\n fields,\n },\n };\n}\n\nfunction freeze(sheetId, frozenRowCount) {\n return {\n updateSheetProperties: {\n properties: { sheetId, gridProperties: { frozenRowCount } },\n fields: 'gridProperties.frozenRowCount',\n },\n };\n}\n\nfunction setWidth(sheetId, startIndex, endIndex, pixelSize) {\n return {\n updateDimensionProperties: {\n range: { sheetId, dimension: 'COLUMNS', startIndex, endIndex },\n properties: { pixelSize },\n fields: 'pixelSize',\n },\n };\n}\n\nfunction autoRows(sheetId, endRowIndex) {\n return {\n autoResizeDimensions: {\n dimensions: {\n sheetId,\n dimension: 'ROWS',\n startIndex: 0,\n endIndex: Math.max(1, endRowIndex),\n },\n },\n };\n}\n\nfunction basicFilter(sheetId, startRowIndex, endRowIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return {\n setBasicFilter: {\n filter: {\n range: {\n sheetId,\n startRowIndex,\n endRowIndex,\n startColumnIndex: 0,\n endColumnIndex,\n },\n },\n },\n };\n}\n\nfunction mergeRow(sheetId, rowIndex, endColumnIndex) {\n return {\n mergeCells: {\n range: {\n sheetId,\n startRowIndex: rowIndex,\n endRowIndex: rowIndex + 1,\n startColumnIndex: 0,\n endColumnIndex,\n },\n mergeType: 'MERGE_ALL',\n },\n };\n}\n\nfunction moneyFormat(sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return repeatFormat(\n sheetId,\n startRowIndex,\n endRowIndex,\n startColumnIndex,\n endColumnIndex,\n { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } },\n 'userEnteredFormat.numberFormat'\n );\n}\n\nfunction addProtection(sheetId, endRowIndex, resolutionColumnIndex = null, resolutionStartRowIndex = 1) {\n const protectedRange = {\n range: { sheetId },\n description: `Protección GLM · Bono 14 · ${sheetId}`,\n warningOnly: false,\n editors: {\n users: ['iaracena@gomezleemarketing.com'],\n domainUsersCanEdit: false,\n },\n };\n\n if (\n Number.isInteger(resolutionColumnIndex) &&\n Number.isInteger(endRowIndex) &&\n endRowIndex > resolutionStartRowIndex\n ) {\n protectedRange.unprotectedRanges = [\n {\n sheetId,\n startRowIndex: resolutionStartRowIndex,\n endRowIndex,\n startColumnIndex: resolutionColumnIndex,\n endColumnIndex: resolutionColumnIndex + 1,\n },\n ];\n }\n\n return { addProtectedRange: { protectedRange } };\n}\n\nfunction standardBodyFormat(sheetId, startRowIndex, endRowIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return repeatFormat(\n sheetId,\n startRowIndex,\n endRowIndex,\n 0,\n endColumnIndex,\n {\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { foregroundColor: DARK },\n },\n 'userEnteredFormat(verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nfunction headerFormat(sheetId, rowIndex, endColumnIndex) {\n return repeatFormat(\n sheetId,\n rowIndex,\n rowIndex + 1,\n 0,\n endColumnIndex,\n {\n backgroundColor: BLUE,\n horizontalAlignment: 'CENTER',\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { bold: true, foregroundColor: WHITE },\n },\n 'userEnteredFormat(backgroundColor,horizontalAlignment,verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nfunction titleFormat(sheetId, rowIndex, endColumnIndex, fontSize = 12) {\n return repeatFormat(\n sheetId,\n rowIndex,\n rowIndex + 1,\n 0,\n endColumnIndex,\n {\n backgroundColor: BLUE,\n horizontalAlignment: 'LEFT',\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { bold: true, fontSize, foregroundColor: WHITE },\n },\n 'userEnteredFormat(backgroundColor,horizontalAlignment,verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nconst formatRequests = [];\nfunction push(request) {\n if (request) formatRequests.push(request);\n}\n\n// 01 Bono 14 vs Banco\nconst mainEnd = mainValues.length;\npush(mergeRow(sheetIds.nominaVsBanco, 0, 10));\npush(mergeRow(sheetIds.nominaVsBanco, 1, 10));\npush(titleFormat(sheetIds.nominaVsBanco, 0, 10, 13));\npush(titleFormat(sheetIds.nominaVsBanco, 1, 10, 11));\npush(headerFormat(sheetIds.nominaVsBanco, 3, 10));\npush(standardBodyFormat(sheetIds.nominaVsBanco, 4, mainEnd, 10));\npush(moneyFormat(sheetIds.nominaVsBanco, 4, mainEnd, 4, 7));\npush(\n repeatFormat(\n sheetIds.nominaVsBanco,\n 4,\n mainEnd,\n 8,\n 9,\n {\n backgroundColor: LIGHT_RED,\n textFormat: { bold: true, foregroundColor: color(0.70, 0.20, 0.20) },\n horizontalAlignment: 'CENTER',\n },\n 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'\n )\n);\npush(basicFilter(sheetIds.nominaVsBanco, 3, mainEnd, 10));\npush(freeze(sheetIds.nominaVsBanco, 4));\n[\n [0, 1, 55], [1, 2, 220], [2, 3, 145], [3, 4, 75], [4, 7, 125],\n [7, 8, 185], [8, 9, 120], [9, 10, 230],\n].forEach(([s, e, px]) => push(setWidth(sheetIds.nominaVsBanco, s, e, px)));\npush(autoRows(sheetIds.nominaVsBanco, mainEnd));\n\nif (mainEnd > 4) {\n push({\n addConditionalFormatRule: {\n rule: {\n ranges: [{ sheetId: sheetIds.nominaVsBanco, startRowIndex: 4, endRowIndex: mainEnd, startColumnIndex: 6, endColumnIndex: 7 }],\n booleanRule: {\n condition: { type: 'CUSTOM_FORMULA', values: [{ userEnteredValue: '=$G5>0' }] },\n format: { backgroundColor: LIGHT_YELLOW, textFormat: { bold: true } },\n },\n },\n index: 0,\n },\n });\n push({\n addConditionalFormatRule: {\n rule: {\n ranges: [{ sheetId: sheetIds.nominaVsBanco, startRowIndex: 4, endRowIndex: mainEnd, startColumnIndex: 6, endColumnIndex: 7 }],\n booleanRule: {\n condition: { type: 'CUSTOM_FORMULA', values: [{ userEnteredValue: '=$G5<0' }] },\n format: { backgroundColor: LIGHT_RED, textFormat: { bold: true } },\n },\n },\n index: 0,\n },\n });\n}\npush(addProtection(sheetIds.nominaVsBanco, mainEnd, 9, 4));\n\n// 02 Banco sin Bono 14\nconst bankNoBonusEnd = 1 + bancoSinNominaRows.length;\npush(headerFormat(sheetIds.bancoSinNomina, 0, 7));\npush(standardBodyFormat(sheetIds.bancoSinNomina, 1, bankNoBonusEnd, 7));\npush(moneyFormat(sheetIds.bancoSinNomina, 1, bankNoBonusEnd, 3, 4));\npush(basicFilter(sheetIds.bancoSinNomina, 0, bankNoBonusEnd, 7));\npush(freeze(sheetIds.bancoSinNomina, 1));\n[[0,1,220],[1,2,145],[2,3,75],[3,4,125],[4,5,130],[5,6,320],[6,7,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.bancoSinNomina,s,e,px)));\npush(autoRows(sheetIds.bancoSinNomina, bankNoBonusEnd));\npush(addProtection(sheetIds.bancoSinNomina, bankNoBonusEnd, 6, 1));\n\n// 03 Banco sin Bamboo\nconst bambooEnd = bancoSinBambooValues.length;\npush(mergeRow(sheetIds.bancoSinBamboo, 0, 9));\npush(mergeRow(sheetIds.bancoSinBamboo, 1, 9));\npush(titleFormat(sheetIds.bancoSinBamboo, 0, 9, 13));\npush(titleFormat(sheetIds.bancoSinBamboo, 1, 9, 11));\npush(headerFormat(sheetIds.bancoSinBamboo, 3, 9));\npush(standardBodyFormat(sheetIds.bancoSinBamboo, 4, bambooEnd, 9));\npush(moneyFormat(sheetIds.bancoSinBamboo, 4, bambooEnd, 5, 6));\npush(\n repeatFormat(\n sheetIds.bancoSinBamboo,\n 4,\n bambooEnd,\n 7,\n 8,\n { backgroundColor: LIGHT_YELLOW, textFormat: { bold: true }, horizontalAlignment: 'CENTER' },\n 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'\n )\n);\npush(basicFilter(sheetIds.bancoSinBamboo, 3, bambooEnd, 9));\npush(freeze(sheetIds.bancoSinBamboo, 4));\n[[0,1,55],[1,2,220],[2,3,220],[3,4,145],[4,5,75],[5,6,125],[6,7,120],[7,8,155],[8,9,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.bancoSinBamboo,s,e,px)));\npush(autoRows(sheetIds.bancoSinBamboo, bambooEnd));\npush(addProtection(sheetIds.bancoSinBamboo, bambooEnd, 8, 4));\n\n// 04 Diferencias nombre banco\nconst diffNameEnd = 1 + diferenciasNombreRows.length;\npush(headerFormat(sheetIds.diferenciasNombreBanco, 0, 11));\npush(standardBodyFormat(sheetIds.diferenciasNombreBanco, 1, diffNameEnd, 11));\npush(moneyFormat(sheetIds.diferenciasNombreBanco, 1, diffNameEnd, 4, 5));\npush(basicFilter(sheetIds.diferenciasNombreBanco, 0, diffNameEnd, 11));\npush(freeze(sheetIds.diferenciasNombreBanco, 1));\n[[0,1,220],[1,2,220],[2,3,145],[3,4,75],[4,5,125],[5,7,115],[7,8,220],[8,9,130],[9,10,320],[10,11,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.diferenciasNombreBanco,s,e,px)));\npush(autoRows(sheetIds.diferenciasNombreBanco, diffNameEnd));\npush(addProtection(sheetIds.diferenciasNombreBanco, diffNameEnd, 10, 1));\n\n// 05 Cuenta Mal Digitada (opcional)\nif (hasCuentaMalDigitada) {\n const wrongEnd = cuentaMalDigitadaValues.length;\n push(mergeRow(sheetIds.cuentaMalDigitada, 0, 4));\n push(mergeRow(sheetIds.cuentaMalDigitada, 1, 4));\n push(titleFormat(sheetIds.cuentaMalDigitada, 0, 4, 13));\n push(titleFormat(sheetIds.cuentaMalDigitada, 1, 4, 11));\n push(headerFormat(sheetIds.cuentaMalDigitada, 3, 4));\n push(standardBodyFormat(sheetIds.cuentaMalDigitada, 4, wrongEnd, 4));\n push(freeze(sheetIds.cuentaMalDigitada, 4));\n [[0,1,55],[1,2,270],[2,3,640],[3,4,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.cuentaMalDigitada,s,e,px)));\n push(autoRows(sheetIds.cuentaMalDigitada, wrongEnd));\n push(addProtection(sheetIds.cuentaMalDigitada, wrongEnd, 3, 4));\n}\n\n// Resumen\nconst summaryEnd = 1 + resumenRows.length;\npush(headerFormat(sheetIds.resumen, 0, 2));\npush(standardBodyFormat(sheetIds.resumen, 1, summaryEnd, 2));\npush(\n repeatFormat(\n sheetIds.resumen,\n 1,\n summaryEnd,\n 0,\n 1,\n { backgroundColor: LIGHT_BLUE, textFormat: { bold: true, foregroundColor: DARK } },\n 'userEnteredFormat(backgroundColor,textFormat)'\n )\n);\npush(freeze(sheetIds.resumen, 1));\npush(setWidth(sheetIds.resumen, 0, 1, 310));\npush(setWidth(sheetIds.resumen, 1, 2, 260));\npush(autoRows(sheetIds.resumen, summaryEnd));\npush(addProtection(sheetIds.resumen, summaryEnd, null, 1));\n\nconst desiredSheets = [\n {\n properties: {\n sheetId: sheetIds.nominaVsBanco,\n title: sheetTitles.nominaVsBanco,\n index: 0,\n gridProperties: { rowCount: Math.max(200, mainEnd + 20), columnCount: 10 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.bancoSinNomina,\n title: sheetTitles.bancoSinNomina,\n index: 1,\n gridProperties: { rowCount: Math.max(200, bankNoBonusEnd + 20), columnCount: 7 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.bancoSinBamboo,\n title: sheetTitles.bancoSinBamboo,\n index: 2,\n gridProperties: { rowCount: Math.max(200, bambooEnd + 20), columnCount: 9 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.diferenciasNombreBanco,\n title: sheetTitles.diferenciasNombreBanco,\n index: 3,\n gridProperties: { rowCount: Math.max(200, diffNameEnd + 20), columnCount: 11 },\n },\n },\n ...(hasCuentaMalDigitada\n ? [\n {\n properties: {\n sheetId: sheetIds.cuentaMalDigitada,\n title: sheetTitles.cuentaMalDigitada,\n index: 4,\n gridProperties: {\n rowCount: Math.max(200, cuentaMalDigitadaValues.length + 20),\n columnCount: 4,\n },\n },\n },\n ]\n : []),\n {\n properties: {\n sheetId: sheetIds.resumen,\n title: sheetTitles.resumen,\n index: hasCuentaMalDigitada ? 5 : 4,\n gridProperties: { rowCount: 200, columnCount: 2 },\n },\n },\n];\n\nreturn [\n {\n json: {\n ok: data.ok ?? true,\n stage: 'google_sheet_bono14_preparado',\n metadata,\n summary,\n sheetIds,\n sheetTitles,\n spreadsheetTitle,\n createSpreadsheetBody: {\n properties: {\n title: spreadsheetTitle,\n locale: 'es_GT',\n timeZone: 'America/Guatemala',\n },\n sheets: desiredSheets,\n },\n valueBatchBody: {\n valueInputOption: 'USER_ENTERED',\n data: valueData,\n },\n formatBatchBody: {\n requests: formatRequests,\n },\n originalResponse: data,\n },\n },\n];" + "jsCode": "const data = $input.first().json || {};\n\nfunction normalizeText(value) {\n return String(value ?? '')\n .replace(/\\uFEFF/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction roundMoney(value) {\n return Math.round((Number(value) || 0) * 100) / 100;\n}\n\nfunction firstValue(value) {\n if (Array.isArray(value)) {\n return value.filter(Boolean).join(' / ');\n }\n return normalizeText(value);\n}\n\nfunction currencyOf(row) {\n return normalizeText(row.currency || 'QTZ').toUpperCase() || 'QTZ';\n}\n\nfunction mainReportSense(row, difference) {\n const category = normalizeText(row.category).toLowerCase();\n const subcategory = normalizeText(row.subcategory).toLowerCase();\n\n if (category === 'posible_cuenta_mal_digitada') return 'Revisar cuenta';\n\n if (\n subcategory === 'nomina_con_cuenta_sin_pago_banco' ||\n (Number(row.bank_amount ?? row.bankAmount ?? 0) === 0 &&\n Number(row.payroll_amount ?? row.payrollAmount ?? 0) > 0)\n ) {\n return 'No aparece pagado en banco';\n }\n\n if (difference > 0) return 'Se pagó de menos';\n if (difference < 0) return 'Se pagó de más';\n return 'Revisar';\n}\n\nconst metadata = data.metadata || {};\nconst summary = data.summary || {};\nconst rows = Array.isArray(data.rows) ? data.rows : [];\nconst bankWithoutBamboo = Array.isArray(data.bankWithoutBamboo)\n ? data.bankWithoutBamboo\n : [];\n\nconst year = Number(metadata.year || 0);\nconst periodLabel =\n metadata.period_label ||\n `Bono 14 ${year}`;\n\nconst spreadsheetTitle =\n `Cruce de Cuentas GLM GT - Bono 14 ${year}`;\n\nconst mainReportSubtitle =\n `Bono 14 vs. Banco · Guatemala · ${periodLabel}`;\nconst bancoSinBambooSubtitle =\n `Pagos de Bono 14 en banco sin empleado identificado en BambooHR · Guatemala · ${periodLabel}`;\nconst cuentaMalDigitadaSubtitle =\n `Posible Cuenta Mal Digitada · Bono 14 · Guatemala · ${periodLabel}`;\n\nconst cuentaMalDigitadaCases = rows.filter(\n (row) => row.category === 'posible_cuenta_mal_digitada'\n);\nconst hasCuentaMalDigitada = cuentaMalDigitadaCases.length > 0;\n\nconst sheetIds = {\n nominaVsBanco: 101,\n bancoSinNomina: 102,\n bancoSinBamboo: 103,\n diferenciasNombreBanco: 104,\n cuentaMalDigitada: 105,\n resumen: 106,\n};\n\nconst sheetTitles = {\n nominaVsBanco: '01 Bono 14 vs Banco',\n bancoSinNomina: '02 Banco sin Bono 14',\n bancoSinBamboo: '03 Banco sin Bamboo',\n diferenciasNombreBanco: '04 Diferencias nombre banco',\n cuentaMalDigitada: '05 Cuenta Mal Digitada',\n resumen: hasCuentaMalDigitada ? '06 Resumen' : '05 Resumen',\n};\n\nconst mainHeader = [\n '#',\n 'Empleado',\n 'Cuenta',\n 'Moneda',\n 'Monto Bono 14',\n 'Monto Banco',\n 'Diferencia',\n 'Sentido',\n 'Estado',\n 'Resolución',\n];\n\nconst mainRows = rows\n .filter((row) => row.category === 'discrepancia')\n .map((row, index) => {\n const payrollAmount = roundMoney(row.payroll_amount ?? row.payrollAmount ?? 0);\n const bankAmount = roundMoney(row.bank_amount ?? row.bankAmount ?? 0);\n const difference = roundMoney(\n row.difference ?? (payrollAmount - bankAmount)\n );\n\n return [\n index + 1,\n normalizeText(row.employee_name || row.employee || ''),\n normalizeText(\n row.bank_account ||\n row.bankAccount ||\n row.payroll_account ||\n row.payrollAccount ||\n row.account ||\n ''\n ),\n currencyOf(row),\n payrollAmount,\n bankAmount,\n difference,\n mainReportSense(row, difference),\n normalizeText(row.status || 'Riesgo').toUpperCase(),\n '',\n ];\n });\n\nconst mainValues = [\n ['GOMEZLEE MARKETING', '', '', '', '', '', '', '', '', ''],\n [mainReportSubtitle, '', '', '', '', '', '', '', '', ''],\n ['', '', '', '', '', '', '', '', '', ''],\n mainHeader,\n ...mainRows,\n];\n\nconst bancoSinNominaHeader = [\n 'Empleado Banco',\n 'Cuenta Banco',\n 'Moneda',\n 'Monto Banco',\n 'Estado',\n 'Observación',\n 'Resolución',\n];\nconst bancoSinNominaRows = rows\n .filter((row) => row.category === 'banco_sin_nomina')\n .map((row) => [\n normalizeText(row.employee_name || row.employee || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? 0),\n normalizeText(row.status || ''),\n normalizeText(row.observation || ''),\n '',\n ]);\n\nconst bancoSinBambooHeader = [\n '#',\n 'Nombre en banco',\n 'Nombre del cuentahabiente',\n 'Cuenta',\n 'Moneda',\n 'Monto en banco',\n 'Número de envío',\n 'Estado',\n 'Resolución',\n];\nconst bancoSinBambooRows = bankWithoutBamboo.map((row, index) => [\n index + 1,\n normalizeText(row.bank_name_file || row.employee_name || ''),\n normalizeText(row.bank_account_holder || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? row.amount ?? 0),\n firstValue(row.shipment_numbers || row.shipment_number || ''),\n 'PENDIENTE REVISIÓN',\n '',\n]);\nconst bancoSinBambooValues = [\n ['GOMEZLEE MARKETING', '', '', '', '', '', '', '', ''],\n [bancoSinBambooSubtitle, '', '', '', '', '', '', '', ''],\n ['', '', '', '', '', '', '', '', ''],\n bancoSinBambooHeader,\n ...bancoSinBambooRows,\n];\n\nconst diferenciasNombreHeader = [\n 'Nombre en Archivo',\n 'Nombre del Cuentahabiente',\n 'Cuenta Destino',\n 'Moneda',\n 'Monto',\n 'Número de envío',\n 'Número de plan',\n 'Archivo',\n 'Estado',\n 'Observación',\n 'Resolución',\n];\nconst diferenciasNombreRows = rows\n .filter((row) => row.category === 'diferencia_nombre_banco')\n .map((row) => [\n normalizeText(row.bank_name_file || row.employee_name || ''),\n normalizeText(row.bank_account_holder || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? 0),\n normalizeText(row.shipment_number || ''),\n normalizeText(row.plan_number || ''),\n normalizeText(row.source_file || ''),\n normalizeText(row.status || ''),\n normalizeText(row.observation || ''),\n '',\n ]);\n\nfunction accountValues(value) {\n const values = Array.isArray(value)\n ? value\n : String(value ?? '').split(/\\s*(?:\\/|;|,|\\by\\b)\\s*/i);\n\n return values\n .map((item) => String(item ?? '').replace(/\\.0$/g, '').replace(/\\D/g, '').trim())\n .filter((item) => item.length >= 7 && !/^0+$/.test(item));\n}\n\nfunction payrollAccountsForWrongAccount(row) {\n const candidates = [\n row.payroll_account,\n row.payrollAccount,\n ...(Array.isArray(row.source_rows)\n ? row.source_rows.flatMap((sourceRow) => [\n sourceRow.account,\n sourceRow.payroll_account,\n sourceRow.payrollAccount,\n ])\n : []),\n ];\n return Array.from(new Set(candidates.flatMap(accountValues)));\n}\n\nfunction bankAccountForWrongAccount(row) {\n return firstValue(row.bank_account || row.bankAccount || row.account || '');\n}\n\nfunction moneyLabel(value, currency) {\n const amount = Math.abs(roundMoney(value)).toLocaleString('en-US', {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n return `${currency === 'USD' ? 'USD ' : 'Q'}${amount}`;\n}\n\nfunction wrongAccountTotalStatus(row) {\n const payrollAmount = roundMoney(row.payroll_amount ?? row.payrollAmount ?? 0);\n const bankAmount = roundMoney(row.bank_amount ?? row.bankAmount ?? 0);\n const difference = roundMoney(payrollAmount - bankAmount);\n const currency = currencyOf(row);\n\n if (Math.abs(difference) <= 0.02) {\n return 'El total del Bono 14 coincide con el total del banco.';\n }\n if (difference > 0) {\n return `El total del Bono 14 supera el total del banco por ${moneyLabel(difference, currency)}.`;\n }\n return `El total pagado por el banco supera el Bono 14 por ${moneyLabel(difference, currency)}.`;\n}\n\nfunction wrongAccountFinding(row) {\n const existing = normalizeText(row.observation || '');\n if (existing) return existing;\n\n const payrollAccounts = payrollAccountsForWrongAccount(row);\n const bankAccount = bankAccountForWrongAccount(row);\n return (\n `El empleado presenta una posible inconsistencia entre la cuenta del Excel de Bono 14 ` +\n `(${payrollAccounts.join(' y ') || 'sin cuenta identificada'}) y la cuenta utilizada por el banco ` +\n `(${bankAccount || 'sin cuenta identificada'}).`\n );\n}\n\nconst cuentaMalDigitadaHeader = ['#', 'Campo', 'Detalle', 'Resolución'];\nconst cuentaMalDigitadaRows = [];\n\nfor (let index = 0; index < cuentaMalDigitadaCases.length; index++) {\n const row = cuentaMalDigitadaCases[index];\n const payrollAccounts = payrollAccountsForWrongAccount(row);\n const bankAccount = bankAccountForWrongAccount(row);\n const fields = [\n ['Empleado', normalizeText(row.employee_name || row.employee || '')],\n ['Moneda', currencyOf(row)],\n [\n 'Cuentas registradas en el Excel de Bono 14',\n payrollAccounts.join(' y ') || 'No se identificó una cuenta válida en el Excel de Bono 14.',\n ],\n [\n 'Cuenta utilizada por el banco',\n bankAccount || 'No se identificó una cuenta válida en el banco.',\n ],\n ['Estado del total', wrongAccountTotalStatus(row)],\n ['Hallazgo', wrongAccountFinding(row)],\n [\n 'Clasificación',\n 'Posible cuenta mal digitada — revisar y unificar la cuenta del Bono 14 con la utilizada por el banco.',\n ],\n ];\n\n fields.forEach((field, fieldIndex) => {\n cuentaMalDigitadaRows.push([\n fieldIndex === 0 ? index + 1 : '',\n field[0],\n field[1],\n '',\n ]);\n });\n}\n\nconst cuentaMalDigitadaValues = [\n ['GOMEZLEE MARKETING', '', '', ''],\n [cuentaMalDigitadaSubtitle, '', '', ''],\n ['', '', '', ''],\n cuentaMalDigitadaHeader,\n ...cuentaMalDigitadaRows,\n];\n\nconst resumenHeader = ['Indicador', 'Valor'];\nconst resumenRows = [\n ['Reporte', `Bono 14 ${year}`],\n ['Período de devengo', periodLabel],\n ['Coincidencias', Number(summary.coincidencias || 0)],\n ['Discrepancias', Number(summary.discrepancias || 0)],\n ['Banco sin Bono 14', Number(summary.bancoSinNomina || 0)],\n ['Banco sin Bamboo', Number(summary.bancoSinBamboo || 0)],\n ['Bono 14 sin cuenta', Number(summary.nominaSinCuenta || 0)],\n ['Diferencias nombre banco', Number(summary.diferenciasNombreBanco || 0)],\n ['Posibles cuentas mal digitadas', Number(summary.posiblesCuentasMalDigitadas || 0)],\n ['Transacciones bancarias', Number(summary.transaccionesBanco || 0)],\n ['Total Bono 14 QTZ', roundMoney(summary.totalNominaQTZ || summary.totalNomina || 0)],\n ['Total Banco QTZ', roundMoney(summary.totalBancoQTZ || summary.totalBanco || 0)],\n ['Diferencia QTZ', roundMoney(summary.diferenciaQTZ || summary.diferenciaTotal || 0)],\n ['Total Bono 14 USD', roundMoney(summary.totalNominaUSD || 0)],\n ['Total Banco USD', roundMoney(summary.totalBancoUSD || 0)],\n ['Diferencia USD', roundMoney(summary.diferenciaUSD || 0)],\n];\n\nfunction buildSheetValues(header, bodyRows) {\n return [header, ...bodyRows];\n}\n\nconst valueData = [\n { range: `'${sheetTitles.nominaVsBanco}'!A1:J`, values: mainValues },\n {\n range: `'${sheetTitles.bancoSinNomina}'!A1:G`,\n values: buildSheetValues(bancoSinNominaHeader, bancoSinNominaRows),\n },\n { range: `'${sheetTitles.bancoSinBamboo}'!A1:I`, values: bancoSinBambooValues },\n {\n range: `'${sheetTitles.diferenciasNombreBanco}'!A1:K`,\n values: buildSheetValues(diferenciasNombreHeader, diferenciasNombreRows),\n },\n ...(hasCuentaMalDigitada\n ? [{ range: `'${sheetTitles.cuentaMalDigitada}'!A1:D`, values: cuentaMalDigitadaValues }]\n : []),\n {\n range: `'${sheetTitles.resumen}'!A1:B`,\n values: buildSheetValues(resumenHeader, resumenRows),\n },\n];\n\nfunction color(red, green, blue) {\n return { red, green, blue };\n}\n\nconst BLUE = color(0.29, 0.49, 0.58);\nconst GREEN = color(0.42, 0.76, 0.29);\nconst WHITE = color(1, 1, 1);\nconst LIGHT_RED = color(1, 0.92, 0.92);\nconst LIGHT_YELLOW = color(1, 0.97, 0.84);\nconst LIGHT_GREEN = color(0.91, 0.96, 0.91);\nconst LIGHT_BLUE = color(0.93, 0.96, 0.98);\nconst DARK = color(0.29, 0.29, 0.29);\n\nfunction repeatFormat(sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex, userEnteredFormat, fields) {\n return {\n repeatCell: {\n range: { sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex },\n cell: { userEnteredFormat },\n fields,\n },\n };\n}\n\nfunction freeze(sheetId, frozenRowCount) {\n return {\n updateSheetProperties: {\n properties: { sheetId, gridProperties: { frozenRowCount } },\n fields: 'gridProperties.frozenRowCount',\n },\n };\n}\n\nfunction setWidth(sheetId, startIndex, endIndex, pixelSize) {\n return {\n updateDimensionProperties: {\n range: { sheetId, dimension: 'COLUMNS', startIndex, endIndex },\n properties: { pixelSize },\n fields: 'pixelSize',\n },\n };\n}\n\nfunction autoRows(sheetId, endRowIndex) {\n return {\n autoResizeDimensions: {\n dimensions: {\n sheetId,\n dimension: 'ROWS',\n startIndex: 0,\n endIndex: Math.max(1, endRowIndex),\n },\n },\n };\n}\n\nfunction basicFilter(sheetId, startRowIndex, endRowIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return {\n setBasicFilter: {\n filter: {\n range: {\n sheetId,\n startRowIndex,\n endRowIndex,\n startColumnIndex: 0,\n endColumnIndex,\n },\n },\n },\n };\n}\n\nfunction mergeRow(sheetId, rowIndex, endColumnIndex) {\n return {\n mergeCells: {\n range: {\n sheetId,\n startRowIndex: rowIndex,\n endRowIndex: rowIndex + 1,\n startColumnIndex: 0,\n endColumnIndex,\n },\n mergeType: 'MERGE_ALL',\n },\n };\n}\n\nfunction moneyFormat(sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return repeatFormat(\n sheetId,\n startRowIndex,\n endRowIndex,\n startColumnIndex,\n endColumnIndex,\n { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } },\n 'userEnteredFormat.numberFormat'\n );\n}\n\nfunction addProtection(sheetId, endRowIndex, resolutionColumnIndex = null, resolutionStartRowIndex = 1) {\n const protectedRange = {\n range: { sheetId },\n description: `Protección GLM · Bono 14 · ${sheetId}`,\n warningOnly: false,\n editors: {\n users: ['iaracena@gomezleemarketing.com'],\n domainUsersCanEdit: false,\n },\n };\n\n if (\n Number.isInteger(resolutionColumnIndex) &&\n Number.isInteger(endRowIndex) &&\n endRowIndex > resolutionStartRowIndex\n ) {\n protectedRange.unprotectedRanges = [\n {\n sheetId,\n startRowIndex: resolutionStartRowIndex,\n endRowIndex,\n startColumnIndex: resolutionColumnIndex,\n endColumnIndex: resolutionColumnIndex + 1,\n },\n ];\n }\n\n return { addProtectedRange: { protectedRange } };\n}\n\nfunction protectedResolutionRequest(\n sheetId,\n endRowIndex,\n resolutionColumnIndex,\n resolutionStartRowIndex = 1\n) {\n if (\n !Number.isInteger(resolutionColumnIndex) ||\n !Number.isInteger(endRowIndex) ||\n endRowIndex <= resolutionStartRowIndex\n ) {\n return null;\n }\n\n return {\n addProtectedRange: {\n protectedRange: {\n range: {\n sheetId,\n startRowIndex: resolutionStartRowIndex,\n endRowIndex,\n startColumnIndex: resolutionColumnIndex,\n endColumnIndex: resolutionColumnIndex + 1,\n },\n description:\n `Protección GLM · Bono 14 · Resolución · ${sheetId}`,\n warningOnly: false,\n editors: {\n users: [\n 'vparamo@gomezleemarketing.com',\n 'adminregional@gomezleemarketing.com',\n ],\n domainUsersCanEdit: false,\n },\n },\n },\n };\n}\n\n\nfunction standardBodyFormat(sheetId, startRowIndex, endRowIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return repeatFormat(\n sheetId,\n startRowIndex,\n endRowIndex,\n 0,\n endColumnIndex,\n {\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { foregroundColor: DARK },\n },\n 'userEnteredFormat(verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nfunction headerFormat(sheetId, rowIndex, endColumnIndex) {\n return repeatFormat(\n sheetId,\n rowIndex,\n rowIndex + 1,\n 0,\n endColumnIndex,\n {\n backgroundColor: BLUE,\n horizontalAlignment: 'CENTER',\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { bold: true, foregroundColor: WHITE },\n },\n 'userEnteredFormat(backgroundColor,horizontalAlignment,verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nfunction titleFormat(sheetId, rowIndex, endColumnIndex, fontSize = 12) {\n return repeatFormat(\n sheetId,\n rowIndex,\n rowIndex + 1,\n 0,\n endColumnIndex,\n {\n backgroundColor: BLUE,\n horizontalAlignment: 'LEFT',\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { bold: true, fontSize, foregroundColor: WHITE },\n },\n 'userEnteredFormat(backgroundColor,horizontalAlignment,verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nconst formatRequests = [];\nfunction push(request) {\n if (request) formatRequests.push(request);\n}\n\n// 01 Bono 14 vs Banco\nconst mainEnd = mainValues.length;\npush(mergeRow(sheetIds.nominaVsBanco, 0, 10));\npush(mergeRow(sheetIds.nominaVsBanco, 1, 10));\npush(titleFormat(sheetIds.nominaVsBanco, 0, 10, 13));\npush(titleFormat(sheetIds.nominaVsBanco, 1, 10, 11));\npush(headerFormat(sheetIds.nominaVsBanco, 3, 10));\npush(standardBodyFormat(sheetIds.nominaVsBanco, 4, mainEnd, 10));\npush(moneyFormat(sheetIds.nominaVsBanco, 4, mainEnd, 4, 7));\npush(\n repeatFormat(\n sheetIds.nominaVsBanco,\n 4,\n mainEnd,\n 8,\n 9,\n {\n backgroundColor: LIGHT_RED,\n textFormat: { bold: true, foregroundColor: color(0.70, 0.20, 0.20) },\n horizontalAlignment: 'CENTER',\n },\n 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'\n )\n);\npush(basicFilter(sheetIds.nominaVsBanco, 3, mainEnd, 10));\npush(freeze(sheetIds.nominaVsBanco, 4));\n[\n [0, 1, 55], [1, 2, 220], [2, 3, 145], [3, 4, 75], [4, 7, 125],\n [7, 8, 185], [8, 9, 120], [9, 10, 230],\n].forEach(([s, e, px]) => push(setWidth(sheetIds.nominaVsBanco, s, e, px)));\npush(autoRows(sheetIds.nominaVsBanco, mainEnd));\n\nif (mainEnd > 4) {\n push({\n addConditionalFormatRule: {\n rule: {\n ranges: [{ sheetId: sheetIds.nominaVsBanco, startRowIndex: 4, endRowIndex: mainEnd, startColumnIndex: 6, endColumnIndex: 7 }],\n booleanRule: {\n condition: { type: 'CUSTOM_FORMULA', values: [{ userEnteredValue: '=$G5>0' }] },\n format: { backgroundColor: LIGHT_YELLOW, textFormat: { bold: true } },\n },\n },\n index: 0,\n },\n });\n push({\n addConditionalFormatRule: {\n rule: {\n ranges: [{ sheetId: sheetIds.nominaVsBanco, startRowIndex: 4, endRowIndex: mainEnd, startColumnIndex: 6, endColumnIndex: 7 }],\n booleanRule: {\n condition: { type: 'CUSTOM_FORMULA', values: [{ userEnteredValue: '=$G5<0' }] },\n format: { backgroundColor: LIGHT_RED, textFormat: { bold: true } },\n },\n },\n index: 0,\n },\n });\n}\npush(addProtection(sheetIds.nominaVsBanco, mainEnd, 9, 4));\n\n// 02 Banco sin Bono 14\nconst bankNoBonusEnd = 1 + bancoSinNominaRows.length;\npush(headerFormat(sheetIds.bancoSinNomina, 0, 7));\npush(standardBodyFormat(sheetIds.bancoSinNomina, 1, bankNoBonusEnd, 7));\npush(moneyFormat(sheetIds.bancoSinNomina, 1, bankNoBonusEnd, 3, 4));\npush(basicFilter(sheetIds.bancoSinNomina, 0, bankNoBonusEnd, 7));\npush(freeze(sheetIds.bancoSinNomina, 1));\n[[0,1,220],[1,2,145],[2,3,75],[3,4,125],[4,5,130],[5,6,320],[6,7,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.bancoSinNomina,s,e,px)));\npush(autoRows(sheetIds.bancoSinNomina, bankNoBonusEnd));\npush(addProtection(sheetIds.bancoSinNomina, bankNoBonusEnd, 6, 1));\n\n// 03 Banco sin Bamboo\nconst bambooEnd = bancoSinBambooValues.length;\npush(mergeRow(sheetIds.bancoSinBamboo, 0, 9));\npush(mergeRow(sheetIds.bancoSinBamboo, 1, 9));\npush(titleFormat(sheetIds.bancoSinBamboo, 0, 9, 13));\npush(titleFormat(sheetIds.bancoSinBamboo, 1, 9, 11));\npush(headerFormat(sheetIds.bancoSinBamboo, 3, 9));\npush(standardBodyFormat(sheetIds.bancoSinBamboo, 4, bambooEnd, 9));\npush(moneyFormat(sheetIds.bancoSinBamboo, 4, bambooEnd, 5, 6));\npush(\n repeatFormat(\n sheetIds.bancoSinBamboo,\n 4,\n bambooEnd,\n 7,\n 8,\n { backgroundColor: LIGHT_YELLOW, textFormat: { bold: true }, horizontalAlignment: 'CENTER' },\n 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'\n )\n);\npush(basicFilter(sheetIds.bancoSinBamboo, 3, bambooEnd, 9));\npush(freeze(sheetIds.bancoSinBamboo, 4));\n[[0,1,55],[1,2,220],[2,3,220],[3,4,145],[4,5,75],[5,6,125],[6,7,120],[7,8,155],[8,9,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.bancoSinBamboo,s,e,px)));\npush(autoRows(sheetIds.bancoSinBamboo, bambooEnd));\npush(addProtection(sheetIds.bancoSinBamboo, bambooEnd, 8, 4));\n\n// 04 Diferencias nombre banco\nconst diffNameEnd = 1 + diferenciasNombreRows.length;\npush(headerFormat(sheetIds.diferenciasNombreBanco, 0, 11));\npush(standardBodyFormat(sheetIds.diferenciasNombreBanco, 1, diffNameEnd, 11));\npush(moneyFormat(sheetIds.diferenciasNombreBanco, 1, diffNameEnd, 4, 5));\npush(basicFilter(sheetIds.diferenciasNombreBanco, 0, diffNameEnd, 11));\npush(freeze(sheetIds.diferenciasNombreBanco, 1));\n[[0,1,220],[1,2,220],[2,3,145],[3,4,75],[4,5,125],[5,7,115],[7,8,220],[8,9,130],[9,10,320],[10,11,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.diferenciasNombreBanco,s,e,px)));\npush(autoRows(sheetIds.diferenciasNombreBanco, diffNameEnd));\npush(addProtection(sheetIds.diferenciasNombreBanco, diffNameEnd, 10, 1));\n\n// 05 Cuenta Mal Digitada (opcional)\nif (hasCuentaMalDigitada) {\n const wrongEnd = cuentaMalDigitadaValues.length;\n push(mergeRow(sheetIds.cuentaMalDigitada, 0, 4));\n push(mergeRow(sheetIds.cuentaMalDigitada, 1, 4));\n push(titleFormat(sheetIds.cuentaMalDigitada, 0, 4, 13));\n push(titleFormat(sheetIds.cuentaMalDigitada, 1, 4, 11));\n push(headerFormat(sheetIds.cuentaMalDigitada, 3, 4));\n push(standardBodyFormat(sheetIds.cuentaMalDigitada, 4, wrongEnd, 4));\n push(freeze(sheetIds.cuentaMalDigitada, 4));\n [[0,1,55],[1,2,270],[2,3,640],[3,4,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.cuentaMalDigitada,s,e,px)));\n push(autoRows(sheetIds.cuentaMalDigitada, wrongEnd));\n push(addProtection(sheetIds.cuentaMalDigitada, wrongEnd, 3, 4));\n}\n\n// Resumen\nconst summaryEnd = 1 + resumenRows.length;\npush(headerFormat(sheetIds.resumen, 0, 2));\npush(standardBodyFormat(sheetIds.resumen, 1, summaryEnd, 2));\npush(\n repeatFormat(\n sheetIds.resumen,\n 1,\n summaryEnd,\n 0,\n 1,\n { backgroundColor: LIGHT_BLUE, textFormat: { bold: true, foregroundColor: DARK } },\n 'userEnteredFormat(backgroundColor,textFormat)'\n )\n);\npush(freeze(sheetIds.resumen, 1));\npush(setWidth(sheetIds.resumen, 0, 1, 310));\npush(setWidth(sheetIds.resumen, 1, 2, 260));\npush(autoRows(sheetIds.resumen, summaryEnd));\npush(addProtection(sheetIds.resumen, summaryEnd, null, 1));\n\n/*\n * La columna Resolución queda fuera de la protección general de la hoja,\n * pero se protege por separado para que solo Viviana Páramo y Liliana\n * Benítez puedan editarla.\n */\npush(protectedResolutionRequest(\n sheetIds.nominaVsBanco,\n mainEnd,\n 9,\n 4\n));\npush(protectedResolutionRequest(\n sheetIds.bancoSinNomina,\n bankNoBonusEnd,\n 6,\n 1\n));\npush(protectedResolutionRequest(\n sheetIds.bancoSinBamboo,\n bambooEnd,\n 8,\n 4\n));\npush(protectedResolutionRequest(\n sheetIds.diferenciasNombreBanco,\n diffNameEnd,\n 10,\n 1\n));\nif (hasCuentaMalDigitada) {\n push(protectedResolutionRequest(\n sheetIds.cuentaMalDigitada,\n cuentaMalDigitadaValues.length,\n 3,\n 4\n ));\n}\n\nconst desiredSheets = [\n {\n properties: {\n sheetId: sheetIds.nominaVsBanco,\n title: sheetTitles.nominaVsBanco,\n index: 0,\n gridProperties: { rowCount: Math.max(200, mainEnd + 20), columnCount: 10 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.bancoSinNomina,\n title: sheetTitles.bancoSinNomina,\n index: 1,\n gridProperties: { rowCount: Math.max(200, bankNoBonusEnd + 20), columnCount: 7 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.bancoSinBamboo,\n title: sheetTitles.bancoSinBamboo,\n index: 2,\n gridProperties: { rowCount: Math.max(200, bambooEnd + 20), columnCount: 9 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.diferenciasNombreBanco,\n title: sheetTitles.diferenciasNombreBanco,\n index: 3,\n gridProperties: { rowCount: Math.max(200, diffNameEnd + 20), columnCount: 11 },\n },\n },\n ...(hasCuentaMalDigitada\n ? [\n {\n properties: {\n sheetId: sheetIds.cuentaMalDigitada,\n title: sheetTitles.cuentaMalDigitada,\n index: 4,\n gridProperties: {\n rowCount: Math.max(200, cuentaMalDigitadaValues.length + 20),\n columnCount: 4,\n },\n },\n },\n ]\n : []),\n {\n properties: {\n sheetId: sheetIds.resumen,\n title: sheetTitles.resumen,\n index: hasCuentaMalDigitada ? 5 : 4,\n gridProperties: { rowCount: 200, columnCount: 2 },\n },\n },\n];\n\nreturn [\n {\n json: {\n ok: data.ok ?? true,\n stage: 'google_sheet_bono14_preparado',\n metadata,\n summary,\n sheetIds,\n sheetTitles,\n spreadsheetTitle,\n createSpreadsheetBody: {\n properties: {\n title: spreadsheetTitle,\n locale: 'es_GT',\n timeZone: 'America/Guatemala',\n },\n sheets: desiredSheets,\n },\n valueBatchBody: {\n valueInputOption: 'USER_ENTERED',\n data: valueData,\n },\n formatBatchBody: {\n requests: formatRequests,\n },\n originalResponse: data,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 8016, - 3280 + 8928, + 3664 ], - "id": "9c64018c-0cb3-486f-9d23-7130d711cb97", + "id": "85af4a31-dac6-4982-9e99-9874a9e9ee69", "name": "Preparar Google Sheet Bono 14" }, { @@ -441,10 +441,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 8256, - 3296 + 9168, + 3680 ], - "id": "c5ef1b72-12c6-48c0-bd6c-6e410704a2e6", + "id": "6df55b98-62ff-4db4-83df-35e5f7b80b76", "name": "Crear Google Sheet", "credentials": { "httpBasicAuth": { @@ -473,10 +473,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 9472, - 3280 + 10384, + 3664 ], - "id": "64e1694d-6b0a-411c-86f7-636042887223", + "id": "75569fdd-ef58-4feb-87d5-e3b0996d72ea", "name": "Escribir Google Sheet", "credentials": { "googleOAuth2Api": { @@ -501,10 +501,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 9712, - 3280 + 10624, + 3664 ], - "id": "c5d3a10e-4614-4f85-a624-5d9c99fb2629", + "id": "c291d5ce-6d75-49c6-b2be-d54b15a0dc1f", "name": "Formatear Google Sheet", "credentials": { "googleOAuth2Api": { @@ -520,23 +520,23 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 11648, - 3280 + 12560, + 3664 ], - "id": "02afa713-5438-4ab4-afaf-bb8cb1d4a3d6", + "id": "de676b6c-7834-4d49-8dc4-e50165e70c4e", "name": "Preparar respuesta final" }, { "parameters": { - "jsCode": "const context =\n $('Preparar actualización Google Sheet').first().json || {};\n\nconst spreadsheetId =\n context.spreadsheetId;\n\nif (!spreadsheetId) {\n throw new Error(\n 'No se recibió spreadsheetId para configurar permisos.'\n );\n}\n\nconst allowedEmails = [\n 'iaracena@gomezleemarketing.com',\n 'ymadera@gomezleemarketing.com',\n 'mgomez@gomezleemarketing.com',\n 'jgomez@gomezleemarketing.com',\n 'vparamo@gomezleemarketing.com',\n 'jsepulveda@gomezlee.marketing',\n 'mbenitez@gomezlee.marketing',\n];\n\nreturn allowedEmails.map((email) => ({\n json: {\n spreadsheetId,\n email,\n permissionBody: {\n type: 'user',\n role: 'writer',\n emailAddress: email,\n },\n reportUpdated:\n context.reportUpdated === true,\n },\n}));" + "jsCode": "const context =\n $('Preparar actualización Google Sheet').first().json || {};\n\nconst spreadsheetId =\n context.spreadsheetId;\n\nif (!spreadsheetId) {\n throw new Error(\n 'No se recibió spreadsheetId para configurar permisos.'\n );\n}\n\nconst allowedEmails = [\n 'iaracena@gomezleemarketing.com',\n 'ymadera@gomezleemarketing.com',\n 'mgomez@gomezleemarketing.com',\n 'jgomez@gomezleemarketing.com',\n 'vparamo@gomezleemarketing.com',\n 'adminregional@gomezleemarketing.com',\n 'jsepulveda@gomezlee.marketing',\n 'mbenitez@gomezlee.marketing',\n];\n\nreturn allowedEmails.map((email) => ({\n json: {\n spreadsheetId,\n email,\n permissionBody: {\n type: 'user',\n role: 'writer',\n emailAddress: email,\n },\n reportUpdated:\n context.reportUpdated === true,\n },\n}));" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 10608, - 3280 + 11520, + 3664 ], - "id": "c465e89a-e976-4f8d-96fd-21ad85aa965c", + "id": "cb6559a6-ca00-4e60-92cb-d4acbcacd51b", "name": "Preparar permisos Google Sheet" }, { @@ -553,10 +553,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 10848, - 3280 + 11760, + 3664 ], - "id": "e36f25f7-f99b-4a7b-ac0e-2e653d86a967", + "id": "8c951923-b751-4e56-a581-3ac911fa0f3b", "name": "Compartir Google Sheet", "credentials": { "googleOAuth2Api": { @@ -573,10 +573,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 11168, - 3280 + 12080, + 3664 ], - "id": "9a375998-957b-4e2f-9ac0-aea3b5ce3cdd", + "id": "5acbdf9e-75f7-4813-9679-80e066b584a4", "name": "Preparar histórico Supabase" }, { @@ -612,10 +612,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 11408, - 3280 + 12320, + 3664 ], - "id": "aee129de-29ee-4808-a480-a0d0c1c9fa4f", + "id": "4b0b39c1-d982-48a9-a59d-cd4d74555548", "name": "Insertar histórico Supabase", "onError": "continueRegularOutput" }, @@ -631,10 +631,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 4400 + 2272, + 4784 ], - "id": "dcd3aab6-326d-4a6e-9f80-5645203de828", + "id": "54f6a317-a276-4695-9490-3c0c50dd046d", "name": "Extract Bono14 - Resumen 4", "retryOnFail": false, "onError": "continueRegularOutput" @@ -644,10 +644,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 4928, - 4064 + 5840, + 4448 ], - "id": "1e8c09ae-f71c-4bb7-905c-4f873c1a9811", + "id": "0cb9a6c8-a0d3-4990-bcfa-e1f73a3dd8a7", "name": "Merge Hojas 10" }, { @@ -696,10 +696,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 912, - 2336 + 1824, + 2720 ], - "id": "ca5bc0bf-fd2a-4bdf-b7ac-34628f666975", + "id": "5f778c49-fc3b-4710-8066-2409e3955c91", "name": "HTTP - Empleados BambooHR Bono14 GT", "retryOnFail": true, "maxTries": 3, @@ -718,10 +718,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 1136, - 2336 + 2048, + 2720 ], - "id": "867f76e7-7db9-4259-9e61-6d0dc9acbbeb", + "id": "a9e924bb-dae3-49ca-9444-7c74724e3dee", "name": "Normalizar BambooHR Bono14 GT" }, { @@ -733,10 +733,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 7360, - 3280 + 8272, + 3664 ], - "id": "b27bfa42-456c-45de-98f0-c7fe919ee8af", + "id": "e85cc2d0-c994-4cda-861d-c083decd3d83", "name": "Merge - Agregar BambooHR Bono14" }, { @@ -749,10 +749,10 @@ "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ - 128, - 1760 + 1040, + 2144 ], - "id": "2e44f1cc-edb2-4fb8-af15-2441068ba6f1", + "id": "b9db02e6-ca68-4741-96de-db9219e47ccf", "name": "Sticky Note" }, { @@ -765,10 +765,10 @@ "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ - 2064, - 1904 + 2976, + 2288 ], - "id": "c346fd57-b420-473e-8131-7b60bde1d960", + "id": "2c5b1a6b-f7a2-443b-81be-8c10de6b64a5", "name": "Sticky Note1" }, { @@ -781,10 +781,10 @@ "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ - 6448, - 3056 + 7360, + 3440 ], - "id": "ad3da1da-1a16-4980-9d1b-fa11c0048b95", + "id": "91f0403f-d9b4-4e43-9164-9e58683f2e63", "name": "Sticky Note2" }, { @@ -797,10 +797,10 @@ "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ - 10064, - 2976 + 10976, + 3360 ], - "id": "46289c23-7cda-434d-827c-f47ad3ba9146", + "id": "c2f51855-dd3c-4247-9aac-7ba6418ddb7a", "name": "Sticky Note3" }, { @@ -810,10 +810,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 11648, - 3600 + 12560, + 3984 ], - "id": "1a73c8b6-7f06-4e5d-a83a-f731bd96a1ad", + "id": "8b23d8bb-72d1-4238-8d87-9ea4900098d7", "name": "Preparar correo reporte generado" }, { @@ -828,10 +828,10 @@ "type": "n8n-nodes-base.gmail", "typeVersion": 2.2, "position": [ - 11888, - 3600 + 12800, + 3984 ], - "id": "fbf05240-1dd2-4cde-b7ca-7ebb81136295", + "id": "0b6009ef-2641-4485-a0d3-ce7c65a08db4", "name": "Enviar correo reporte generado", "webhookId": "c10f5aec-68bc-44d9-8152-2e14f8898e02", "credentials": { @@ -884,10 +884,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 7584, - 3264 + 8496, + 3648 ], - "id": "aa995943-d367-449f-a720-7aa37c2862b5", + "id": "87f38db5-8ac0-4e95-813c-df037dcf55fb", "name": "Obtener alias BambooHR Bono14 Supabase", "retryOnFail": true, "maxTries": 2, @@ -934,10 +934,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 7792, - 3280 + 8704, + 3664 ], - "id": "47e29f81-ee8d-429f-a461-0288c86bf5e2", + "id": "d51126f5-5c4f-4ea7-9ade-f79303cc48e0", "name": "Buscar reporte existente Supabase" }, { @@ -952,10 +952,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 8512, - 3280 + 9424, + 3664 ], - "id": "26b3e1a2-ebfd-4cbd-a53d-5fd9244a4de1", + "id": "38f92ba2-e407-49f2-8146-fbbee84e714a", "name": "Leer Google Sheet actual", "credentials": { "googleOAuth2Api": { @@ -971,10 +971,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 8752, - 3280 + 9664, + 3664 ], - "id": "09a08613-346b-4121-9025-08a84a4a9329", + "id": "b7a12346-da22-45e5-ad35-5c1808bc3454", "name": "Preparar actualización Google Sheet" }, { @@ -993,10 +993,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 8992, - 3280 + 9904, + 3664 ], - "id": "c530c0ac-227b-4596-a261-540bc0799dce", + "id": "0501892c-ef8d-48da-9b41-8234b0a5dba9", "name": "Actualizar estructura Google Sheet", "credentials": { "googleOAuth2Api": { @@ -1021,10 +1021,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 9232, - 3280 + 10144, + 3664 ], - "id": "65330e06-3297-4213-9385-de6ad2b62026", + "id": "09488c5b-90cb-4b68-b13b-a1d68599c63b", "name": "Limpiar Google Sheet", "credentials": { "googleOAuth2Api": { @@ -1613,9 +1613,9 @@ }, "staticData": null, "meta": null, - "versionId": "fa138609-9867-4b15-b685-759ea211b234", - "activeVersionId": "fa138609-9867-4b15-b685-759ea211b234", - "versionCounter": 37, + "versionId": "91b012d4-b842-4c67-805c-466e60284978", + "activeVersionId": "91b012d4-b842-4c67-805c-466e60284978", + "versionCounter": 40, "triggerCount": 1, "shared": [ { @@ -1638,9 +1638,9 @@ ], "tags": [], "activeVersion": { - "updatedAt": "2026-08-14T12:12:03.000Z", - "createdAt": "2026-08-14T12:11:58.931Z", - "versionId": "fa138609-9867-4b15-b685-759ea211b234", + "updatedAt": "2026-08-19T15:20:45.000Z", + "createdAt": "2026-08-19T15:20:43.540Z", + "versionId": "91b012d4-b842-4c67-805c-466e60284978", "workflowId": "ox03EQlSmgjUCQuK", "nodes": [ { @@ -1653,10 +1653,10 @@ "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ - 464, - 2736 + 1376, + 3120 ], - "id": "e3fa9074-3d8c-4986-a921-750c71cc5eee", + "id": "67b62f15-2b30-4b21-b32b-4637f3f15c1a", "name": "Webhook", "webhookId": "83f02b5a-2322-42a3-9ca9-263622bd7a34" }, @@ -1667,10 +1667,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 672, - 2736 + 1584, + 3120 ], - "id": "57bcbea7-e1f7-44be-a754-6758d0ff48b9", + "id": "ce70d108-f876-4c71-83a8-21d84d6a81ae", "name": "Preparar entrada app" }, { @@ -1692,10 +1692,10 @@ "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ - 11888, - 3120 + 12800, + 3504 ], - "id": "fb71b850-d364-4c3e-aa2d-f29684c272fd", + "id": "c8a057a0-324e-49d5-9ada-d74174be60c3", "name": "Respond to Webhook" }, { @@ -1705,10 +1705,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 1344, - 1920 + 2256, + 2304 ], - "id": "c3fcbd08-860d-4940-8d0e-55142327d07e", + "id": "a4db3060-7fdd-48c2-89c8-7a9ba31a4992", "name": "Parsear CSV banco Bono 14 GT" }, { @@ -1720,10 +1720,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 7152, - 3280 + 8064, + 3664 ], - "id": "e8a66686-6888-4b30-9f0e-38e8d896d57e", + "id": "8fd1542a-7679-4a4c-a535-3f6ff731837e", "name": "Merge" }, { @@ -1733,10 +1733,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 7824, - 3120 + 8736, + 3504 ], - "id": "9ab13ae8-f5f1-439a-84cf-875b06ba593c", + "id": "e795db2e-7fb8-41b8-be02-f86901ae6fc8", "name": "Cruzar Bono 14 vs Banco" }, { @@ -1751,10 +1751,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1344, - 2112 + 2256, + 2496 ], - "id": "b7121eac-c58a-4378-bf17-8866ffc818a0", + "id": "4d882ac2-bb8b-4c08-907b-bda48da4cf98", "name": "Extract Bono14 - P&G", "retryOnFail": false }, @@ -1770,10 +1770,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1344, - 2352 + 2256, + 2736 ], - "id": "dd787c02-74cb-453f-8611-b515b026c6ab", + "id": "44039f6d-45f5-40b5-9681-c82dd1aeb23e", "name": "Extract Bono14 - Motorola", "retryOnFail": false, "onError": "continueRegularOutput" @@ -1790,10 +1790,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1344, - 2592 + 2256, + 2976 ], - "id": "9dd753e9-e9e7-47e9-8dce-c9ceef7accfd", + "id": "17e8a8ba-2fc1-442b-b7fd-0e3ecf89ca83", "name": "Extract Bono14 - Whirlpool", "retryOnFail": false, "onError": "continueRegularOutput" @@ -1810,10 +1810,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 2784 + 2272, + 3168 ], - "id": "ef502b8a-3ea6-42a9-b911-70d5e72ea877", + "id": "a66c9e89-6dce-4fc6-aa39-52f5a6199b43", "name": "Extract Bono14 - PMI", "retryOnFail": false, "onError": "continueRegularOutput" @@ -1830,10 +1830,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 2992 + 2272, + 3376 ], - "id": "db4c4942-830e-47c2-a8e2-982252a1ebc8", + "id": "0b07c236-68e9-4860-9a23-040f647c119f", "name": "Extract Bono14 - Nestle", "retryOnFail": false, "onError": "continueRegularOutput" @@ -1850,10 +1850,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 3200 + 2272, + 3584 ], - "id": "14b7b95a-d583-4788-a8b0-7afab135d0e4", + "id": "3305f9a2-702c-4c9a-ae23-ee50cb7fb199", "name": "Extract Bono14 - GLM Q", "retryOnFail": false, "onError": "continueRegularOutput" @@ -1870,10 +1870,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1376, - 3536 + 2288, + 3920 ], - "id": "2c5de156-7bf8-4d69-b71d-bcb38f98b332", + "id": "9a7202fe-4f18-4b05-bfbd-f864370c3146", "name": "Extract Bono14 - GLM USD", "retryOnFail": false, "onError": "continueRegularOutput" @@ -1890,10 +1890,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 3872 + 2272, + 4256 ], - "id": "24b36bbe-2a0b-4058-a4c6-a47f5103991e", + "id": "2b1aa77d-4ca7-43f0-aa7b-6a8f9137e3f4", "name": "Extract Bono14 - Resumen 1", "retryOnFail": false, "onError": "continueRegularOutput" @@ -1910,10 +1910,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 4160 + 2272, + 4544 ], - "id": "cd86a313-2e21-435c-9725-31098528cae1", + "id": "116bbb2b-aff9-4b5a-b235-2bb0e286e023", "name": "Extract Bono14 - Resumen 2", "retryOnFail": false, "onError": "continueRegularOutput" @@ -1925,10 +1925,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 5568, - 3888 + 6480, + 4272 ], - "id": "926de4e1-8954-4f6f-830f-2e6f724f4c6e", + "id": "00a4b9dd-308f-4cba-a769-45f0d28bf193", "name": "Normalizar Bono 14" }, { @@ -1936,10 +1936,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 2368, - 2416 + 3280, + 2800 ], - "id": "ae4ba7a6-9e0c-492f-adff-5dd5bdb20692", + "id": "f8f1dc21-b749-47ff-b781-9dadf6710993", "name": "Merge Hojas 01-02" }, { @@ -1947,10 +1947,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 2560, - 2624 + 3472, + 3008 ], - "id": "14da77f8-838f-4bdf-90ed-4786990b145c", + "id": "91c9410a-016b-4954-9879-f53472ee471a", "name": "Merge Hojas 03" }, { @@ -1958,10 +1958,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 2848, - 2752 + 3760, + 3136 ], - "id": "eb0de9de-c940-45b9-8f5c-b427f9fa198c", + "id": "1eecede6-94a1-4d6a-b5d7-9979d77eb181", "name": "Merge Hojas 04" }, { @@ -1969,10 +1969,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 3264, - 2880 + 4176, + 3264 ], - "id": "0910a078-76c0-4010-8daa-ffd11381cfa8", + "id": "731a7298-29d5-45af-aca4-0601ee281275", "name": "Merge Hojas 05" }, { @@ -1980,10 +1980,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 3568, - 3024 + 4480, + 3408 ], - "id": "eda17990-ef1d-4b96-a17c-3c68a3617ee6", + "id": "e263b8a6-fa74-4539-9e91-3667fba26033", "name": "Merge Hojas 06" }, { @@ -1991,10 +1991,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 3904, - 3296 + 4816, + 3680 ], - "id": "efe11e4e-9fe0-4836-b815-beeaedec2b3c", + "id": "d0d8c656-4ae9-4758-a6bf-a5e20ace59b1", "name": "Merge Hojas 07" }, { @@ -2002,10 +2002,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 4160, - 3424 + 5072, + 3808 ], - "id": "7ff3bcf8-0bfd-43da-bc6b-96c4bd236034", + "id": "a94df28b-8587-455a-9754-e3b4fbaebe3d", "name": "Merge Hojas 08" }, { @@ -2013,10 +2013,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 4464, - 3808 + 5376, + 4192 ], - "id": "3751c607-4ef5-423e-b611-592bb9bad758", + "id": "3c95b12a-cb75-4e48-9add-ab8bf735095e", "name": "Merge Hojas 09" }, { @@ -2031,10 +2031,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 4416 + 2272, + 4800 ], - "id": "9e0d0649-cecb-4ddf-b499-1c0ccd84cc09", + "id": "da0a0abb-38c6-4dd3-8c61-fd614cf5da27", "name": "Extract Bono14 - Resumen 3", "retryOnFail": false, "onError": "continueRegularOutput" @@ -2044,23 +2044,23 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 4768, - 3920 + 5680, + 4304 ], - "id": "1a6b94fa-bee4-4c72-8adc-389a0084a0b2", + "id": "67800564-372c-4921-b8aa-8be532a9c433", "name": "Merge Hojas " }, { "parameters": { - "jsCode": "const data = $input.first().json || {};\n\nfunction normalizeText(value) {\n return String(value ?? '')\n .replace(/\\uFEFF/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction roundMoney(value) {\n return Math.round((Number(value) || 0) * 100) / 100;\n}\n\nfunction firstValue(value) {\n if (Array.isArray(value)) {\n return value.filter(Boolean).join(' / ');\n }\n return normalizeText(value);\n}\n\nfunction currencyOf(row) {\n return normalizeText(row.currency || 'QTZ').toUpperCase() || 'QTZ';\n}\n\nfunction mainReportSense(row, difference) {\n const category = normalizeText(row.category).toLowerCase();\n const subcategory = normalizeText(row.subcategory).toLowerCase();\n\n if (category === 'posible_cuenta_mal_digitada') return 'Revisar cuenta';\n\n if (\n subcategory === 'nomina_con_cuenta_sin_pago_banco' ||\n (Number(row.bank_amount ?? row.bankAmount ?? 0) === 0 &&\n Number(row.payroll_amount ?? row.payrollAmount ?? 0) > 0)\n ) {\n return 'No aparece pagado en banco';\n }\n\n if (difference > 0) return 'Se pagó de menos';\n if (difference < 0) return 'Se pagó de más';\n return 'Revisar';\n}\n\nconst metadata = data.metadata || {};\nconst summary = data.summary || {};\nconst rows = Array.isArray(data.rows) ? data.rows : [];\nconst bankWithoutBamboo = Array.isArray(data.bankWithoutBamboo)\n ? data.bankWithoutBamboo\n : [];\n\nconst year = Number(metadata.year || 0);\nconst periodLabel =\n metadata.period_label ||\n `Bono 14 ${year}`;\n\nconst spreadsheetTitle =\n `Cruce de Cuentas GLM GT - Bono 14 ${year}`;\n\nconst mainReportSubtitle =\n `Bono 14 vs. Banco · Guatemala · ${periodLabel}`;\nconst bancoSinBambooSubtitle =\n `Pagos de Bono 14 en banco sin empleado identificado en BambooHR · Guatemala · ${periodLabel}`;\nconst cuentaMalDigitadaSubtitle =\n `Posible Cuenta Mal Digitada · Bono 14 · Guatemala · ${periodLabel}`;\n\nconst cuentaMalDigitadaCases = rows.filter(\n (row) => row.category === 'posible_cuenta_mal_digitada'\n);\nconst hasCuentaMalDigitada = cuentaMalDigitadaCases.length > 0;\n\nconst sheetIds = {\n nominaVsBanco: 101,\n bancoSinNomina: 102,\n bancoSinBamboo: 103,\n diferenciasNombreBanco: 104,\n cuentaMalDigitada: 105,\n resumen: 106,\n};\n\nconst sheetTitles = {\n nominaVsBanco: '01 Bono 14 vs Banco',\n bancoSinNomina: '02 Banco sin Bono 14',\n bancoSinBamboo: '03 Banco sin Bamboo',\n diferenciasNombreBanco: '04 Diferencias nombre banco',\n cuentaMalDigitada: '05 Cuenta Mal Digitada',\n resumen: hasCuentaMalDigitada ? '06 Resumen' : '05 Resumen',\n};\n\nconst mainHeader = [\n '#',\n 'Empleado',\n 'Cuenta',\n 'Moneda',\n 'Monto Bono 14',\n 'Monto Banco',\n 'Diferencia',\n 'Sentido',\n 'Estado',\n 'Resolución',\n];\n\nconst mainRows = rows\n .filter((row) => row.category === 'discrepancia')\n .map((row, index) => {\n const payrollAmount = roundMoney(row.payroll_amount ?? row.payrollAmount ?? 0);\n const bankAmount = roundMoney(row.bank_amount ?? row.bankAmount ?? 0);\n const difference = roundMoney(\n row.difference ?? (payrollAmount - bankAmount)\n );\n\n return [\n index + 1,\n normalizeText(row.employee_name || row.employee || ''),\n normalizeText(\n row.bank_account ||\n row.bankAccount ||\n row.payroll_account ||\n row.payrollAccount ||\n row.account ||\n ''\n ),\n currencyOf(row),\n payrollAmount,\n bankAmount,\n difference,\n mainReportSense(row, difference),\n normalizeText(row.status || 'Riesgo').toUpperCase(),\n '',\n ];\n });\n\nconst mainValues = [\n ['GOMEZLEE MARKETING', '', '', '', '', '', '', '', '', ''],\n [mainReportSubtitle, '', '', '', '', '', '', '', '', ''],\n ['', '', '', '', '', '', '', '', '', ''],\n mainHeader,\n ...mainRows,\n];\n\nconst bancoSinNominaHeader = [\n 'Empleado Banco',\n 'Cuenta Banco',\n 'Moneda',\n 'Monto Banco',\n 'Estado',\n 'Observación',\n 'Resolución',\n];\nconst bancoSinNominaRows = rows\n .filter((row) => row.category === 'banco_sin_nomina')\n .map((row) => [\n normalizeText(row.employee_name || row.employee || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? 0),\n normalizeText(row.status || ''),\n normalizeText(row.observation || ''),\n '',\n ]);\n\nconst bancoSinBambooHeader = [\n '#',\n 'Nombre en banco',\n 'Nombre del cuentahabiente',\n 'Cuenta',\n 'Moneda',\n 'Monto en banco',\n 'Número de envío',\n 'Estado',\n 'Resolución',\n];\nconst bancoSinBambooRows = bankWithoutBamboo.map((row, index) => [\n index + 1,\n normalizeText(row.bank_name_file || row.employee_name || ''),\n normalizeText(row.bank_account_holder || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? row.amount ?? 0),\n firstValue(row.shipment_numbers || row.shipment_number || ''),\n 'PENDIENTE REVISIÓN',\n '',\n]);\nconst bancoSinBambooValues = [\n ['GOMEZLEE MARKETING', '', '', '', '', '', '', '', ''],\n [bancoSinBambooSubtitle, '', '', '', '', '', '', '', ''],\n ['', '', '', '', '', '', '', '', ''],\n bancoSinBambooHeader,\n ...bancoSinBambooRows,\n];\n\nconst diferenciasNombreHeader = [\n 'Nombre en Archivo',\n 'Nombre del Cuentahabiente',\n 'Cuenta Destino',\n 'Moneda',\n 'Monto',\n 'Número de envío',\n 'Número de plan',\n 'Archivo',\n 'Estado',\n 'Observación',\n 'Resolución',\n];\nconst diferenciasNombreRows = rows\n .filter((row) => row.category === 'diferencia_nombre_banco')\n .map((row) => [\n normalizeText(row.bank_name_file || row.employee_name || ''),\n normalizeText(row.bank_account_holder || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? 0),\n normalizeText(row.shipment_number || ''),\n normalizeText(row.plan_number || ''),\n normalizeText(row.source_file || ''),\n normalizeText(row.status || ''),\n normalizeText(row.observation || ''),\n '',\n ]);\n\nfunction accountValues(value) {\n const values = Array.isArray(value)\n ? value\n : String(value ?? '').split(/\\s*(?:\\/|;|,|\\by\\b)\\s*/i);\n\n return values\n .map((item) => String(item ?? '').replace(/\\.0$/g, '').replace(/\\D/g, '').trim())\n .filter((item) => item.length >= 7 && !/^0+$/.test(item));\n}\n\nfunction payrollAccountsForWrongAccount(row) {\n const candidates = [\n row.payroll_account,\n row.payrollAccount,\n ...(Array.isArray(row.source_rows)\n ? row.source_rows.flatMap((sourceRow) => [\n sourceRow.account,\n sourceRow.payroll_account,\n sourceRow.payrollAccount,\n ])\n : []),\n ];\n return Array.from(new Set(candidates.flatMap(accountValues)));\n}\n\nfunction bankAccountForWrongAccount(row) {\n return firstValue(row.bank_account || row.bankAccount || row.account || '');\n}\n\nfunction moneyLabel(value, currency) {\n const amount = Math.abs(roundMoney(value)).toLocaleString('en-US', {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n return `${currency === 'USD' ? 'USD ' : 'Q'}${amount}`;\n}\n\nfunction wrongAccountTotalStatus(row) {\n const payrollAmount = roundMoney(row.payroll_amount ?? row.payrollAmount ?? 0);\n const bankAmount = roundMoney(row.bank_amount ?? row.bankAmount ?? 0);\n const difference = roundMoney(payrollAmount - bankAmount);\n const currency = currencyOf(row);\n\n if (Math.abs(difference) <= 0.02) {\n return 'El total del Bono 14 coincide con el total del banco.';\n }\n if (difference > 0) {\n return `El total del Bono 14 supera el total del banco por ${moneyLabel(difference, currency)}.`;\n }\n return `El total pagado por el banco supera el Bono 14 por ${moneyLabel(difference, currency)}.`;\n}\n\nfunction wrongAccountFinding(row) {\n const existing = normalizeText(row.observation || '');\n if (existing) return existing;\n\n const payrollAccounts = payrollAccountsForWrongAccount(row);\n const bankAccount = bankAccountForWrongAccount(row);\n return (\n `El empleado presenta una posible inconsistencia entre la cuenta del Excel de Bono 14 ` +\n `(${payrollAccounts.join(' y ') || 'sin cuenta identificada'}) y la cuenta utilizada por el banco ` +\n `(${bankAccount || 'sin cuenta identificada'}).`\n );\n}\n\nconst cuentaMalDigitadaHeader = ['#', 'Campo', 'Detalle', 'Resolución'];\nconst cuentaMalDigitadaRows = [];\n\nfor (let index = 0; index < cuentaMalDigitadaCases.length; index++) {\n const row = cuentaMalDigitadaCases[index];\n const payrollAccounts = payrollAccountsForWrongAccount(row);\n const bankAccount = bankAccountForWrongAccount(row);\n const fields = [\n ['Empleado', normalizeText(row.employee_name || row.employee || '')],\n ['Moneda', currencyOf(row)],\n [\n 'Cuentas registradas en el Excel de Bono 14',\n payrollAccounts.join(' y ') || 'No se identificó una cuenta válida en el Excel de Bono 14.',\n ],\n [\n 'Cuenta utilizada por el banco',\n bankAccount || 'No se identificó una cuenta válida en el banco.',\n ],\n ['Estado del total', wrongAccountTotalStatus(row)],\n ['Hallazgo', wrongAccountFinding(row)],\n [\n 'Clasificación',\n 'Posible cuenta mal digitada — revisar y unificar la cuenta del Bono 14 con la utilizada por el banco.',\n ],\n ];\n\n fields.forEach((field, fieldIndex) => {\n cuentaMalDigitadaRows.push([\n fieldIndex === 0 ? index + 1 : '',\n field[0],\n field[1],\n '',\n ]);\n });\n}\n\nconst cuentaMalDigitadaValues = [\n ['GOMEZLEE MARKETING', '', '', ''],\n [cuentaMalDigitadaSubtitle, '', '', ''],\n ['', '', '', ''],\n cuentaMalDigitadaHeader,\n ...cuentaMalDigitadaRows,\n];\n\nconst resumenHeader = ['Indicador', 'Valor'];\nconst resumenRows = [\n ['Reporte', `Bono 14 ${year}`],\n ['Período de devengo', periodLabel],\n ['Coincidencias', Number(summary.coincidencias || 0)],\n ['Discrepancias', Number(summary.discrepancias || 0)],\n ['Banco sin Bono 14', Number(summary.bancoSinNomina || 0)],\n ['Banco sin Bamboo', Number(summary.bancoSinBamboo || 0)],\n ['Bono 14 sin cuenta', Number(summary.nominaSinCuenta || 0)],\n ['Diferencias nombre banco', Number(summary.diferenciasNombreBanco || 0)],\n ['Posibles cuentas mal digitadas', Number(summary.posiblesCuentasMalDigitadas || 0)],\n ['Transacciones bancarias', Number(summary.transaccionesBanco || 0)],\n ['Total Bono 14 QTZ', roundMoney(summary.totalNominaQTZ || summary.totalNomina || 0)],\n ['Total Banco QTZ', roundMoney(summary.totalBancoQTZ || summary.totalBanco || 0)],\n ['Diferencia QTZ', roundMoney(summary.diferenciaQTZ || summary.diferenciaTotal || 0)],\n ['Total Bono 14 USD', roundMoney(summary.totalNominaUSD || 0)],\n ['Total Banco USD', roundMoney(summary.totalBancoUSD || 0)],\n ['Diferencia USD', roundMoney(summary.diferenciaUSD || 0)],\n];\n\nfunction buildSheetValues(header, bodyRows) {\n return [header, ...bodyRows];\n}\n\nconst valueData = [\n { range: `'${sheetTitles.nominaVsBanco}'!A1:J`, values: mainValues },\n {\n range: `'${sheetTitles.bancoSinNomina}'!A1:G`,\n values: buildSheetValues(bancoSinNominaHeader, bancoSinNominaRows),\n },\n { range: `'${sheetTitles.bancoSinBamboo}'!A1:I`, values: bancoSinBambooValues },\n {\n range: `'${sheetTitles.diferenciasNombreBanco}'!A1:K`,\n values: buildSheetValues(diferenciasNombreHeader, diferenciasNombreRows),\n },\n ...(hasCuentaMalDigitada\n ? [{ range: `'${sheetTitles.cuentaMalDigitada}'!A1:D`, values: cuentaMalDigitadaValues }]\n : []),\n {\n range: `'${sheetTitles.resumen}'!A1:B`,\n values: buildSheetValues(resumenHeader, resumenRows),\n },\n];\n\nfunction color(red, green, blue) {\n return { red, green, blue };\n}\n\nconst BLUE = color(0.29, 0.49, 0.58);\nconst GREEN = color(0.42, 0.76, 0.29);\nconst WHITE = color(1, 1, 1);\nconst LIGHT_RED = color(1, 0.92, 0.92);\nconst LIGHT_YELLOW = color(1, 0.97, 0.84);\nconst LIGHT_GREEN = color(0.91, 0.96, 0.91);\nconst LIGHT_BLUE = color(0.93, 0.96, 0.98);\nconst DARK = color(0.29, 0.29, 0.29);\n\nfunction repeatFormat(sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex, userEnteredFormat, fields) {\n return {\n repeatCell: {\n range: { sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex },\n cell: { userEnteredFormat },\n fields,\n },\n };\n}\n\nfunction freeze(sheetId, frozenRowCount) {\n return {\n updateSheetProperties: {\n properties: { sheetId, gridProperties: { frozenRowCount } },\n fields: 'gridProperties.frozenRowCount',\n },\n };\n}\n\nfunction setWidth(sheetId, startIndex, endIndex, pixelSize) {\n return {\n updateDimensionProperties: {\n range: { sheetId, dimension: 'COLUMNS', startIndex, endIndex },\n properties: { pixelSize },\n fields: 'pixelSize',\n },\n };\n}\n\nfunction autoRows(sheetId, endRowIndex) {\n return {\n autoResizeDimensions: {\n dimensions: {\n sheetId,\n dimension: 'ROWS',\n startIndex: 0,\n endIndex: Math.max(1, endRowIndex),\n },\n },\n };\n}\n\nfunction basicFilter(sheetId, startRowIndex, endRowIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return {\n setBasicFilter: {\n filter: {\n range: {\n sheetId,\n startRowIndex,\n endRowIndex,\n startColumnIndex: 0,\n endColumnIndex,\n },\n },\n },\n };\n}\n\nfunction mergeRow(sheetId, rowIndex, endColumnIndex) {\n return {\n mergeCells: {\n range: {\n sheetId,\n startRowIndex: rowIndex,\n endRowIndex: rowIndex + 1,\n startColumnIndex: 0,\n endColumnIndex,\n },\n mergeType: 'MERGE_ALL',\n },\n };\n}\n\nfunction moneyFormat(sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return repeatFormat(\n sheetId,\n startRowIndex,\n endRowIndex,\n startColumnIndex,\n endColumnIndex,\n { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } },\n 'userEnteredFormat.numberFormat'\n );\n}\n\nfunction addProtection(sheetId, endRowIndex, resolutionColumnIndex = null, resolutionStartRowIndex = 1) {\n const protectedRange = {\n range: { sheetId },\n description: `Protección GLM · Bono 14 · ${sheetId}`,\n warningOnly: false,\n editors: {\n users: ['iaracena@gomezleemarketing.com'],\n domainUsersCanEdit: false,\n },\n };\n\n if (\n Number.isInteger(resolutionColumnIndex) &&\n Number.isInteger(endRowIndex) &&\n endRowIndex > resolutionStartRowIndex\n ) {\n protectedRange.unprotectedRanges = [\n {\n sheetId,\n startRowIndex: resolutionStartRowIndex,\n endRowIndex,\n startColumnIndex: resolutionColumnIndex,\n endColumnIndex: resolutionColumnIndex + 1,\n },\n ];\n }\n\n return { addProtectedRange: { protectedRange } };\n}\n\nfunction standardBodyFormat(sheetId, startRowIndex, endRowIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return repeatFormat(\n sheetId,\n startRowIndex,\n endRowIndex,\n 0,\n endColumnIndex,\n {\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { foregroundColor: DARK },\n },\n 'userEnteredFormat(verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nfunction headerFormat(sheetId, rowIndex, endColumnIndex) {\n return repeatFormat(\n sheetId,\n rowIndex,\n rowIndex + 1,\n 0,\n endColumnIndex,\n {\n backgroundColor: BLUE,\n horizontalAlignment: 'CENTER',\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { bold: true, foregroundColor: WHITE },\n },\n 'userEnteredFormat(backgroundColor,horizontalAlignment,verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nfunction titleFormat(sheetId, rowIndex, endColumnIndex, fontSize = 12) {\n return repeatFormat(\n sheetId,\n rowIndex,\n rowIndex + 1,\n 0,\n endColumnIndex,\n {\n backgroundColor: BLUE,\n horizontalAlignment: 'LEFT',\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { bold: true, fontSize, foregroundColor: WHITE },\n },\n 'userEnteredFormat(backgroundColor,horizontalAlignment,verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nconst formatRequests = [];\nfunction push(request) {\n if (request) formatRequests.push(request);\n}\n\n// 01 Bono 14 vs Banco\nconst mainEnd = mainValues.length;\npush(mergeRow(sheetIds.nominaVsBanco, 0, 10));\npush(mergeRow(sheetIds.nominaVsBanco, 1, 10));\npush(titleFormat(sheetIds.nominaVsBanco, 0, 10, 13));\npush(titleFormat(sheetIds.nominaVsBanco, 1, 10, 11));\npush(headerFormat(sheetIds.nominaVsBanco, 3, 10));\npush(standardBodyFormat(sheetIds.nominaVsBanco, 4, mainEnd, 10));\npush(moneyFormat(sheetIds.nominaVsBanco, 4, mainEnd, 4, 7));\npush(\n repeatFormat(\n sheetIds.nominaVsBanco,\n 4,\n mainEnd,\n 8,\n 9,\n {\n backgroundColor: LIGHT_RED,\n textFormat: { bold: true, foregroundColor: color(0.70, 0.20, 0.20) },\n horizontalAlignment: 'CENTER',\n },\n 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'\n )\n);\npush(basicFilter(sheetIds.nominaVsBanco, 3, mainEnd, 10));\npush(freeze(sheetIds.nominaVsBanco, 4));\n[\n [0, 1, 55], [1, 2, 220], [2, 3, 145], [3, 4, 75], [4, 7, 125],\n [7, 8, 185], [8, 9, 120], [9, 10, 230],\n].forEach(([s, e, px]) => push(setWidth(sheetIds.nominaVsBanco, s, e, px)));\npush(autoRows(sheetIds.nominaVsBanco, mainEnd));\n\nif (mainEnd > 4) {\n push({\n addConditionalFormatRule: {\n rule: {\n ranges: [{ sheetId: sheetIds.nominaVsBanco, startRowIndex: 4, endRowIndex: mainEnd, startColumnIndex: 6, endColumnIndex: 7 }],\n booleanRule: {\n condition: { type: 'CUSTOM_FORMULA', values: [{ userEnteredValue: '=$G5>0' }] },\n format: { backgroundColor: LIGHT_YELLOW, textFormat: { bold: true } },\n },\n },\n index: 0,\n },\n });\n push({\n addConditionalFormatRule: {\n rule: {\n ranges: [{ sheetId: sheetIds.nominaVsBanco, startRowIndex: 4, endRowIndex: mainEnd, startColumnIndex: 6, endColumnIndex: 7 }],\n booleanRule: {\n condition: { type: 'CUSTOM_FORMULA', values: [{ userEnteredValue: '=$G5<0' }] },\n format: { backgroundColor: LIGHT_RED, textFormat: { bold: true } },\n },\n },\n index: 0,\n },\n });\n}\npush(addProtection(sheetIds.nominaVsBanco, mainEnd, 9, 4));\n\n// 02 Banco sin Bono 14\nconst bankNoBonusEnd = 1 + bancoSinNominaRows.length;\npush(headerFormat(sheetIds.bancoSinNomina, 0, 7));\npush(standardBodyFormat(sheetIds.bancoSinNomina, 1, bankNoBonusEnd, 7));\npush(moneyFormat(sheetIds.bancoSinNomina, 1, bankNoBonusEnd, 3, 4));\npush(basicFilter(sheetIds.bancoSinNomina, 0, bankNoBonusEnd, 7));\npush(freeze(sheetIds.bancoSinNomina, 1));\n[[0,1,220],[1,2,145],[2,3,75],[3,4,125],[4,5,130],[5,6,320],[6,7,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.bancoSinNomina,s,e,px)));\npush(autoRows(sheetIds.bancoSinNomina, bankNoBonusEnd));\npush(addProtection(sheetIds.bancoSinNomina, bankNoBonusEnd, 6, 1));\n\n// 03 Banco sin Bamboo\nconst bambooEnd = bancoSinBambooValues.length;\npush(mergeRow(sheetIds.bancoSinBamboo, 0, 9));\npush(mergeRow(sheetIds.bancoSinBamboo, 1, 9));\npush(titleFormat(sheetIds.bancoSinBamboo, 0, 9, 13));\npush(titleFormat(sheetIds.bancoSinBamboo, 1, 9, 11));\npush(headerFormat(sheetIds.bancoSinBamboo, 3, 9));\npush(standardBodyFormat(sheetIds.bancoSinBamboo, 4, bambooEnd, 9));\npush(moneyFormat(sheetIds.bancoSinBamboo, 4, bambooEnd, 5, 6));\npush(\n repeatFormat(\n sheetIds.bancoSinBamboo,\n 4,\n bambooEnd,\n 7,\n 8,\n { backgroundColor: LIGHT_YELLOW, textFormat: { bold: true }, horizontalAlignment: 'CENTER' },\n 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'\n )\n);\npush(basicFilter(sheetIds.bancoSinBamboo, 3, bambooEnd, 9));\npush(freeze(sheetIds.bancoSinBamboo, 4));\n[[0,1,55],[1,2,220],[2,3,220],[3,4,145],[4,5,75],[5,6,125],[6,7,120],[7,8,155],[8,9,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.bancoSinBamboo,s,e,px)));\npush(autoRows(sheetIds.bancoSinBamboo, bambooEnd));\npush(addProtection(sheetIds.bancoSinBamboo, bambooEnd, 8, 4));\n\n// 04 Diferencias nombre banco\nconst diffNameEnd = 1 + diferenciasNombreRows.length;\npush(headerFormat(sheetIds.diferenciasNombreBanco, 0, 11));\npush(standardBodyFormat(sheetIds.diferenciasNombreBanco, 1, diffNameEnd, 11));\npush(moneyFormat(sheetIds.diferenciasNombreBanco, 1, diffNameEnd, 4, 5));\npush(basicFilter(sheetIds.diferenciasNombreBanco, 0, diffNameEnd, 11));\npush(freeze(sheetIds.diferenciasNombreBanco, 1));\n[[0,1,220],[1,2,220],[2,3,145],[3,4,75],[4,5,125],[5,7,115],[7,8,220],[8,9,130],[9,10,320],[10,11,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.diferenciasNombreBanco,s,e,px)));\npush(autoRows(sheetIds.diferenciasNombreBanco, diffNameEnd));\npush(addProtection(sheetIds.diferenciasNombreBanco, diffNameEnd, 10, 1));\n\n// 05 Cuenta Mal Digitada (opcional)\nif (hasCuentaMalDigitada) {\n const wrongEnd = cuentaMalDigitadaValues.length;\n push(mergeRow(sheetIds.cuentaMalDigitada, 0, 4));\n push(mergeRow(sheetIds.cuentaMalDigitada, 1, 4));\n push(titleFormat(sheetIds.cuentaMalDigitada, 0, 4, 13));\n push(titleFormat(sheetIds.cuentaMalDigitada, 1, 4, 11));\n push(headerFormat(sheetIds.cuentaMalDigitada, 3, 4));\n push(standardBodyFormat(sheetIds.cuentaMalDigitada, 4, wrongEnd, 4));\n push(freeze(sheetIds.cuentaMalDigitada, 4));\n [[0,1,55],[1,2,270],[2,3,640],[3,4,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.cuentaMalDigitada,s,e,px)));\n push(autoRows(sheetIds.cuentaMalDigitada, wrongEnd));\n push(addProtection(sheetIds.cuentaMalDigitada, wrongEnd, 3, 4));\n}\n\n// Resumen\nconst summaryEnd = 1 + resumenRows.length;\npush(headerFormat(sheetIds.resumen, 0, 2));\npush(standardBodyFormat(sheetIds.resumen, 1, summaryEnd, 2));\npush(\n repeatFormat(\n sheetIds.resumen,\n 1,\n summaryEnd,\n 0,\n 1,\n { backgroundColor: LIGHT_BLUE, textFormat: { bold: true, foregroundColor: DARK } },\n 'userEnteredFormat(backgroundColor,textFormat)'\n )\n);\npush(freeze(sheetIds.resumen, 1));\npush(setWidth(sheetIds.resumen, 0, 1, 310));\npush(setWidth(sheetIds.resumen, 1, 2, 260));\npush(autoRows(sheetIds.resumen, summaryEnd));\npush(addProtection(sheetIds.resumen, summaryEnd, null, 1));\n\nconst desiredSheets = [\n {\n properties: {\n sheetId: sheetIds.nominaVsBanco,\n title: sheetTitles.nominaVsBanco,\n index: 0,\n gridProperties: { rowCount: Math.max(200, mainEnd + 20), columnCount: 10 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.bancoSinNomina,\n title: sheetTitles.bancoSinNomina,\n index: 1,\n gridProperties: { rowCount: Math.max(200, bankNoBonusEnd + 20), columnCount: 7 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.bancoSinBamboo,\n title: sheetTitles.bancoSinBamboo,\n index: 2,\n gridProperties: { rowCount: Math.max(200, bambooEnd + 20), columnCount: 9 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.diferenciasNombreBanco,\n title: sheetTitles.diferenciasNombreBanco,\n index: 3,\n gridProperties: { rowCount: Math.max(200, diffNameEnd + 20), columnCount: 11 },\n },\n },\n ...(hasCuentaMalDigitada\n ? [\n {\n properties: {\n sheetId: sheetIds.cuentaMalDigitada,\n title: sheetTitles.cuentaMalDigitada,\n index: 4,\n gridProperties: {\n rowCount: Math.max(200, cuentaMalDigitadaValues.length + 20),\n columnCount: 4,\n },\n },\n },\n ]\n : []),\n {\n properties: {\n sheetId: sheetIds.resumen,\n title: sheetTitles.resumen,\n index: hasCuentaMalDigitada ? 5 : 4,\n gridProperties: { rowCount: 200, columnCount: 2 },\n },\n },\n];\n\nreturn [\n {\n json: {\n ok: data.ok ?? true,\n stage: 'google_sheet_bono14_preparado',\n metadata,\n summary,\n sheetIds,\n sheetTitles,\n spreadsheetTitle,\n createSpreadsheetBody: {\n properties: {\n title: spreadsheetTitle,\n locale: 'es_GT',\n timeZone: 'America/Guatemala',\n },\n sheets: desiredSheets,\n },\n valueBatchBody: {\n valueInputOption: 'USER_ENTERED',\n data: valueData,\n },\n formatBatchBody: {\n requests: formatRequests,\n },\n originalResponse: data,\n },\n },\n];" + "jsCode": "const data = $input.first().json || {};\n\nfunction normalizeText(value) {\n return String(value ?? '')\n .replace(/\\uFEFF/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction roundMoney(value) {\n return Math.round((Number(value) || 0) * 100) / 100;\n}\n\nfunction firstValue(value) {\n if (Array.isArray(value)) {\n return value.filter(Boolean).join(' / ');\n }\n return normalizeText(value);\n}\n\nfunction currencyOf(row) {\n return normalizeText(row.currency || 'QTZ').toUpperCase() || 'QTZ';\n}\n\nfunction mainReportSense(row, difference) {\n const category = normalizeText(row.category).toLowerCase();\n const subcategory = normalizeText(row.subcategory).toLowerCase();\n\n if (category === 'posible_cuenta_mal_digitada') return 'Revisar cuenta';\n\n if (\n subcategory === 'nomina_con_cuenta_sin_pago_banco' ||\n (Number(row.bank_amount ?? row.bankAmount ?? 0) === 0 &&\n Number(row.payroll_amount ?? row.payrollAmount ?? 0) > 0)\n ) {\n return 'No aparece pagado en banco';\n }\n\n if (difference > 0) return 'Se pagó de menos';\n if (difference < 0) return 'Se pagó de más';\n return 'Revisar';\n}\n\nconst metadata = data.metadata || {};\nconst summary = data.summary || {};\nconst rows = Array.isArray(data.rows) ? data.rows : [];\nconst bankWithoutBamboo = Array.isArray(data.bankWithoutBamboo)\n ? data.bankWithoutBamboo\n : [];\n\nconst year = Number(metadata.year || 0);\nconst periodLabel =\n metadata.period_label ||\n `Bono 14 ${year}`;\n\nconst spreadsheetTitle =\n `Cruce de Cuentas GLM GT - Bono 14 ${year}`;\n\nconst mainReportSubtitle =\n `Bono 14 vs. Banco · Guatemala · ${periodLabel}`;\nconst bancoSinBambooSubtitle =\n `Pagos de Bono 14 en banco sin empleado identificado en BambooHR · Guatemala · ${periodLabel}`;\nconst cuentaMalDigitadaSubtitle =\n `Posible Cuenta Mal Digitada · Bono 14 · Guatemala · ${periodLabel}`;\n\nconst cuentaMalDigitadaCases = rows.filter(\n (row) => row.category === 'posible_cuenta_mal_digitada'\n);\nconst hasCuentaMalDigitada = cuentaMalDigitadaCases.length > 0;\n\nconst sheetIds = {\n nominaVsBanco: 101,\n bancoSinNomina: 102,\n bancoSinBamboo: 103,\n diferenciasNombreBanco: 104,\n cuentaMalDigitada: 105,\n resumen: 106,\n};\n\nconst sheetTitles = {\n nominaVsBanco: '01 Bono 14 vs Banco',\n bancoSinNomina: '02 Banco sin Bono 14',\n bancoSinBamboo: '03 Banco sin Bamboo',\n diferenciasNombreBanco: '04 Diferencias nombre banco',\n cuentaMalDigitada: '05 Cuenta Mal Digitada',\n resumen: hasCuentaMalDigitada ? '06 Resumen' : '05 Resumen',\n};\n\nconst mainHeader = [\n '#',\n 'Empleado',\n 'Cuenta',\n 'Moneda',\n 'Monto Bono 14',\n 'Monto Banco',\n 'Diferencia',\n 'Sentido',\n 'Estado',\n 'Resolución',\n];\n\nconst mainRows = rows\n .filter((row) => row.category === 'discrepancia')\n .map((row, index) => {\n const payrollAmount = roundMoney(row.payroll_amount ?? row.payrollAmount ?? 0);\n const bankAmount = roundMoney(row.bank_amount ?? row.bankAmount ?? 0);\n const difference = roundMoney(\n row.difference ?? (payrollAmount - bankAmount)\n );\n\n return [\n index + 1,\n normalizeText(row.employee_name || row.employee || ''),\n normalizeText(\n row.bank_account ||\n row.bankAccount ||\n row.payroll_account ||\n row.payrollAccount ||\n row.account ||\n ''\n ),\n currencyOf(row),\n payrollAmount,\n bankAmount,\n difference,\n mainReportSense(row, difference),\n normalizeText(row.status || 'Riesgo').toUpperCase(),\n '',\n ];\n });\n\nconst mainValues = [\n ['GOMEZLEE MARKETING', '', '', '', '', '', '', '', '', ''],\n [mainReportSubtitle, '', '', '', '', '', '', '', '', ''],\n ['', '', '', '', '', '', '', '', '', ''],\n mainHeader,\n ...mainRows,\n];\n\nconst bancoSinNominaHeader = [\n 'Empleado Banco',\n 'Cuenta Banco',\n 'Moneda',\n 'Monto Banco',\n 'Estado',\n 'Observación',\n 'Resolución',\n];\nconst bancoSinNominaRows = rows\n .filter((row) => row.category === 'banco_sin_nomina')\n .map((row) => [\n normalizeText(row.employee_name || row.employee || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? 0),\n normalizeText(row.status || ''),\n normalizeText(row.observation || ''),\n '',\n ]);\n\nconst bancoSinBambooHeader = [\n '#',\n 'Nombre en banco',\n 'Nombre del cuentahabiente',\n 'Cuenta',\n 'Moneda',\n 'Monto en banco',\n 'Número de envío',\n 'Estado',\n 'Resolución',\n];\nconst bancoSinBambooRows = bankWithoutBamboo.map((row, index) => [\n index + 1,\n normalizeText(row.bank_name_file || row.employee_name || ''),\n normalizeText(row.bank_account_holder || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? row.amount ?? 0),\n firstValue(row.shipment_numbers || row.shipment_number || ''),\n 'PENDIENTE REVISIÓN',\n '',\n]);\nconst bancoSinBambooValues = [\n ['GOMEZLEE MARKETING', '', '', '', '', '', '', '', ''],\n [bancoSinBambooSubtitle, '', '', '', '', '', '', '', ''],\n ['', '', '', '', '', '', '', '', ''],\n bancoSinBambooHeader,\n ...bancoSinBambooRows,\n];\n\nconst diferenciasNombreHeader = [\n 'Nombre en Archivo',\n 'Nombre del Cuentahabiente',\n 'Cuenta Destino',\n 'Moneda',\n 'Monto',\n 'Número de envío',\n 'Número de plan',\n 'Archivo',\n 'Estado',\n 'Observación',\n 'Resolución',\n];\nconst diferenciasNombreRows = rows\n .filter((row) => row.category === 'diferencia_nombre_banco')\n .map((row) => [\n normalizeText(row.bank_name_file || row.employee_name || ''),\n normalizeText(row.bank_account_holder || ''),\n normalizeText(row.bank_account || row.bankAccount || row.account || ''),\n currencyOf(row),\n roundMoney(row.bank_amount ?? row.bankAmount ?? 0),\n normalizeText(row.shipment_number || ''),\n normalizeText(row.plan_number || ''),\n normalizeText(row.source_file || ''),\n normalizeText(row.status || ''),\n normalizeText(row.observation || ''),\n '',\n ]);\n\nfunction accountValues(value) {\n const values = Array.isArray(value)\n ? value\n : String(value ?? '').split(/\\s*(?:\\/|;|,|\\by\\b)\\s*/i);\n\n return values\n .map((item) => String(item ?? '').replace(/\\.0$/g, '').replace(/\\D/g, '').trim())\n .filter((item) => item.length >= 7 && !/^0+$/.test(item));\n}\n\nfunction payrollAccountsForWrongAccount(row) {\n const candidates = [\n row.payroll_account,\n row.payrollAccount,\n ...(Array.isArray(row.source_rows)\n ? row.source_rows.flatMap((sourceRow) => [\n sourceRow.account,\n sourceRow.payroll_account,\n sourceRow.payrollAccount,\n ])\n : []),\n ];\n return Array.from(new Set(candidates.flatMap(accountValues)));\n}\n\nfunction bankAccountForWrongAccount(row) {\n return firstValue(row.bank_account || row.bankAccount || row.account || '');\n}\n\nfunction moneyLabel(value, currency) {\n const amount = Math.abs(roundMoney(value)).toLocaleString('en-US', {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n return `${currency === 'USD' ? 'USD ' : 'Q'}${amount}`;\n}\n\nfunction wrongAccountTotalStatus(row) {\n const payrollAmount = roundMoney(row.payroll_amount ?? row.payrollAmount ?? 0);\n const bankAmount = roundMoney(row.bank_amount ?? row.bankAmount ?? 0);\n const difference = roundMoney(payrollAmount - bankAmount);\n const currency = currencyOf(row);\n\n if (Math.abs(difference) <= 0.02) {\n return 'El total del Bono 14 coincide con el total del banco.';\n }\n if (difference > 0) {\n return `El total del Bono 14 supera el total del banco por ${moneyLabel(difference, currency)}.`;\n }\n return `El total pagado por el banco supera el Bono 14 por ${moneyLabel(difference, currency)}.`;\n}\n\nfunction wrongAccountFinding(row) {\n const existing = normalizeText(row.observation || '');\n if (existing) return existing;\n\n const payrollAccounts = payrollAccountsForWrongAccount(row);\n const bankAccount = bankAccountForWrongAccount(row);\n return (\n `El empleado presenta una posible inconsistencia entre la cuenta del Excel de Bono 14 ` +\n `(${payrollAccounts.join(' y ') || 'sin cuenta identificada'}) y la cuenta utilizada por el banco ` +\n `(${bankAccount || 'sin cuenta identificada'}).`\n );\n}\n\nconst cuentaMalDigitadaHeader = ['#', 'Campo', 'Detalle', 'Resolución'];\nconst cuentaMalDigitadaRows = [];\n\nfor (let index = 0; index < cuentaMalDigitadaCases.length; index++) {\n const row = cuentaMalDigitadaCases[index];\n const payrollAccounts = payrollAccountsForWrongAccount(row);\n const bankAccount = bankAccountForWrongAccount(row);\n const fields = [\n ['Empleado', normalizeText(row.employee_name || row.employee || '')],\n ['Moneda', currencyOf(row)],\n [\n 'Cuentas registradas en el Excel de Bono 14',\n payrollAccounts.join(' y ') || 'No se identificó una cuenta válida en el Excel de Bono 14.',\n ],\n [\n 'Cuenta utilizada por el banco',\n bankAccount || 'No se identificó una cuenta válida en el banco.',\n ],\n ['Estado del total', wrongAccountTotalStatus(row)],\n ['Hallazgo', wrongAccountFinding(row)],\n [\n 'Clasificación',\n 'Posible cuenta mal digitada — revisar y unificar la cuenta del Bono 14 con la utilizada por el banco.',\n ],\n ];\n\n fields.forEach((field, fieldIndex) => {\n cuentaMalDigitadaRows.push([\n fieldIndex === 0 ? index + 1 : '',\n field[0],\n field[1],\n '',\n ]);\n });\n}\n\nconst cuentaMalDigitadaValues = [\n ['GOMEZLEE MARKETING', '', '', ''],\n [cuentaMalDigitadaSubtitle, '', '', ''],\n ['', '', '', ''],\n cuentaMalDigitadaHeader,\n ...cuentaMalDigitadaRows,\n];\n\nconst resumenHeader = ['Indicador', 'Valor'];\nconst resumenRows = [\n ['Reporte', `Bono 14 ${year}`],\n ['Período de devengo', periodLabel],\n ['Coincidencias', Number(summary.coincidencias || 0)],\n ['Discrepancias', Number(summary.discrepancias || 0)],\n ['Banco sin Bono 14', Number(summary.bancoSinNomina || 0)],\n ['Banco sin Bamboo', Number(summary.bancoSinBamboo || 0)],\n ['Bono 14 sin cuenta', Number(summary.nominaSinCuenta || 0)],\n ['Diferencias nombre banco', Number(summary.diferenciasNombreBanco || 0)],\n ['Posibles cuentas mal digitadas', Number(summary.posiblesCuentasMalDigitadas || 0)],\n ['Transacciones bancarias', Number(summary.transaccionesBanco || 0)],\n ['Total Bono 14 QTZ', roundMoney(summary.totalNominaQTZ || summary.totalNomina || 0)],\n ['Total Banco QTZ', roundMoney(summary.totalBancoQTZ || summary.totalBanco || 0)],\n ['Diferencia QTZ', roundMoney(summary.diferenciaQTZ || summary.diferenciaTotal || 0)],\n ['Total Bono 14 USD', roundMoney(summary.totalNominaUSD || 0)],\n ['Total Banco USD', roundMoney(summary.totalBancoUSD || 0)],\n ['Diferencia USD', roundMoney(summary.diferenciaUSD || 0)],\n];\n\nfunction buildSheetValues(header, bodyRows) {\n return [header, ...bodyRows];\n}\n\nconst valueData = [\n { range: `'${sheetTitles.nominaVsBanco}'!A1:J`, values: mainValues },\n {\n range: `'${sheetTitles.bancoSinNomina}'!A1:G`,\n values: buildSheetValues(bancoSinNominaHeader, bancoSinNominaRows),\n },\n { range: `'${sheetTitles.bancoSinBamboo}'!A1:I`, values: bancoSinBambooValues },\n {\n range: `'${sheetTitles.diferenciasNombreBanco}'!A1:K`,\n values: buildSheetValues(diferenciasNombreHeader, diferenciasNombreRows),\n },\n ...(hasCuentaMalDigitada\n ? [{ range: `'${sheetTitles.cuentaMalDigitada}'!A1:D`, values: cuentaMalDigitadaValues }]\n : []),\n {\n range: `'${sheetTitles.resumen}'!A1:B`,\n values: buildSheetValues(resumenHeader, resumenRows),\n },\n];\n\nfunction color(red, green, blue) {\n return { red, green, blue };\n}\n\nconst BLUE = color(0.29, 0.49, 0.58);\nconst GREEN = color(0.42, 0.76, 0.29);\nconst WHITE = color(1, 1, 1);\nconst LIGHT_RED = color(1, 0.92, 0.92);\nconst LIGHT_YELLOW = color(1, 0.97, 0.84);\nconst LIGHT_GREEN = color(0.91, 0.96, 0.91);\nconst LIGHT_BLUE = color(0.93, 0.96, 0.98);\nconst DARK = color(0.29, 0.29, 0.29);\n\nfunction repeatFormat(sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex, userEnteredFormat, fields) {\n return {\n repeatCell: {\n range: { sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex },\n cell: { userEnteredFormat },\n fields,\n },\n };\n}\n\nfunction freeze(sheetId, frozenRowCount) {\n return {\n updateSheetProperties: {\n properties: { sheetId, gridProperties: { frozenRowCount } },\n fields: 'gridProperties.frozenRowCount',\n },\n };\n}\n\nfunction setWidth(sheetId, startIndex, endIndex, pixelSize) {\n return {\n updateDimensionProperties: {\n range: { sheetId, dimension: 'COLUMNS', startIndex, endIndex },\n properties: { pixelSize },\n fields: 'pixelSize',\n },\n };\n}\n\nfunction autoRows(sheetId, endRowIndex) {\n return {\n autoResizeDimensions: {\n dimensions: {\n sheetId,\n dimension: 'ROWS',\n startIndex: 0,\n endIndex: Math.max(1, endRowIndex),\n },\n },\n };\n}\n\nfunction basicFilter(sheetId, startRowIndex, endRowIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return {\n setBasicFilter: {\n filter: {\n range: {\n sheetId,\n startRowIndex,\n endRowIndex,\n startColumnIndex: 0,\n endColumnIndex,\n },\n },\n },\n };\n}\n\nfunction mergeRow(sheetId, rowIndex, endColumnIndex) {\n return {\n mergeCells: {\n range: {\n sheetId,\n startRowIndex: rowIndex,\n endRowIndex: rowIndex + 1,\n startColumnIndex: 0,\n endColumnIndex,\n },\n mergeType: 'MERGE_ALL',\n },\n };\n}\n\nfunction moneyFormat(sheetId, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return repeatFormat(\n sheetId,\n startRowIndex,\n endRowIndex,\n startColumnIndex,\n endColumnIndex,\n { numberFormat: { type: 'NUMBER', pattern: '#,##0.00' } },\n 'userEnteredFormat.numberFormat'\n );\n}\n\nfunction addProtection(sheetId, endRowIndex, resolutionColumnIndex = null, resolutionStartRowIndex = 1) {\n const protectedRange = {\n range: { sheetId },\n description: `Protección GLM · Bono 14 · ${sheetId}`,\n warningOnly: false,\n editors: {\n users: ['iaracena@gomezleemarketing.com'],\n domainUsersCanEdit: false,\n },\n };\n\n if (\n Number.isInteger(resolutionColumnIndex) &&\n Number.isInteger(endRowIndex) &&\n endRowIndex > resolutionStartRowIndex\n ) {\n protectedRange.unprotectedRanges = [\n {\n sheetId,\n startRowIndex: resolutionStartRowIndex,\n endRowIndex,\n startColumnIndex: resolutionColumnIndex,\n endColumnIndex: resolutionColumnIndex + 1,\n },\n ];\n }\n\n return { addProtectedRange: { protectedRange } };\n}\n\nfunction protectedResolutionRequest(\n sheetId,\n endRowIndex,\n resolutionColumnIndex,\n resolutionStartRowIndex = 1\n) {\n if (\n !Number.isInteger(resolutionColumnIndex) ||\n !Number.isInteger(endRowIndex) ||\n endRowIndex <= resolutionStartRowIndex\n ) {\n return null;\n }\n\n return {\n addProtectedRange: {\n protectedRange: {\n range: {\n sheetId,\n startRowIndex: resolutionStartRowIndex,\n endRowIndex,\n startColumnIndex: resolutionColumnIndex,\n endColumnIndex: resolutionColumnIndex + 1,\n },\n description:\n `Protección GLM · Bono 14 · Resolución · ${sheetId}`,\n warningOnly: false,\n editors: {\n users: [\n 'vparamo@gomezleemarketing.com',\n 'adminregional@gomezleemarketing.com',\n ],\n domainUsersCanEdit: false,\n },\n },\n },\n };\n}\n\n\nfunction standardBodyFormat(sheetId, startRowIndex, endRowIndex, endColumnIndex) {\n if (endRowIndex <= startRowIndex) return null;\n return repeatFormat(\n sheetId,\n startRowIndex,\n endRowIndex,\n 0,\n endColumnIndex,\n {\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { foregroundColor: DARK },\n },\n 'userEnteredFormat(verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nfunction headerFormat(sheetId, rowIndex, endColumnIndex) {\n return repeatFormat(\n sheetId,\n rowIndex,\n rowIndex + 1,\n 0,\n endColumnIndex,\n {\n backgroundColor: BLUE,\n horizontalAlignment: 'CENTER',\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { bold: true, foregroundColor: WHITE },\n },\n 'userEnteredFormat(backgroundColor,horizontalAlignment,verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nfunction titleFormat(sheetId, rowIndex, endColumnIndex, fontSize = 12) {\n return repeatFormat(\n sheetId,\n rowIndex,\n rowIndex + 1,\n 0,\n endColumnIndex,\n {\n backgroundColor: BLUE,\n horizontalAlignment: 'LEFT',\n verticalAlignment: 'MIDDLE',\n wrapStrategy: 'WRAP',\n textFormat: { bold: true, fontSize, foregroundColor: WHITE },\n },\n 'userEnteredFormat(backgroundColor,horizontalAlignment,verticalAlignment,wrapStrategy,textFormat)'\n );\n}\n\nconst formatRequests = [];\nfunction push(request) {\n if (request) formatRequests.push(request);\n}\n\n// 01 Bono 14 vs Banco\nconst mainEnd = mainValues.length;\npush(mergeRow(sheetIds.nominaVsBanco, 0, 10));\npush(mergeRow(sheetIds.nominaVsBanco, 1, 10));\npush(titleFormat(sheetIds.nominaVsBanco, 0, 10, 13));\npush(titleFormat(sheetIds.nominaVsBanco, 1, 10, 11));\npush(headerFormat(sheetIds.nominaVsBanco, 3, 10));\npush(standardBodyFormat(sheetIds.nominaVsBanco, 4, mainEnd, 10));\npush(moneyFormat(sheetIds.nominaVsBanco, 4, mainEnd, 4, 7));\npush(\n repeatFormat(\n sheetIds.nominaVsBanco,\n 4,\n mainEnd,\n 8,\n 9,\n {\n backgroundColor: LIGHT_RED,\n textFormat: { bold: true, foregroundColor: color(0.70, 0.20, 0.20) },\n horizontalAlignment: 'CENTER',\n },\n 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'\n )\n);\npush(basicFilter(sheetIds.nominaVsBanco, 3, mainEnd, 10));\npush(freeze(sheetIds.nominaVsBanco, 4));\n[\n [0, 1, 55], [1, 2, 220], [2, 3, 145], [3, 4, 75], [4, 7, 125],\n [7, 8, 185], [8, 9, 120], [9, 10, 230],\n].forEach(([s, e, px]) => push(setWidth(sheetIds.nominaVsBanco, s, e, px)));\npush(autoRows(sheetIds.nominaVsBanco, mainEnd));\n\nif (mainEnd > 4) {\n push({\n addConditionalFormatRule: {\n rule: {\n ranges: [{ sheetId: sheetIds.nominaVsBanco, startRowIndex: 4, endRowIndex: mainEnd, startColumnIndex: 6, endColumnIndex: 7 }],\n booleanRule: {\n condition: { type: 'CUSTOM_FORMULA', values: [{ userEnteredValue: '=$G5>0' }] },\n format: { backgroundColor: LIGHT_YELLOW, textFormat: { bold: true } },\n },\n },\n index: 0,\n },\n });\n push({\n addConditionalFormatRule: {\n rule: {\n ranges: [{ sheetId: sheetIds.nominaVsBanco, startRowIndex: 4, endRowIndex: mainEnd, startColumnIndex: 6, endColumnIndex: 7 }],\n booleanRule: {\n condition: { type: 'CUSTOM_FORMULA', values: [{ userEnteredValue: '=$G5<0' }] },\n format: { backgroundColor: LIGHT_RED, textFormat: { bold: true } },\n },\n },\n index: 0,\n },\n });\n}\npush(addProtection(sheetIds.nominaVsBanco, mainEnd, 9, 4));\n\n// 02 Banco sin Bono 14\nconst bankNoBonusEnd = 1 + bancoSinNominaRows.length;\npush(headerFormat(sheetIds.bancoSinNomina, 0, 7));\npush(standardBodyFormat(sheetIds.bancoSinNomina, 1, bankNoBonusEnd, 7));\npush(moneyFormat(sheetIds.bancoSinNomina, 1, bankNoBonusEnd, 3, 4));\npush(basicFilter(sheetIds.bancoSinNomina, 0, bankNoBonusEnd, 7));\npush(freeze(sheetIds.bancoSinNomina, 1));\n[[0,1,220],[1,2,145],[2,3,75],[3,4,125],[4,5,130],[5,6,320],[6,7,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.bancoSinNomina,s,e,px)));\npush(autoRows(sheetIds.bancoSinNomina, bankNoBonusEnd));\npush(addProtection(sheetIds.bancoSinNomina, bankNoBonusEnd, 6, 1));\n\n// 03 Banco sin Bamboo\nconst bambooEnd = bancoSinBambooValues.length;\npush(mergeRow(sheetIds.bancoSinBamboo, 0, 9));\npush(mergeRow(sheetIds.bancoSinBamboo, 1, 9));\npush(titleFormat(sheetIds.bancoSinBamboo, 0, 9, 13));\npush(titleFormat(sheetIds.bancoSinBamboo, 1, 9, 11));\npush(headerFormat(sheetIds.bancoSinBamboo, 3, 9));\npush(standardBodyFormat(sheetIds.bancoSinBamboo, 4, bambooEnd, 9));\npush(moneyFormat(sheetIds.bancoSinBamboo, 4, bambooEnd, 5, 6));\npush(\n repeatFormat(\n sheetIds.bancoSinBamboo,\n 4,\n bambooEnd,\n 7,\n 8,\n { backgroundColor: LIGHT_YELLOW, textFormat: { bold: true }, horizontalAlignment: 'CENTER' },\n 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'\n )\n);\npush(basicFilter(sheetIds.bancoSinBamboo, 3, bambooEnd, 9));\npush(freeze(sheetIds.bancoSinBamboo, 4));\n[[0,1,55],[1,2,220],[2,3,220],[3,4,145],[4,5,75],[5,6,125],[6,7,120],[7,8,155],[8,9,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.bancoSinBamboo,s,e,px)));\npush(autoRows(sheetIds.bancoSinBamboo, bambooEnd));\npush(addProtection(sheetIds.bancoSinBamboo, bambooEnd, 8, 4));\n\n// 04 Diferencias nombre banco\nconst diffNameEnd = 1 + diferenciasNombreRows.length;\npush(headerFormat(sheetIds.diferenciasNombreBanco, 0, 11));\npush(standardBodyFormat(sheetIds.diferenciasNombreBanco, 1, diffNameEnd, 11));\npush(moneyFormat(sheetIds.diferenciasNombreBanco, 1, diffNameEnd, 4, 5));\npush(basicFilter(sheetIds.diferenciasNombreBanco, 0, diffNameEnd, 11));\npush(freeze(sheetIds.diferenciasNombreBanco, 1));\n[[0,1,220],[1,2,220],[2,3,145],[3,4,75],[4,5,125],[5,7,115],[7,8,220],[8,9,130],[9,10,320],[10,11,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.diferenciasNombreBanco,s,e,px)));\npush(autoRows(sheetIds.diferenciasNombreBanco, diffNameEnd));\npush(addProtection(sheetIds.diferenciasNombreBanco, diffNameEnd, 10, 1));\n\n// 05 Cuenta Mal Digitada (opcional)\nif (hasCuentaMalDigitada) {\n const wrongEnd = cuentaMalDigitadaValues.length;\n push(mergeRow(sheetIds.cuentaMalDigitada, 0, 4));\n push(mergeRow(sheetIds.cuentaMalDigitada, 1, 4));\n push(titleFormat(sheetIds.cuentaMalDigitada, 0, 4, 13));\n push(titleFormat(sheetIds.cuentaMalDigitada, 1, 4, 11));\n push(headerFormat(sheetIds.cuentaMalDigitada, 3, 4));\n push(standardBodyFormat(sheetIds.cuentaMalDigitada, 4, wrongEnd, 4));\n push(freeze(sheetIds.cuentaMalDigitada, 4));\n [[0,1,55],[1,2,270],[2,3,640],[3,4,230]].forEach(([s,e,px]) => push(setWidth(sheetIds.cuentaMalDigitada,s,e,px)));\n push(autoRows(sheetIds.cuentaMalDigitada, wrongEnd));\n push(addProtection(sheetIds.cuentaMalDigitada, wrongEnd, 3, 4));\n}\n\n// Resumen\nconst summaryEnd = 1 + resumenRows.length;\npush(headerFormat(sheetIds.resumen, 0, 2));\npush(standardBodyFormat(sheetIds.resumen, 1, summaryEnd, 2));\npush(\n repeatFormat(\n sheetIds.resumen,\n 1,\n summaryEnd,\n 0,\n 1,\n { backgroundColor: LIGHT_BLUE, textFormat: { bold: true, foregroundColor: DARK } },\n 'userEnteredFormat(backgroundColor,textFormat)'\n )\n);\npush(freeze(sheetIds.resumen, 1));\npush(setWidth(sheetIds.resumen, 0, 1, 310));\npush(setWidth(sheetIds.resumen, 1, 2, 260));\npush(autoRows(sheetIds.resumen, summaryEnd));\npush(addProtection(sheetIds.resumen, summaryEnd, null, 1));\n\n/*\n * La columna Resolución queda fuera de la protección general de la hoja,\n * pero se protege por separado para que solo Viviana Páramo y Liliana\n * Benítez puedan editarla.\n */\npush(protectedResolutionRequest(\n sheetIds.nominaVsBanco,\n mainEnd,\n 9,\n 4\n));\npush(protectedResolutionRequest(\n sheetIds.bancoSinNomina,\n bankNoBonusEnd,\n 6,\n 1\n));\npush(protectedResolutionRequest(\n sheetIds.bancoSinBamboo,\n bambooEnd,\n 8,\n 4\n));\npush(protectedResolutionRequest(\n sheetIds.diferenciasNombreBanco,\n diffNameEnd,\n 10,\n 1\n));\nif (hasCuentaMalDigitada) {\n push(protectedResolutionRequest(\n sheetIds.cuentaMalDigitada,\n cuentaMalDigitadaValues.length,\n 3,\n 4\n ));\n}\n\nconst desiredSheets = [\n {\n properties: {\n sheetId: sheetIds.nominaVsBanco,\n title: sheetTitles.nominaVsBanco,\n index: 0,\n gridProperties: { rowCount: Math.max(200, mainEnd + 20), columnCount: 10 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.bancoSinNomina,\n title: sheetTitles.bancoSinNomina,\n index: 1,\n gridProperties: { rowCount: Math.max(200, bankNoBonusEnd + 20), columnCount: 7 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.bancoSinBamboo,\n title: sheetTitles.bancoSinBamboo,\n index: 2,\n gridProperties: { rowCount: Math.max(200, bambooEnd + 20), columnCount: 9 },\n },\n },\n {\n properties: {\n sheetId: sheetIds.diferenciasNombreBanco,\n title: sheetTitles.diferenciasNombreBanco,\n index: 3,\n gridProperties: { rowCount: Math.max(200, diffNameEnd + 20), columnCount: 11 },\n },\n },\n ...(hasCuentaMalDigitada\n ? [\n {\n properties: {\n sheetId: sheetIds.cuentaMalDigitada,\n title: sheetTitles.cuentaMalDigitada,\n index: 4,\n gridProperties: {\n rowCount: Math.max(200, cuentaMalDigitadaValues.length + 20),\n columnCount: 4,\n },\n },\n },\n ]\n : []),\n {\n properties: {\n sheetId: sheetIds.resumen,\n title: sheetTitles.resumen,\n index: hasCuentaMalDigitada ? 5 : 4,\n gridProperties: { rowCount: 200, columnCount: 2 },\n },\n },\n];\n\nreturn [\n {\n json: {\n ok: data.ok ?? true,\n stage: 'google_sheet_bono14_preparado',\n metadata,\n summary,\n sheetIds,\n sheetTitles,\n spreadsheetTitle,\n createSpreadsheetBody: {\n properties: {\n title: spreadsheetTitle,\n locale: 'es_GT',\n timeZone: 'America/Guatemala',\n },\n sheets: desiredSheets,\n },\n valueBatchBody: {\n valueInputOption: 'USER_ENTERED',\n data: valueData,\n },\n formatBatchBody: {\n requests: formatRequests,\n },\n originalResponse: data,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 8016, - 3280 + 8928, + 3664 ], - "id": "9c64018c-0cb3-486f-9d23-7130d711cb97", + "id": "85af4a31-dac6-4982-9e99-9874a9e9ee69", "name": "Preparar Google Sheet Bono 14" }, { @@ -2077,10 +2077,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 8256, - 3296 + 9168, + 3680 ], - "id": "c5ef1b72-12c6-48c0-bd6c-6e410704a2e6", + "id": "6df55b98-62ff-4db4-83df-35e5f7b80b76", "name": "Crear Google Sheet", "credentials": { "httpBasicAuth": { @@ -2109,10 +2109,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 9472, - 3280 + 10384, + 3664 ], - "id": "64e1694d-6b0a-411c-86f7-636042887223", + "id": "75569fdd-ef58-4feb-87d5-e3b0996d72ea", "name": "Escribir Google Sheet", "credentials": { "googleOAuth2Api": { @@ -2137,10 +2137,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 9712, - 3280 + 10624, + 3664 ], - "id": "c5d3a10e-4614-4f85-a624-5d9c99fb2629", + "id": "c291d5ce-6d75-49c6-b2be-d54b15a0dc1f", "name": "Formatear Google Sheet", "credentials": { "googleOAuth2Api": { @@ -2156,23 +2156,23 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 11648, - 3280 + 12560, + 3664 ], - "id": "02afa713-5438-4ab4-afaf-bb8cb1d4a3d6", + "id": "de676b6c-7834-4d49-8dc4-e50165e70c4e", "name": "Preparar respuesta final" }, { "parameters": { - "jsCode": "const context =\n $('Preparar actualización Google Sheet').first().json || {};\n\nconst spreadsheetId =\n context.spreadsheetId;\n\nif (!spreadsheetId) {\n throw new Error(\n 'No se recibió spreadsheetId para configurar permisos.'\n );\n}\n\nconst allowedEmails = [\n 'iaracena@gomezleemarketing.com',\n 'ymadera@gomezleemarketing.com',\n 'mgomez@gomezleemarketing.com',\n 'jgomez@gomezleemarketing.com',\n 'vparamo@gomezleemarketing.com',\n 'jsepulveda@gomezlee.marketing',\n 'mbenitez@gomezlee.marketing',\n];\n\nreturn allowedEmails.map((email) => ({\n json: {\n spreadsheetId,\n email,\n permissionBody: {\n type: 'user',\n role: 'writer',\n emailAddress: email,\n },\n reportUpdated:\n context.reportUpdated === true,\n },\n}));" + "jsCode": "const context =\n $('Preparar actualización Google Sheet').first().json || {};\n\nconst spreadsheetId =\n context.spreadsheetId;\n\nif (!spreadsheetId) {\n throw new Error(\n 'No se recibió spreadsheetId para configurar permisos.'\n );\n}\n\nconst allowedEmails = [\n 'iaracena@gomezleemarketing.com',\n 'ymadera@gomezleemarketing.com',\n 'mgomez@gomezleemarketing.com',\n 'jgomez@gomezleemarketing.com',\n 'vparamo@gomezleemarketing.com',\n 'adminregional@gomezleemarketing.com',\n 'jsepulveda@gomezlee.marketing',\n 'mbenitez@gomezlee.marketing',\n];\n\nreturn allowedEmails.map((email) => ({\n json: {\n spreadsheetId,\n email,\n permissionBody: {\n type: 'user',\n role: 'writer',\n emailAddress: email,\n },\n reportUpdated:\n context.reportUpdated === true,\n },\n}));" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 10608, - 3280 + 11520, + 3664 ], - "id": "c465e89a-e976-4f8d-96fd-21ad85aa965c", + "id": "cb6559a6-ca00-4e60-92cb-d4acbcacd51b", "name": "Preparar permisos Google Sheet" }, { @@ -2189,10 +2189,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 10848, - 3280 + 11760, + 3664 ], - "id": "e36f25f7-f99b-4a7b-ac0e-2e653d86a967", + "id": "8c951923-b751-4e56-a581-3ac911fa0f3b", "name": "Compartir Google Sheet", "credentials": { "googleOAuth2Api": { @@ -2209,10 +2209,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 11168, - 3280 + 12080, + 3664 ], - "id": "9a375998-957b-4e2f-9ac0-aea3b5ce3cdd", + "id": "5acbdf9e-75f7-4813-9679-80e066b584a4", "name": "Preparar histórico Supabase" }, { @@ -2248,10 +2248,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 11408, - 3280 + 12320, + 3664 ], - "id": "aee129de-29ee-4808-a480-a0d0c1c9fa4f", + "id": "4b0b39c1-d982-48a9-a59d-cd4d74555548", "name": "Insertar histórico Supabase", "onError": "continueRegularOutput" }, @@ -2267,10 +2267,10 @@ "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ - 1360, - 4400 + 2272, + 4784 ], - "id": "dcd3aab6-326d-4a6e-9f80-5645203de828", + "id": "54f6a317-a276-4695-9490-3c0c50dd046d", "name": "Extract Bono14 - Resumen 4", "retryOnFail": false, "onError": "continueRegularOutput" @@ -2280,10 +2280,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 4928, - 4064 + 5840, + 4448 ], - "id": "1e8c09ae-f71c-4bb7-905c-4f873c1a9811", + "id": "0cb9a6c8-a0d3-4990-bcfa-e1f73a3dd8a7", "name": "Merge Hojas 10" }, { @@ -2332,10 +2332,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 912, - 2336 + 1824, + 2720 ], - "id": "ca5bc0bf-fd2a-4bdf-b7ac-34628f666975", + "id": "5f778c49-fc3b-4710-8066-2409e3955c91", "name": "HTTP - Empleados BambooHR Bono14 GT", "retryOnFail": true, "maxTries": 3, @@ -2354,10 +2354,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 1136, - 2336 + 2048, + 2720 ], - "id": "867f76e7-7db9-4259-9e61-6d0dc9acbbeb", + "id": "a9e924bb-dae3-49ca-9444-7c74724e3dee", "name": "Normalizar BambooHR Bono14 GT" }, { @@ -2369,10 +2369,10 @@ "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ - 7360, - 3280 + 8272, + 3664 ], - "id": "b27bfa42-456c-45de-98f0-c7fe919ee8af", + "id": "e85cc2d0-c994-4cda-861d-c083decd3d83", "name": "Merge - Agregar BambooHR Bono14" }, { @@ -2385,10 +2385,10 @@ "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ - 128, - 1760 + 1040, + 2144 ], - "id": "2e44f1cc-edb2-4fb8-af15-2441068ba6f1", + "id": "b9db02e6-ca68-4741-96de-db9219e47ccf", "name": "Sticky Note" }, { @@ -2401,10 +2401,10 @@ "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ - 2064, - 1904 + 2976, + 2288 ], - "id": "c346fd57-b420-473e-8131-7b60bde1d960", + "id": "2c5b1a6b-f7a2-443b-81be-8c10de6b64a5", "name": "Sticky Note1" }, { @@ -2417,10 +2417,10 @@ "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ - 6448, - 3056 + 7360, + 3440 ], - "id": "ad3da1da-1a16-4980-9d1b-fa11c0048b95", + "id": "91f0403f-d9b4-4e43-9164-9e58683f2e63", "name": "Sticky Note2" }, { @@ -2433,10 +2433,10 @@ "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ - 10064, - 2976 + 10976, + 3360 ], - "id": "46289c23-7cda-434d-827c-f47ad3ba9146", + "id": "c2f51855-dd3c-4247-9aac-7ba6418ddb7a", "name": "Sticky Note3" }, { @@ -2446,10 +2446,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 11648, - 3600 + 12560, + 3984 ], - "id": "1a73c8b6-7f06-4e5d-a83a-f731bd96a1ad", + "id": "8b23d8bb-72d1-4238-8d87-9ea4900098d7", "name": "Preparar correo reporte generado" }, { @@ -2464,10 +2464,10 @@ "type": "n8n-nodes-base.gmail", "typeVersion": 2.2, "position": [ - 11888, - 3600 + 12800, + 3984 ], - "id": "fbf05240-1dd2-4cde-b7ca-7ebb81136295", + "id": "0b6009ef-2641-4485-a0d3-ce7c65a08db4", "name": "Enviar correo reporte generado", "webhookId": "c10f5aec-68bc-44d9-8152-2e14f8898e02", "credentials": { @@ -2520,10 +2520,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 7584, - 3264 + 8496, + 3648 ], - "id": "aa995943-d367-449f-a720-7aa37c2862b5", + "id": "87f38db5-8ac0-4e95-813c-df037dcf55fb", "name": "Obtener alias BambooHR Bono14 Supabase", "retryOnFail": true, "maxTries": 2, @@ -2570,10 +2570,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 7792, - 3280 + 8704, + 3664 ], - "id": "47e29f81-ee8d-429f-a461-0288c86bf5e2", + "id": "d51126f5-5c4f-4ea7-9ade-f79303cc48e0", "name": "Buscar reporte existente Supabase" }, { @@ -2588,10 +2588,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 8512, - 3280 + 9424, + 3664 ], - "id": "26b3e1a2-ebfd-4cbd-a53d-5fd9244a4de1", + "id": "38f92ba2-e407-49f2-8146-fbbee84e714a", "name": "Leer Google Sheet actual", "credentials": { "googleOAuth2Api": { @@ -2607,10 +2607,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 8752, - 3280 + 9664, + 3664 ], - "id": "09a08613-346b-4121-9025-08a84a4a9329", + "id": "b7a12346-da22-45e5-ad35-5c1808bc3454", "name": "Preparar actualización Google Sheet" }, { @@ -2629,10 +2629,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 8992, - 3280 + 9904, + 3664 ], - "id": "c530c0ac-227b-4596-a261-540bc0799dce", + "id": "0501892c-ef8d-48da-9b41-8234b0a5dba9", "name": "Actualizar estructura Google Sheet", "credentials": { "googleOAuth2Api": { @@ -2657,10 +2657,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 9232, - 3280 + 10144, + 3664 ], - "id": "65330e06-3297-4213-9385-de6ad2b62026", + "id": "09488c5b-90cb-4b68-b13b-a1d68599c63b", "name": "Limpiar Google Sheet", "credentials": { "googleOAuth2Api": { @@ -3244,15 +3244,15 @@ } }, "authors": "Isaac Aracena", - "name": "Version fa138609", + "name": "Version 91b012d4", "description": "", - "autosaved": true, + "autosaved": false, "workflowPublishHistory": [ { - "createdAt": "2026-08-14T12:12:02.974Z", - "id": 4688, + "createdAt": "2026-08-19T15:20:44.974Z", + "id": 4956, "workflowId": "ox03EQlSmgjUCQuK", - "versionId": "fa138609-9867-4b15-b685-759ea211b234", + "versionId": "91b012d4-b842-4c67-805c-466e60284978", "event": "activated", "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" }