diff --git a/bamboohr-agent-core.json b/bamboohr-agent-core.json index a9a6acc..002bb68 100644 --- a/bamboohr-agent-core.json +++ b/bamboohr-agent-core.json @@ -1,9 +1,9 @@ { - "updatedAt": "2026-07-06T19:00:02.500Z", + "updatedAt": "2026-07-16T16:14:20.251Z", "createdAt": "2026-06-25T13:03:04.442Z", "id": "5VOiadzdgbs2Qq1N", "name": "BambooHR Agent Core", - "description": null, + "description": "Flujo central del agente de BambooHR encargado de interpretar solicitudes, validar permisos, consultar o modificar información de empleados y registrar la trazabilidad de cada operación.", "active": true, "isArchived": false, "nodes": [ @@ -26,8 +26,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -4128, - -7072 + -3248, + -8896 ], "id": "276ea916-3fab-46f5-a708-38ce0cce5bbe", "name": "HTTP - Employees Directory", @@ -44,8 +44,8 @@ "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ - -20688, - -2400 + -23328, + -2144 ], "id": "5c339d4f-7a43-497c-813c-10a0fe9edc8a", "name": "BambooHR Agent Core" @@ -59,8 +59,8 @@ "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ - -20480, - -2400 + -23120, + -2144 ], "id": "ca88cf95-285f-4389-84c9-458198f82f94", "name": "Set - Incoming Message" @@ -73,8 +73,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10320, - -2304 + -11328, + -1168 ], "id": "16424bed-7d3c-4d1d-8da7-8b8c1437a9f6", "name": "Code - Intent Parser" @@ -87,8 +87,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -9808, - -2288 + -10816, + -1152 ], "id": "3f7772d0-ca2c-446f-a790-84881562ab84", "name": "Code - Validate Intent" @@ -192,8 +192,8 @@ "type": "n8n-nodes-base.splitOut", "typeVersion": 1, "position": [ - -3888, - -7072 + -3008, + -8896 ], "id": "88fcede6-4eea-4b65-b320-1a86d1da10ed", "name": "Split Out - Employees" @@ -205,8 +205,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -3680, - -7072 + -2800, + -8896 ], "id": "8fb4ee82-f72d-46f3-b872-2b4bbb51849b", "name": "Code - Resolve Employee" @@ -298,8 +298,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -9600, - -2288 + -10608, + -1152 ], "id": "8dfb0930-863b-4b86-905c-c457933efe42", "name": "IF - Can Continue?" @@ -332,8 +332,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -3120, - -7072 + -2240, + -8896 ], "id": "b6726c6b-7238-4cd1-92e8-a08306b2333f", "name": "IF - Employee Found?" @@ -346,8 +346,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12240, - -2592 + -13248, + -1456 ], "id": "082b337a-96ee-4cc6-960f-06351672df90", "name": "Code - Request Context" @@ -889,8 +889,8 @@ "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ - 1408, - -8704 + 1280, + -9920 ], "id": "e79dd7ae-db69-489d-8d77-6a96c9ad7aaa", "name": "Switch - Post Resolve Mode" @@ -903,8 +903,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 3840, - -8416 + 4592, + -9920 ], "id": "f769b2b5-7055-4052-9b39-f1b3b85e8b4e", "name": "Code - Action Confirmation Guard" @@ -937,8 +937,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 4144, - -8416 + 4896, + -9920 ], "id": "64ab7961-a9fb-435d-b109-c82bb40e2a3a", "name": "IF - Action Approved?" @@ -1182,8 +1182,8 @@ "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ - 4416, - -9040 + 5168, + -10544 ], "id": "6236bf70-4f09-49b9-b11c-6d8f4d1cb88a", "name": "Switch - Action Type" @@ -1437,8 +1437,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -4464, - -6112 + -4448, + -6656 ], "id": "0f430b61-50b6-4d78-9e43-a910091ccaea", "name": "Code - Create Employee Confirmation Guard" @@ -1471,7 +1471,7 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -1504, + -1248, -2800 ], "id": "13549ab1-68a8-4630-b6d9-e08fec43950b", @@ -1837,8 +1837,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 6224, - -8544 + 6496, + -9648 ], "id": "b37a3f61-7d9e-42a2-beb2-760e0ffba872", "name": "Code - Format Prepare Termination Response" @@ -1851,8 +1851,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -1920, - -6352 + -1824, + -8896 ], "id": "126b4979-c77e-4640-8b19-b5c9b2ff323c", "name": "Code - Check Direct Employee ID Fallback" @@ -1885,8 +1885,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -1152, - -6176 + -1056, + -8720 ], "id": "65ffcff8-b8b0-42be-b9b8-965773255145", "name": "IF - Has Direct Employee ID?" @@ -1910,8 +1910,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 64, - -7152 + 0, + -9632 ], "id": "bb411b52-5759-4802-a6e7-c198f2f4ad2a", "name": "HTTP - Get Employee By Direct ID", @@ -1930,8 +1930,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 384, - -7152 + 320, + -9632 ], "id": "1e200e58-2b3b-4e63-8615-35cc2fad5871", "name": "Code - Normalize Direct Employee Resolve" @@ -2389,8 +2389,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -9216, - -2384 + -10224, + -1248 ], "id": "46fc792b-dd60-4083-a4df-9dfbdf90c3ab", "name": "Supabase - Get User Permissions", @@ -2403,8 +2403,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -8912, - -2624 + -9920, + -1488 ], "id": "5ca00b0d-75b2-4ed4-b8a2-e9c609b8c112", "name": "Code - Authorization Gate" @@ -2437,8 +2437,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -6832, - -2768 + -7840, + -1632 ], "id": "c3ca5e91-a459-44b6-ad32-188f9c829e5c", "name": "IF - User Authorized?" @@ -2451,8 +2451,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 848, - -8560 + 720, + -9776 ], "id": "58501789-c3aa-4161-b738-6b7d66be80a3", "name": "Code - Employee Scope Gate" @@ -2485,8 +2485,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 1056, - -8560 + 928, + -9776 ], "id": "f4885736-891b-4bc4-bee7-8e4328861e8a", "name": "IF - Employee Scope Allowed?" @@ -2546,8 +2546,8 @@ "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ - -22128, - -2880 + -25312, + -2864 ], "id": "f689f4e1-63a4-436c-a15f-9859a9b8a0fd", "name": "Webhook - Google Chat Incoming", @@ -2562,8 +2562,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -20912, - -2880 + -24096, + -2864 ], "id": "ca5fb655-16b5-4211-a983-ced7e5adfec5", "name": "Code - Normalize Google Chat Input" @@ -2683,8 +2683,8 @@ "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ - -20480, - -2880 + -23856, + -2864 ], "id": "a8fb64d3-ace3-4f5f-8f00-03f084efb70a", "name": "Respond - Google Chat Processing" @@ -2697,8 +2697,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -20272, - -2880 + -22928, + -2896 ], "id": "5fb42bef-9c9b-4b09-8f26-a93eb7b9a060", "name": "Code - Detect Confirmation Reply" @@ -2731,8 +2731,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -20064, - -2880 + -22720, + -2896 ], "id": "181f33b5-375d-482f-8119-0ef81d66aab2", "name": "IF - Is Confirmation Reply?" @@ -2766,8 +2766,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -19712, - -2992 + -22368, + -3008 ], "id": "9bad8293-fbd6-4669-ae66-e3712ac3c710", "name": "Supabase - Get Pending Confirmation", @@ -2780,8 +2780,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -19504, - -2992 + -22160, + -3008 ], "id": "38c4d2b8-9b0a-4982-972b-80d8e45fada0", "name": "Code - Resolve Confirmation Reply" @@ -2813,8 +2813,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -13584, - -3504 + -14592, + -2368 ], "id": "2b77cc64-771f-4758-b414-73e036f188be", "name": "IF - Confirmation Cancelled?" @@ -2852,8 +2852,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -12512, - -3456 + -13520, + -2320 ], "id": "8bf1e0b7-767c-464e-b3cd-57cdac1136aa", "name": "Supabase - Mark Confirmation Cancelled" @@ -2967,8 +2967,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12112, - -3456 + -13120, + -2320 ], "id": "919396ee-523d-4db9-8a35-840ee4c7992c", "name": "Code - Restore Cancelled Confirmation Response" @@ -3001,8 +3001,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -12912, - -2896 + -13920, + -1760 ], "id": "85e01b65-8296-486e-bbca-dc856e77dba8", "name": "IF - Confirmation Confirmed?" @@ -3040,8 +3040,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -12704, - -3056 + -13712, + -1920 ], "id": "6406f5d1-5c6f-4d40-961a-1903269d777b", "name": "Supabase - Mark Confirmation Confirmed" @@ -3054,8 +3054,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12496, - -3056 + -13504, + -1920 ], "id": "1d69b228-521c-45a6-bf25-86ae602881a2", "name": "Code - Restore Confirmed Confirmation Response" @@ -3121,8 +3121,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -11312, - -2592 + -12320, + -1456 ], "id": "b354a006-560d-4784-a882-d4a8ffa9fc38", "name": "Code - Build Gemini Intent Prompt" @@ -3137,8 +3137,8 @@ "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini", "typeVersion": 1.1, "position": [ - -11232, - -2384 + -12240, + -1248 ], "id": "5314a9c0-b3fc-4e41-9a69-d1b396e084af", "name": "Google Gemini Chat Model", @@ -3161,8 +3161,8 @@ "type": "@n8n/n8n-nodes-langchain.agent", "typeVersion": 3.1, "position": [ - -11088, - -2592 + -12096, + -1456 ], "id": "3bffe8fe-5617-4805-b10a-f53e50f2af1b", "name": "AI Agent - Gemini Intent Interpreter" @@ -3175,8 +3175,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10736, - -2592 + -11744, + -1456 ], "id": "bab585e3-0cca-40b6-96ef-5e031a6f9759", "name": "Code - Normalize Gemini Intent" @@ -3217,8 +3217,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -688, - -6160 + -592, + -8704 ], "id": "9008719a-7dd9-4791-a6a2-567580c6b0af", "name": "HTTP - List Employees For Resolver", @@ -3237,8 +3237,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 112, - -6192 + 208, + -8736 ], "id": "5d695bb8-10c4-4944-9739-f9c78cf37bd2", "name": "Code - Match Employee From List Employees" @@ -3797,19 +3797,6 @@ } } }, - { - "parameters": { - "jsCode": "const requestContext = $items('Code - Build Saved Report Export Request')[0].json;\n\nfunction isEmptyObject(value) {\n return (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0\n );\n}\n\nfunction normalizeReportBody(items) {\n if (!items || items.length === 0) {\n return {\n rows: [],\n pagination: null\n };\n }\n\n const body = items[0].json;\n\n if (!body || isEmptyObject(body)) {\n return {\n rows: [],\n pagination: null\n };\n }\n\n if (Array.isArray(body)) {\n return {\n rows: body,\n pagination: null\n };\n }\n\n return {\n rows: Array.isArray(body.data)\n ? body.data\n : Array.isArray(body.employees)\n ? body.employees\n : Array.isArray(body.rows)\n ? body.rows\n : [],\n pagination: body.pagination || null\n };\n}\n\nfunction collectColumns(rows) {\n const columns = [];\n const seen = new Set();\n\n for (const row of rows) {\n for (const key of Object.keys(row || {})) {\n if (!seen.has(key)) {\n seen.add(key);\n columns.push(key);\n }\n }\n }\n\n return columns;\n}\n\nconst normalized = normalizeReportBody(items);\nconst rows = normalized.rows;\nconst pagination = normalized.pagination || {};\n\nconst totalRows =\n pagination.total_records ||\n pagination.totalRecords ||\n pagination.total ||\n pagination.count ||\n rows.length;\n\nconst pageSize =\n pagination.page_size ||\n pagination.pageSize ||\n requestContext.saved_report_page_size ||\n rows.length;\n\nconst totalPages =\n totalRows > 0 && pageSize > 0\n ? Math.max(1, Math.ceil(totalRows / pageSize))\n : 1;\n\nconst columns = collectColumns(rows);\n\nconst isCompleteInOneRequest =\n rows.length >= totalRows || totalPages <= 1;\n\nconst responseText =\n `Exportación preparada para reporte guardado ID ${requestContext.saved_report_id}\\n\\n` +\n `Reporte: ${requestContext.saved_report_name || 'Nombre no disponible'}\\n` +\n `Filas recibidas en esta primera consulta: ${rows.length}\\n` +\n `Total reportado por BambooHR: ${totalRows}\\n` +\n `Columnas detectadas: ${columns.length}\\n` +\n `Páginas estimadas: ${totalPages}\\n\\n` +\n (\n isCompleteInOneRequest\n ? `Estado: este reporte cabe completo en una sola consulta. Próximo paso: generar Google Sheet.`\n : `Estado: este reporte tiene varias páginas. Próximo paso: crear paginación completa antes de generar Google Sheet.`\n );\n\nreturn [\n {\n json: {\n ...requestContext,\n\n status: 'saved_report_export_preview_ready',\n can_continue: true,\n response_type: 'chat_message',\n\n saved_report_export_rows_first_page: rows,\n saved_report_export_columns: columns,\n saved_report_export_rows_received: rows.length,\n saved_report_export_total_rows: totalRows,\n saved_report_export_page_size: pageSize,\n saved_report_export_total_pages: totalPages,\n saved_report_export_complete_in_one_request: isCompleteInOneRequest,\n\n response_text: responseText,\n\n audit_event: {\n ...(requestContext.audit_event || {}),\n\n execution_result: 'saved_report_export_preview_ready',\n\n saved_report_export_rows_received: rows.length,\n saved_report_export_total_rows: totalRows,\n saved_report_export_columns_count: columns.length,\n saved_report_export_total_pages: totalPages,\n saved_report_export_complete_in_one_request: isCompleteInOneRequest,\n\n bamboohr_endpoint: `/api/v1/custom-reports/${requestContext.saved_report_id}`\n }\n }\n }\n];" - }, - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - -2096, - -6112 - ], - "id": "22d28c82-863a-48c0-b9be-d09d23531248", - "name": "Code - Format Saved Report Export Preview" - }, { "parameters": { "jsCode": "const requestContext = $json;\n\nfunction cleanText(value) {\n return String(value || '').trim();\n}\n\nfunction isEmptyObject(value) {\n return (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0\n );\n}\n\nfunction normalizeRowsFromCurrentInput(data) {\n if (!data || isEmptyObject(data)) {\n return [];\n }\n\n if (Array.isArray(data.saved_report_export_rows)) {\n return data.saved_report_export_rows;\n }\n\n if (Array.isArray(data.data)) {\n return data.data;\n }\n\n if (Array.isArray(data.employees)) {\n return data.employees;\n }\n\n if (Array.isArray(data.rows)) {\n return data.rows;\n }\n\n return [];\n}\n\nfunction collectColumns(rows) {\n const columns = [];\n const seen = new Set();\n\n for (const row of rows) {\n for (const key of Object.keys(row || {})) {\n if (!seen.has(key)) {\n seen.add(key);\n columns.push(key);\n }\n }\n }\n\n return columns;\n}\n\nfunction getFriendlyColumnName(key) {\n const map = {\n id: 'ID',\n employeeId: 'ID empleado',\n employeeNumber: 'Número empleado',\n\n displayName: 'Nombre',\n lastNameFirstName: 'Nombre',\n firstName: 'Nombre',\n middleName: 'Segundo nombre',\n lastName: 'Apellido',\n preferredName: 'Nombre preferido',\n\n hireDate: 'Fecha de ingreso',\n originalHireDate: 'Fecha de ingreso original',\n terminationDate: 'Fecha de salida',\n\n country: 'País',\n location: 'Ubicación',\n\n department: 'Departamento',\n division: 'División',\n jobTitle: 'Cargo',\n\n jobInformationJobTitle: 'Cargo',\n jobInformationDepartment: 'Departamento',\n jobInformationDivision: 'División',\n jobInformationLocation: 'Ubicación',\n jobInformationReportsTo: 'Supervisor',\n\n supervisor: 'Supervisor',\n supervisorEmail: 'Correo supervisor',\n\n workEmail: 'Correo laboral',\n email: 'Correo',\n workPhone: 'Teléfono laboral',\n workPhoneExtension: 'Extensión laboral',\n\n status: 'Estatus',\n employmentStatus: 'Estatus laboral',\n employmentStatusDate: 'Fecha estatus laboral'\n };\n\n if (map[key]) {\n return map[key];\n }\n\n const customFieldMatch = String(key || '').match(/^customField(\\d+)$/i);\n\n if (customFieldMatch) {\n return `Campo personalizado ${customFieldMatch[1]}`;\n }\n\n return String(key || '')\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/_/g, ' ')\n .trim();\n}\n\nfunction buildSpreadsheetTitle(reportId, reportName) {\n const now = new Date();\n\n const timestamp = now\n .toISOString()\n .replace('T', ' ')\n .slice(0, 16)\n .replace(/:/g, '-');\n\n const safeName = cleanText(reportName || `Reporte ${reportId || 'sin-id'}`)\n .replace(/[\\\\/:*?\"<>|#%{}[\\]~]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 80);\n\n return `BambooHR - ${safeName} - ${timestamp}`;\n}\n\nconst reportId =\n requestContext.saved_report_id ||\n requestContext.report_id ||\n requestContext.filters?.report_id ||\n requestContext.data?.filters?.report_id ||\n 'sin-id';\n\nconst reportName =\n requestContext.saved_report_name ||\n requestContext.filters?.report_name ||\n requestContext.data?.filters?.report_name ||\n requestContext.saved_report_name_query ||\n `Reporte ${reportId}`;\n\nconst rows = normalizeRowsFromCurrentInput(requestContext);\n\nconst columns =\n Array.isArray(requestContext.saved_report_export_columns) &&\n requestContext.saved_report_export_columns.length > 0\n ? requestContext.saved_report_export_columns\n : collectColumns(rows);\n\nconst columnLabels = columns.map(getFriendlyColumnName);\n\nconst totalRows =\n Number(\n requestContext.saved_report_total_records ||\n requestContext.saved_report_export_total_records_expected ||\n rows.length\n ) || rows.length;\n\nconst pageSize =\n Number(\n requestContext.saved_report_page_size ||\n requestContext.saved_report_export_page_size ||\n rows.length\n ) || rows.length;\n\nconst estimatedPages =\n Number(\n requestContext.saved_report_total_pages ||\n requestContext.saved_report_estimated_pages ||\n requestContext.saved_report_export_total_pages_expected ||\n (\n totalRows > 0 && pageSize > 0\n ? Math.max(1, Math.ceil(totalRows / pageSize))\n : 1\n )\n ) || 1;\n\nconst spreadsheetTitle = buildSpreadsheetTitle(reportId, reportName);\n\nif (!rows.length) {\n return [\n {\n json: {\n ...requestContext,\n\n status: 'saved_report_google_sheet_blocked_no_rows',\n can_continue: false,\n response_type: 'error_message',\n\n saved_report_id: String(reportId),\n report_id: String(reportId),\n saved_report_name: reportName,\n\n saved_report_export_rows: [],\n saved_report_export_columns: columns,\n saved_report_export_column_labels: columnLabels,\n\n response_text:\n `No puedo crear el Google Sheet porque el reporte no devolvió filas.\\n\\n` +\n `Reporte: ${reportName}\\n` +\n `ID BambooHR: ${reportId}`,\n\n audit_event: {\n ...(requestContext.audit_event || {}),\n\n execution_result: 'saved_report_google_sheet_blocked_no_rows',\n\n saved_report_id: String(reportId),\n saved_report_name: reportName,\n saved_report_rows_received: 0,\n saved_report_total_records: totalRows,\n saved_report_page_size: pageSize,\n saved_report_estimated_pages: estimatedPages\n }\n }\n }\n ];\n}\n\nreturn [\n {\n json: {\n ...requestContext,\n\n status: 'saved_report_google_sheet_prepared',\n can_continue: true,\n response_type: 'chat_message',\n\n saved_report_id: String(reportId),\n report_id: String(reportId),\n saved_report_name: reportName,\n\n saved_report_export_rows: rows,\n saved_report_export_columns: columns,\n saved_report_export_column_labels: columnLabels,\n\n saved_report_rows_received: rows.length,\n saved_report_total_records: totalRows,\n saved_report_page_size: pageSize,\n saved_report_estimated_pages: estimatedPages,\n\n google_sheet_title: spreadsheetTitle,\n google_sheet_first_sheet_name: 'Reporte',\n\n audit_event: {\n ...(requestContext.audit_event || {}),\n\n execution_result: 'saved_report_google_sheet_prepared',\n\n saved_report_id: String(reportId),\n saved_report_name: reportName,\n\n saved_report_rows_received: rows.length,\n saved_report_total_records: totalRows,\n saved_report_page_size: pageSize,\n saved_report_estimated_pages: estimatedPages,\n\n google_sheet_title: spreadsheetTitle\n }\n }\n }\n];" @@ -4044,8 +4031,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10112, - -2304 + -11120, + -1168 ], "id": "71cc5ff5-97ae-462a-9c2e-4d7052b39b4e", "name": "Code - Apply Gemini Interpretation" @@ -4058,8 +4045,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -8704, - -2624 + -9712, + -1488 ], "id": "f76ccb84-0963-4fa3-9f3b-1b1966ef1fd5", "name": "Code - Sensitive Approval Gate" @@ -4092,8 +4079,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -8496, - -2624 + -9504, + -1488 ], "id": "6649f03d-f59e-478a-afce-ab713886959d", "name": "IF - Needs Maximo Approval?" @@ -4106,8 +4093,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -8240, - -2816 + -9248, + -1680 ], "id": "32ecee10-ec1a-447b-af5f-28f2b3ffbd08", "name": "Code - Build Maximo Approval Record" @@ -4145,8 +4132,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -8032, - -2816 + -9040, + -1680 ], "id": "5e9e8c59-fea4-4ab4-9265-99250cdbc7ce", "name": "HTTP - Insert Pending Maximo Approval", @@ -4160,8 +4147,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -7536, - -2816 + -8544, + -1680 ], "id": "9e478fe8-1785-4b92-9329-34fbe14784d5", "name": "Code - Format Maximo Approval Pending Response" @@ -4195,8 +4182,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -7712, - -3104 + -8720, + -1968 ], "id": "1d3d89fc-ab65-443e-9056-63208876b673", "name": "HTTP - Get Maximo Chat Contact" @@ -4209,8 +4196,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -7504, - -3104 + -8512, + -1968 ], "id": "5606a01e-32b2-4947-9c98-6654fe456347", "name": "Code - Build Maximo Chat Notification" @@ -4243,8 +4230,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -7296, - -3104 + -8304, + -1968 ], "id": "d4ce2dbb-fa57-478c-bc57-c67e0e98ce15", "name": "IF - Maximo Chat Space Available?" @@ -4257,8 +4244,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12000, - -2576 + -13008, + -1440 ], "id": "b035b321-5407-4e17-88d7-31b5a0868092", "name": "Code - Detect Approval Command" @@ -4291,8 +4278,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -11792, - -2576 + -12800, + -1440 ], "id": "6d150e82-0d1d-498b-8ee0-bbdfe06785d2", "name": "IF - Is Approval Command?" @@ -4325,8 +4312,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -11536, - -2736 + -12544, + -1600 ], "id": "0f227f13-e900-4d06-b1b5-23357b94bf6b", "name": "IF - Approval Command Valid?" @@ -4360,8 +4347,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -11200, - -2912 + -12208, + -1776 ], "id": "57fb0092-0cac-4c32-83fa-3355a655f8f7", "name": "HTTP - Get Pending Approval By ID", @@ -4375,8 +4362,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10992, - -2912 + -12000, + -1776 ], "id": "039fb415-91a4-48ea-ba57-c623c2083535", "name": "Code - Validate Approval Decision" @@ -4409,8 +4396,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -10784, - -2912 + -11792, + -1776 ], "id": "d811c9f8-a840-4732-8d75-11f429b9336e", "name": "IF - Approval Decision Valid?" @@ -4452,8 +4439,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -10400, - -3008 + -11408, + -1872 ], "id": "2b4e5e24-6bc0-4454-82bd-6e14d156a273", "name": "HTTP - Update Approval Decision", @@ -4467,8 +4454,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10192, - -3008 + -11200, + -1872 ], "id": "6523a2b2-b3d0-4616-9c21-8637fdf76ff8", "name": "Code - Format Approval Decision Response" @@ -4483,8 +4470,8 @@ "type": "n8n-nodes-base.googleChat", "typeVersion": 1, "position": [ - -6912, - -3200 + -7920, + -2064 ], "id": "35146802-016a-4d1d-a402-4e4511f1f7d3", "name": "Chat - Enviar notificación aprobación Máximo", @@ -4505,8 +4492,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -21600, - -2880 + -24784, + -2864 ], "id": "ef9025d5-315d-421c-b5d8-abf4ce17dfae", "name": "Code - Detect Approval Button Event" @@ -4539,8 +4526,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -21392, - -2880 + -24576, + -2864 ], "id": "0fc91f75-294a-4e67-af1d-41fbbf90b081", "name": "IF - Is Approval Button Event?" @@ -4573,8 +4560,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -21072, - -3040 + -24256, + -3024 ], "id": "df4922ce-f5fb-4f15-b241-7bfd99eaca8b", "name": "IF - Approval Button Valid?" @@ -4608,8 +4595,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -20720, - -3152 + -23376, + -3168 ], "id": "cbe220e6-26b5-462e-a1d9-b61daffbfc3f", "name": "HTTP - Get Approval By Button ID" @@ -4622,8 +4609,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -20368, - -3152 + -23024, + -3168 ], "id": "54bd1ef8-dad0-4383-876b-566c4e523f1a", "name": "Code - Validate Approval Button Decision" @@ -4656,8 +4643,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -20160, - -3184 + -22816, + -3200 ], "id": "f3317ffe-917c-4029-9684-3d0a1b2e09e9", "name": "IF - Approval Button Decision Valid?" @@ -4699,8 +4686,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -19776, - -3808 + -22432, + -3824 ], "id": "a0fea6e6-2885-4b0e-83aa-7250582b9472", "name": "HTTP - Update Approval From Button" @@ -4713,8 +4700,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -19504, - -3696 + -22160, + -3712 ], "id": "c7902820-7b11-4117-bbb4-392198bb2dc5", "name": "Code - Format Approval Button Response" @@ -4728,8 +4715,8 @@ "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ - -17344, - -6016 + -17568, + -7312 ], "id": "595e726e-ed27-4a86-aa34-abe3e364df96", "name": "Respond - Approval Button Result" @@ -4742,8 +4729,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17600, - -6224 + -17824, + -7520 ], "id": "47e6d5c4-4354-447f-ae2c-7cde06ff2008", "name": "Code - Prepare Requester Approval Notification" @@ -4776,8 +4763,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -17392, - -6224 + -17616, + -7520 ], "id": "6b72019d-6b22-4ce1-806b-6fab4ba68b0f", "name": "IF - Should Notify Requester?" @@ -4811,8 +4798,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17072, - -6352 + -17296, + -7648 ], "id": "9105ea28-b2a3-42d8-bdc6-b11ed7418e7a", "name": "HTTP - Get Requester Chat Contact", @@ -4826,8 +4813,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16864, - -6352 + -17088, + -7648 ], "id": "55eadf4c-a42b-40c6-827e-8f641f5378c5", "name": "Code - Build Requester Decision Notification" @@ -4860,8 +4847,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -16656, - -6352 + -16880, + -7648 ], "id": "73e8dd34-d8e6-4318-90f9-0d056d53e962", "name": "IF - Requester Chat Available?" @@ -4877,8 +4864,8 @@ "type": "n8n-nodes-base.googleChat", "typeVersion": 1, "position": [ - -11056, - -4656 + -11568, + -4800 ], "id": "c976153e-dd92-4eef-905e-4c96d5385c8f", "name": "Chat - Notify Requester Approval Decision", @@ -4918,8 +4905,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -19184, - -3392 + -21840, + -3408 ], "id": "2ac456fb-fb23-47ec-9bca-8152ebc4ec12", "name": "IF - Maximo Approved?" @@ -4932,8 +4919,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -18752, - -4144 + -19248, + -4640 ], "id": "f747051d-5a97-4d7a-af4e-1537e2c4bcfd", "name": "Code - Build Approved Action Execution Context" @@ -4966,8 +4953,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -18544, - -4144 + -19040, + -4640 ], "id": "72e7a2f2-8b69-42bb-9960-79e1105f56e2", "name": "IF - Approved Action Supported?" @@ -4980,8 +4967,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17504, - -3392 + -18000, + -3888 ], "id": "c4e46ef8-6615-450d-ae80-b54a2aca6474", "name": "Code - Format Approved Action Not Supported" @@ -5146,8 +5133,8 @@ "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ - -18320, - -4384 + -18816, + -4880 ], "id": "1e7fd9d2-99d2-4ecc-8850-602b18b286f2", "name": "Switch - Approved Action Route" @@ -5160,8 +5147,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17264, - -5664 + -17760, + -6160 ], "id": "b944e184-6c54-4fb8-8a41-577024fbec32", "name": "Code - Prepare Approved Compensation Preflight" @@ -5185,8 +5172,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -16912, - -5728 + -17408, + -6224 ], "id": "bba62412-ebc9-41d5-b10e-aa8dedf25ae1", "name": "HTTP - Approved Get Employee Directory", @@ -5206,8 +5193,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16704, - -5728 + -17200, + -6224 ], "id": "535885c1-77a3-4ea0-a9f7-ef8ca8dabd9a", "name": "Code - Resolve Approved Employee From Directory" @@ -5240,8 +5227,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -16496, - -5728 + -16992, + -6224 ], "id": "187eee8e-23c5-42db-a998-43a596e68ce1", "name": "IF - Approved Employee Resolved?" @@ -5265,8 +5252,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -14048, - -4144 + -13824, + -4288 ], "id": "5c1b1674-58f1-46a1-a4a0-71797f0f1e16", "name": "HTTP - Approved Get Current Compensation", @@ -5286,8 +5273,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -13840, - -4144 + -13616, + -4288 ], "id": "384ba03f-82bf-4b54-b649-4846e0ed27ca", "name": "Code - Build Approved Compensation Write Request" @@ -5320,8 +5307,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -13632, - -4144 + -13408, + -4288 ], "id": "c1793f86-dbc6-4927-abb6-62025398087c", "name": "IF - Approved Compensation Ready To Execute?" @@ -5334,8 +5321,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -13328, - -4032 + -13104, + -4176 ], "id": "72721d77-a1a3-4e93-8dd7-6768ddae99c0", "name": "Code - Format Compensation Execution Blocked" @@ -5373,8 +5360,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -13072, - -3920 + -12848, + -4064 ], "id": "5712e9ec-27dd-4ab5-aa58-6e48e65ea91a", "name": "HTTP - Mark Approval Execution Blocked", @@ -5407,8 +5394,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -13424, - -4240 + -13200, + -4384 ], "id": "1974427f-a8db-4cd7-a289-4d190158e1f7", "name": "HTTP - Execute Approved Compensation Update", @@ -5428,8 +5415,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -13216, - -4240 + -12992, + -4384 ], "id": "dd1cb3da-c560-4fc7-ad68-f8d5cd078fbd", "name": "Code - Format Compensation Execution Result" @@ -5467,8 +5454,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -13040, - -4256 + -12816, + -4400 ], "id": "fabd1fe2-2dd2-4d5d-b567-f4af69afbef1", "name": "HTTP - Mark Approval Executed", @@ -5482,8 +5469,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12592, - -4288 + -12368, + -4432 ], "id": "4ae2afb1-e4d7-4cc1-bfba-df1d4351a09c", "name": "Code - Build Requester Final Execution Notification" @@ -5516,8 +5503,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -12384, - -4288 + -12160, + -4432 ], "id": "0f7323f7-9d63-4dcb-971f-06cf54b0e513", "name": "IF - Requester Final Chat Available?" @@ -5551,8 +5538,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -12832, - -4256 + -12608, + -4400 ], "id": "3db19bb6-7ac7-420e-9dd2-46d31f900af5", "name": "HTTP - Get Requester Final Chat Contact", @@ -5566,8 +5553,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17744, - -3744 + -18240, + -4240 ], "id": "f1b3bb9b-c32f-46bf-a961-cb4386f18b11", "name": "Code - Format Approved Action Not Supported1" @@ -5605,8 +5592,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17520, - -3744 + -18016, + -4240 ], "id": "a4838fbd-ed49-4e7f-a7ef-42ff663cfff4", "name": "HTTP - Mark Approved Action Not Supported", @@ -5620,8 +5607,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -18016, - -4064 + -18512, + -4560 ], "id": "5b534071-4084-4b7c-9176-e36a2caf2b75", "name": "Code - Prepare Approved PDF Upload" @@ -5654,8 +5641,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -17792, - -4096 + -18288, + -4592 ], "id": "60b17828-2c0e-4f9c-8f10-093d9b1f704d", "name": "IF - Approved PDF Upload Ready?" @@ -5668,8 +5655,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17408, - -4000 + -17904, + -4496 ], "id": "5bc08e8d-5ad0-4bc0-b4a7-27dc3e947ca5", "name": "Code - Format PDF Upload Blocked" @@ -5707,8 +5694,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17200, - -4000 + -17696, + -4496 ], "id": "06c7229e-a917-4aa7-92b2-4bf0fdf24ca8", "name": "HTTP - Mark PDF Upload Blocked", @@ -5730,8 +5717,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17056, - -4944 + -17552, + -5440 ], "id": "980a99a3-7c3e-442f-b856-dc5d793f4af6", "name": "HTTP - Download Approved PDF From Google Chat", @@ -5759,8 +5746,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16800, - -4944 + -17296, + -5440 ], "id": "18f3510c-55a1-4ea6-bd1c-17d31ec3f4df", "name": "Code - Validate Downloaded PDF Binary" @@ -5793,8 +5780,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -16640, - -4944 + -17136, + -5440 ], "id": "d9d5f9ea-8fa4-4cf8-a93d-7afb1fa23871", "name": "IF - Downloaded PDF Ready?" @@ -5807,8 +5794,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16432, - -4848 + -16928, + -5344 ], "id": "dda5f754-63b3-45d9-be12-9e767dee108e", "name": "Code - Format PDF Download Blocked" @@ -5825,8 +5812,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -16224, - -4848 + -16720, + -5344 ], "id": "fccb68ee-e3e7-416b-a140-4a30f10d3c66", "name": "HTTP - Mark PDF Download Blocked", @@ -5840,8 +5827,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16480, - -5136 + -16976, + -5632 ], "id": "6d9a58cd-c46d-4609-a284-c16421d047ec", "name": "Code - Prepare BambooHR PDF Upload Request" @@ -5874,8 +5861,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -15712, - -4336 + -15488, + -4480 ], "id": "223e685e-1544-4e6e-a874-5862b723ec12", "name": "IF - BambooHR PDF Upload Ready?" @@ -5888,8 +5875,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -15504, - -4240 + -15280, + -4384 ], "id": "ea50aaa2-1c16-4775-9fe4-9da45f2faaa1", "name": "Code - Format BambooHR PDF Upload Precheck Blocked" @@ -5927,8 +5914,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -15296, - -4240 + -15072, + -4384 ], "id": "57e82484-beb9-4050-9f5a-3b457fe95b36", "name": "HTTP - Mark BambooHR PDF Upload Precheck Blocked", @@ -5981,8 +5968,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -15488, - -4496 + -15264, + -4640 ], "id": "8ee9bb94-9a80-417a-9036-54284f4cf578", "name": "HTTP - Upload PDF To BambooHR", @@ -6002,8 +5989,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -15280, - -4496 + -15056, + -4640 ], "id": "3e3a4d95-812b-4704-98af-58a7a0d5e45d", "name": "Code - Format PDF Upload Result" @@ -6041,8 +6028,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -15072, - -4496 + -14848, + -4640 ], "id": "e1e4f580-b6bd-45b8-9557-ec3375deb38b", "name": "HTTP - Mark PDF Upload Finished", @@ -6056,8 +6043,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -14944, - -4688 + -14720, + -4832 ], "id": "6e63b7d9-182d-4ed9-96b9-ec1e3ea287e3", "name": "Code - Build PDF Upload Metadata Log" @@ -6095,8 +6082,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -14736, - -4688 + -14512, + -4832 ], "id": "e4b522ab-a8cd-4b70-a0c2-c3dc34f61193", "name": "Supabase - Log PDF Upload Metadata", @@ -6110,8 +6097,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -14112, - -4528 + -13888, + -4672 ], "id": "948083aa-269e-4f2d-9f4d-76e4e6edfd01", "name": "Code - Build PDF Blocked Metadata Log" @@ -6149,8 +6136,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -13904, - -4528 + -13680, + -4672 ], "id": "ddfdb8cb-5f64-41a0-8ecc-099213f8c51f", "name": "Supabase - Log PDF Blocked Metadata", @@ -6179,8 +6166,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -16272, - -5136 + -16768, + -5632 ], "id": "12327d4c-bfc0-4658-96ec-1be2ccb34ad0", "name": "HTTP - List BambooHR Employee File Categories", @@ -6200,8 +6187,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16064, - -5136 + -16560, + -5632 ], "id": "1b404504-66a1-4151-a2ed-fa616ae0bf63", "name": "Code - Resolve BambooHR PDF Category ID" @@ -6214,8 +6201,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17552, - -4320 + -18048, + -4816 ], "id": "f5bebc99-90b7-411f-8ef6-94544373506f", "name": "Code - Decide Approved PDF Download Source" @@ -6305,8 +6292,8 @@ "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ - -17360, - -4448 + -17856, + -4944 ], "id": "33ee8521-e9fb-4095-9a32-11f7fbd9bc0d", "name": "Switch - Approved PDF Download Source" @@ -6336,8 +6323,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17104, - -4592 + -17600, + -5088 ], "id": "ff2a8f74-1b3f-4296-ba14-feba62b13330", "name": "HTTP - Download Approved PDF From Google Drive", @@ -6357,11 +6344,249 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17072, - -4384 + -17568, + -4880 ], "id": "42b9d5b3-55b3-438f-a158-505d3fbe9917", "name": "Code - Prepare PDF Download Source Blocker" + }, + { + "parameters": { + "content": "# 🤖 BAMBOOHR AGENT CORE\n\nAgente conversacional de RR. HH. para consultar y ejecutar operaciones en BambooHR mediante Google Chat.\n\nArquitectura:\n\n- **Google Chat:** canal de comunicación con el usuario.\n- **Gemini:** interpreta lenguaje natural y genera intención estructurada.\n- **Supabase:** permisos, confirmaciones, aprobaciones y auditoría.\n- **BambooHR API:** fuente oficial y sistema de ejecución.\n- **Google Sheets:** exportación de reportes extensos.\n- **n8n:** validación, autorización, enrutamiento y control del proceso.\n\nModos disponibles:\n\n1. Consultas de empleados.\n2. Consultas históricas y sensibles.\n3. Actualizaciones de información.\n4. Creación de empleados.\n5. Desvinculaciones.\n6. Reportes generales y guardados.\n7. Subida de documentos PDF.\n8. Aprobaciones sensibles por Máximo.\n\nToda acción sensible requiere autorización, confirmación y trazabilidad.", + "height": 912, + "width": 1632, + "color": "#537628" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -2864, + -14784 + ], + "id": "c0c5c70f-0b0f-4066-8a72-fb3e1cb6d6bf", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# 🧪 ENTRADA MANUAL DE PRUEBAS\n\nPunto de entrada utilizado para ejecutar pruebas controladas directamente desde el editor de n8n.\n\nEste bloque permite definir manualmente:\n\n- Correo del usuario.\n- Nombre del usuario.\n- Canal de origen.\n- Ambiente de ejecución.\n- Mensaje que será interpretado por el agente.\n\nSu objetivo es validar la lógica del core sin depender de Google Chat.\n\nImportante:\n\n- Esta entrada corresponde al ambiente de desarrollo.\n- Los permisos utilizados deben representar usuarios de prueba.\n- No debe confundirse con el Webhook oficial de producción.\n- Los resultados deben pasar por las mismas reglas de interpretación, autorización y auditoría que una solicitud real.", + "height": 544, + "width": 720, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -23488, + -2528 + ], + "id": "53563542-d38a-4a09-ba71-49321da5d1ae", + "name": "Sticky Note1" + }, + { + "parameters": { + "content": "# 💬 RECEPCIÓN DESDE GOOGLE CHAT\n\nRecibe los eventos enviados por Google Chat y determina qué tipo de interacción inició el usuario.\n\nEl bloque:\n\n- Detecta mensajes normales.\n- Detecta botones de aprobación.\n- Extrae usuario, correo, espacio, hilo y mensaje.\n- Identifica documentos adjuntos.\n- Normaliza el evento al formato interno del agente.\n- Genera un request_id para trazabilidad.\n- Responde inmediatamente a Google Chat para evitar timeout.\n\nEventos especiales:\n\n- Mensaje de texto.\n- Documento adjunto.\n- Botón de aprobación o rechazo.\n- Incorporación del bot a un espacio.\n- Respuesta a una confirmación pendiente.\n\nLa respuesta inmediata no representa el resultado final; solamente confirma que la solicitud comenzó a procesarse.", + "height": 816, + "width": 1680, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -25376, + -3472 + ], + "id": "debf5c52-e267-4b21-9db3-dc1d315ad90a", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "# 🔘 DECISIÓN MEDIANTE BOTÓN DE APROBACIÓN\n\nProcesa los botones de aprobar o rechazar enviados a Máximo en Google Chat.\n\nValidaciones:\n\n- El botón debe contener un approval_id válido.\n- La aprobación debe existir en Supabase.\n- La solicitud debe continuar pendiente.\n- La decisión debe corresponder a aprobar o rechazar.\n- El usuario que responde debe estar autorizado.\n- No se permiten decisiones duplicadas.\n\nCuando la decisión es válida:\n\n1. Se actualiza la aprobación en Supabase.\n2. Se registra quién tomó la decisión.\n3. Se guarda la fecha y el resultado.\n4. Se prepara una respuesta para el botón.\n5. Si Máximo aprobó, se inicia la ejecución de la acción autorizada.\n\nUn rechazo finaliza la solicitud sin modificar BambooHR.", + "height": 1392, + "width": 1808, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -23440, + -4064 + ], + "id": "21a54585-774f-48f6-88b8-14936787e432", + "name": "Sticky Note3" + }, + { + "parameters": { + "content": "# 🔔 NOTIFICACIÓN DE DECISIÓN AL SOLICITANTE\n\nInforma al solicitante cuando Máximo aprueba o rechaza una acción sensible.\n\nEste bloque:\n\n1. Recupera el resultado de la aprobación procesada.\n2. Determina si corresponde notificar al solicitante.\n3. Busca en Supabase su contacto o espacio de Google Chat.\n4. Construye un mensaje con la decisión tomada.\n5. Verifica que exista un espacio válido antes de intentar enviarlo.\n6. Devuelve inmediatamente el resultado técnico de la interacción con el botón.\n\nLa notificación puede indicar:\n\n- Solicitud aprobada.\n- Solicitud rechazada.\n- Acción pendiente de ejecución.\n- Acción que no requiere notificación adicional.\n- Imposibilidad de localizar al solicitante en Google Chat.\n\nReglas:\n\n- Solo se notifica cuando la decisión pertenece a una aprobación válida.\n- La respuesta del botón y la notificación al solicitante son procesos distintos.\n- La aprobación no significa necesariamente que BambooHR ya fue modificado.\n- El mensaje debe diferenciar entre APROBADA, RECHAZADA y EJECUTADA.\n- Si no existe contacto de Google Chat, el workflow continúa sin provocar error.\n- No se deben exponer datos sensibles adicionales en la notificación.", + "height": 976, + "width": 1520, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -18064, + -8112 + ], + "id": "da3dd64e-c645-4e2f-9b55-c71af898a9c8", + "name": "Sticky Note4" + }, + { + "parameters": { + "content": "# 🧭 EJECUCIÓN DE ACCIONES APROBADAS\n\nRecibe una solicitud previamente aprobada y determina qué operación debe ejecutarse en BambooHR.\n\nAntes de continuar, este bloque:\n\n- Reconstruye el contexto original de la aprobación.\n- Recupera la acción, el empleado y el payload autorizado.\n- Verifica que la aprobación continúe vigente.\n- Confirma que la acción tenga una ruta implementada.\n- Impide modificar los datos después de la aprobación.\n- Dirige la solicitud hacia compensación, carga de PDF u otra ruta autorizada.\n\n## Ruta de compensación\n\nCuando la acción aprobada corresponde a una modificación salarial:\n\n1. Prepara el contexto de ejecución.\n2. Consulta nuevamente el directorio de BambooHR.\n3. Resuelve al empleado objetivo.\n4. Verifica que exista una coincidencia confiable.\n5. Solamente entonces permite continuar con las validaciones y la actualización.\n\n## Ruta de documento PDF\n\nCuando la acción aprobada corresponde a cargar un documento:\n\n1. Valida empleado, archivo, nombre y categoría.\n2. Determina si el archivo proviene de Google Chat o Google Drive.\n3. Descarga el documento desde la fuente autorizada.\n4. Comprueba que exista contenido binario válido.\n5. Verifica que el documento sea un PDF.\n6. Consulta las categorías de archivos disponibles en BambooHR.\n7. Resuelve la categoría correcta antes de preparar la carga.\n\n## Controles de seguridad\n\nLa operación se bloquea cuando:\n\n- La acción no está implementada.\n- La aprobación no contiene todos los datos.\n- No puede resolverse el empleado.\n- La fuente del documento no está soportada.\n- El archivo no puede descargarse.\n- El contenido no es un PDF válido.\n- No puede resolverse la categoría de BambooHR.\n- El payload actual difiere del aprobado.\n\nToda ruta bloqueada actualiza el estado de la aprobación para dejar trazabilidad y evitar que el sistema informe una ejecución exitosa incorrectamente.", + "height": 2656, + "width": 2976, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -19344, + -6320 + ], + "id": "919811d0-612f-4bfd-85e2-f9892cb14f8a", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "# ✅ EJECUCIÓN FINAL Y TRAZABILIDAD\n\nCompleta la ejecución de las acciones aprobadas, registra el resultado y notifica al solicitante.\n\nEste bloque administra dos operaciones sensibles:\n\n- Carga de documentos PDF al expediente de un empleado.\n- Actualización aprobada de compensación.\n\n## Documento PDF\n\nAntes de cargar el archivo se valida que:\n\n- El empleado esté correctamente identificado.\n- El documento haya sido descargado.\n- Exista contenido binario válido.\n- El archivo sea un PDF.\n- La categoría de BambooHR esté resuelta.\n- Los datos coincidan con la solicitud aprobada.\n\nCuando la carga es exitosa:\n\n1. El PDF se incorpora al expediente del empleado.\n2. Se formatea la respuesta de BambooHR.\n3. La aprobación se marca como finalizada.\n4. Se construyen los metadatos de la operación.\n5. El resultado se registra en Supabase.\n\nCuando la carga queda bloqueada:\n\n- No se envía ningún archivo a BambooHR.\n- Se registra el motivo del bloqueo.\n- La aprobación queda marcada con el resultado correspondiente.\n- Los metadatos del intento también se guardan en Supabase.\n\n## Compensación\n\nAntes de actualizar la compensación:\n\n- Se consulta el valor vigente.\n- Se construye el request con el payload aprobado.\n- Se validan monto, moneda, frecuencia y fecha efectiva.\n- Se comprueba que los valores no hayan cambiado después de la aprobación.\n\nCuando la ejecución es válida:\n\n1. Se actualiza la compensación en BambooHR.\n2. Se interpreta la respuesta de la API.\n3. La aprobación se marca como EJECUTADA.\n\nCuando una validación falla:\n\n- La modificación no se realiza.\n- La aprobación se marca como BLOQUEADA.\n- Se conserva el motivo para auditoría.\n\n## Notificación final\n\nDespués de completar o bloquear la operación:\n\n1. Se busca el contacto del solicitante en Google Chat.\n2. Se construye el mensaje con el resultado real.\n3. Se verifica que exista un espacio de Chat disponible.\n4. Se envía la notificación final.\n\nEl mensaje debe diferenciar claramente entre:\n\n- Acción ejecutada correctamente.\n- Acción aprobada pero bloqueada.\n- Error devuelto por BambooHR.\n- Documento cargado.\n- Documento no cargado.\n- Compensación actualizada.\n- Compensación no modificada.\n\nUna aprobación no debe presentarse como exitosa hasta confirmar que la operación terminó correctamente en BambooHR.", + "height": 2320, + "width": 4384, + "color": "#1F7489" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -15712, + -6208 + ], + "id": "57e114ca-8869-4f3a-8609-6a881da21da0", + "name": "Sticky Note6" + }, + { + "parameters": { + "content": "# 🧠 INTERPRETACIÓN, AUTORIZACIÓN Y APROBACIÓN\n\nControla la etapa central de seguridad del BambooHR Agent antes de consultar o modificar información.\n\nEste bloque procesa:\n\n- Confirmaciones y cancelaciones del usuario.\n- Comandos escritos de aprobación o rechazo.\n- Interpretación de solicitudes con Gemini.\n- Validación determinística de la intención.\n- Permisos almacenados en Supabase.\n- Acceso a información sensible.\n- Solicitudes que requieren aprobación de Máximo.\n\n## Confirmaciones pendientes\n\nCuando el usuario responde CONFIRMAR o CANCELAR:\n\n- Se actualiza la confirmación correspondiente en Supabase.\n- Se recupera el contexto original de la solicitud.\n- Una cancelación detiene la operación.\n- Una confirmación restaura la acción para continuar con los controles de autorización.\n\nConfirmar una acción no sustituye los permisos ni una aprobación superior obligatoria.\n\n## Comandos de aprobación\n\nDetecta mensajes como:\n\n- APROBAR 123\n- RECHAZAR 123\n- APROBAR SOLICITUD 123\n\nAntes de registrar la decisión se valida:\n\n- Identificador de aprobación.\n- Estado pendiente.\n- Decisión solicitada.\n- Usuario que responde.\n- Integridad del registro.\n- Ausencia de una decisión anterior.\n\nUna aprobación inválida, vencida o ya procesada no puede ejecutarse nuevamente.\n\n## Interpretación con Gemini\n\nGemini convierte el mensaje del usuario en una intención estructurada con datos como:\n\n- Modo: consulta, acción o reporte.\n- Empleado objetivo.\n- Campos solicitados.\n- Acción y payload.\n- Nivel de riesgo.\n- Necesidad de confirmación.\n- Confianza de interpretación.\n\nDespués, los nodos de código normalizan y validan el resultado.\n\nGemini solamente interpreta; no concede permisos, no aprueba solicitudes y no ejecuta cambios en BambooHR.\n\n## Autorización\n\nSupabase proporciona los permisos oficiales del usuario.\n\nEl workflow verifica:\n\n- Identidad y correo del solicitante.\n- Rol y alcance organizacional.\n- País o unidad permitida.\n- Tipo de consulta o acción.\n- Sensibilidad de la información.\n- Nivel de riesgo.\n- Necesidad de aprobación superior.\n\nResultados posibles:\n\n1. Solicitud autorizada para continuar.\n2. Solicitud bloqueada por falta de permisos.\n3. Solicitud pendiente de aclaración.\n4. Solicitud enviada a Máximo para aprobación.\n\n## Aprobación de Máximo\n\nCuando la acción es sensible:\n\n1. Se construye el registro de aprobación.\n2. Se guarda la solicitud pendiente en Supabase.\n3. Se conserva el payload exacto que fue solicitado.\n4. Se busca el espacio de Google Chat de Máximo.\n5. Se genera una notificación con el resumen de la acción.\n6. Se envían las opciones para aprobar o rechazar.\n7. El solicitante queda informado de que la operación está pendiente.\n\nLa acción no puede ejecutarse hasta que exista una aprobación válida.\n\nReglas de seguridad:\n\n- No confiar únicamente en la interpretación de Gemini.\n- No modificar el payload después de enviarlo a aprobación.\n- No ejecutar acciones con permisos incompletos.\n- No revelar datos sensibles a usuarios fuera de alcance.\n- No presentar una aprobación como una ejecución completada.\n- Mantener en Supabase la trazabilidad de confirmaciones, decisiones y autorizaciones.", + "height": 1760, + "width": 7584, + "color": "#682727" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -15248, + -2688 + ], + "id": "dceb1721-9612-4620-bc3b-76db78c36cd4", + "name": "Sticky Note7" + }, + { + "parameters": { + "content": "# 📬 CONFIRMACIÓN PENDIENTE Y RESPUESTA FINAL\n\nCentraliza la respuesta producida por el agente y determina si debe guardar una confirmación antes de responder al usuario.\n\n## Preparación de la respuesta\n\nEl bloque recibe el resultado de cualquiera de las rutas anteriores y construye una respuesta uniforme con información como:\n\n- Estado de la solicitud.\n- Resultado de la consulta o acción.\n- Empleado relacionado.\n- Datos que requieren confirmación.\n- Mensaje de error o bloqueo.\n- Próximo paso que debe realizar el usuario.\n\n## Confirmaciones pendientes\n\nCuando una acción necesita confirmación expresa:\n\n1. Se prepara el registro con la intención, el empleado y el payload.\n2. Se invalidan las confirmaciones pendientes anteriores del mismo usuario.\n3. Se restaura el contexto después de actualizar los registros anteriores.\n4. Se inserta la nueva confirmación pendiente en Supabase.\n5. Se conserva la respuesta original para continuar hacia Google Chat.\n\nEl registro pendiente permite que una respuesta posterior como **CONFIRMAR** o **CANCELAR** recupere exactamente la acción original.\n\n## Reglas de confirmación\n\n- Solamente puede existir una confirmación activa por usuario.\n- Una nueva confirmación reemplaza las anteriores aún pendientes.\n- El payload debe conservarse sin modificaciones.\n- Guardar la confirmación no ejecuta la acción.\n- La operación se realizará únicamente después de una confirmación válida y de superar los controles de autorización.\n- Las consultas que no requieren confirmación pasan directamente a la respuesta final.\n\n## Respuesta por Google Chat\n\nAntes de enviar el mensaje:\n\n1. Se comprueba que el canal de origen sea Google Chat.\n2. Se construye el formato final del mensaje.\n3. Se valida que exista contenido apto para enviar.\n4. Se publica la respuesta en el espacio o hilo correspondiente.\n\nLa respuesta puede representar:\n\n- Consulta completada.\n- Solicitud pendiente de confirmación.\n- Solicitud pendiente de aprobación.\n- Acción ejecutada.\n- Acción cancelada.\n- Acción bloqueada.\n- Error técnico.\n\nNo se debe presentar una acción como ejecutada cuando únicamente fue guardada como confirmación pendiente.", + "height": 1104, + "width": 3712, + "color": "#5B2559" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 18560, + -4848 + ], + "id": "8fa31a95-003c-483d-bec9-54095524e9c5", + "name": "Sticky Note8" + }, + { + "parameters": { + "content": "# 👤 CREACIÓN Y VERIFICACIÓN DE EMPLEADO\n\nCrea un nuevo empleado en BambooHR después de validar la aprobación, los datos obligatorios y la ausencia de duplicados.\n\n## Validación previa\n\nAntes de crear el empleado, este bloque:\n\n1. Comprueba que la acción haya sido confirmada y aprobada.\n2. Construye el payload del perfil principal.\n3. Consulta el directorio actual de BambooHR.\n4. Busca coincidencias por correo, nombre u otros identificadores.\n5. Bloquea la creación cuando detecta un posible duplicado.\n6. Verifica que estén presentes todos los datos obligatorios.\n\nDatos principales esperados:\n\n- Nombre.\n- Apellido.\n- Correo laboral.\n- País o ubicación.\n- Departamento.\n- División.\n- Cargo.\n- Fecha de ingreso.\n\n## Creación del perfil\n\nCuando la solicitud es válida:\n\n1. Crea el perfil base en BambooHR.\n2. Recupera y normaliza el ID generado.\n3. Conserva ese ID para todas las operaciones posteriores.\n4. Prepara la información laboral inicial del empleado.\n\nNo se debe continuar cuando BambooHR no devuelve un ID de empleado válido.\n\n## Registro inicial de jobInfo\n\nDespués de crear el perfil:\n\n1. Construye la fila inicial de jobInfo.\n2. Consulta el historial existente del nuevo empleado.\n3. Verifica que la fila no haya sido agregada previamente.\n4. Valida la fecha efectiva y los campos laborales.\n5. Inserta la fila inicial cuando corresponde.\n\nLa fila de jobInfo puede incluir:\n\n- Fecha efectiva.\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor, cuando esté disponible.\n\n## Verificación final\n\nAl terminar:\n\n1. Consulta nuevamente el empleado por su ID.\n2. Consulta su tabla jobInfo.\n3. Comprueba que el perfil y la información laboral existan.\n4. Construye una respuesta final con el resultado real.\n\nLa respuesta debe indicar claramente:\n\n- Empleado creado correctamente.\n- ID generado por BambooHR.\n- Datos principales registrados.\n- Estado de la fila inicial de jobInfo.\n- Creación bloqueada por duplicado.\n- Datos incompletos.\n- Error devuelto por BambooHR.\n\nNo se debe informar éxito hasta verificar el perfil creado y su información laboral inicial.", + "height": 1440, + "width": 5392, + "color": "#7B7023" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1920, + -4048 + ], + "id": "d8a4e06c-688b-40f3-8acb-a58e73704760", + "name": "Sticky Note9" + }, + { + "parameters": { + "content": "# 🔎 CONSULTAS DE EXPEDIENTE E HISTORIALES\n\nEnruta las consultas autorizadas hacia la sección correspondiente del expediente del empleado en BambooHR.\n\n## Campos básicos\n\nConsulta la información actual del perfil del empleado y devuelve únicamente los campos solicitados, por ejemplo:\n\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor.\n- Correo laboral.\n- Teléfono y extensión.\n- Estatus.\n- Fecha de ingreso.\n- Información general.\n\nLa respuesta se transforma a un formato claro para el usuario, sin mostrar información adicional que no haya sido solicitada.\n\n## Historial jobInfo\n\nConsulta los cambios históricos relacionados con:\n\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor.\n- Fecha efectiva de cada modificación.\n\nLos registros se ordenan y presentan como una secuencia de movimientos laborales.\n\n## Historial employmentStatus\n\nConsulta los cambios de estatus laboral del empleado, incluyendo:\n\n- Estado registrado.\n- Fecha efectiva.\n- Motivo o tipo de movimiento, cuando BambooHR lo proporciona.\n- Historial de activación, inactivación o desvinculación.\n\nEsta ruta es solamente de consulta y no modifica el estatus del empleado.\n\n## Historial de compensación\n\nLa compensación se considera información altamente sensible.\n\nAntes de consultar BambooHR se valida:\n\n1. Permiso específico del solicitante.\n2. Alcance sobre el empleado.\n3. Rol autorizado.\n4. Nivel de sensibilidad permitido.\n\nCuando el acceso es aprobado:\n\n- Se consulta la tabla de compensación.\n- Se formatean fechas, montos, monedas y frecuencias.\n- Se devuelve únicamente la información autorizada.\n\nCuando el acceso es rechazado, no se consulta ni se revela información salarial.\n\n## Datos faltantes\n\nRevisa los campos relevantes del expediente y determina cuáles están vacíos o incompletos.\n\nPuede validar información como:\n\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor.\n- Correo y teléfono laboral.\n- Estatus.\n- Fecha de ingreso.\n\nLa respuesta distingue entre campos completos, faltantes y no disponibles.\n\n## Archivos del empleado\n\nConsulta los documentos registrados en el expediente de BambooHR y prepara una respuesta con:\n\n- Nombre del archivo.\n- Categoría.\n- Fecha disponible.\n- Identificador o referencia permitida.\n- Cantidad total de documentos encontrados.\n\nLa consulta de archivos también queda sujeta al alcance y permisos del usuario.\n\n## Reglas generales\n\n- El empleado debe estar resuelto antes de entrar a estas rutas.\n- Solamente se consultan los datos solicitados.\n- BambooHR es la fuente oficial de la información.\n- Una respuesta vacía no debe presentarse como un error de ejecución.\n- No se inventan campos, historiales, documentos ni valores.\n- La información sensible requiere validación adicional.\n- Ninguna de estas rutas modifica el expediente del empleado.", + "height": 2256, + "width": 3456 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 5200, + -14048 + ], + "id": "e6abb8c2-e45d-45f5-98c7-da2439653862", + "name": "Sticky Note10" + }, + { + "parameters": { + "content": "# ⚙️ EJECUCIÓN DE ACCIONES CONFIRMADAS\n\nProcesa las acciones que ya fueron confirmadas por el usuario y superaron los controles de autorización.\n\nAntes de ejecutar cualquier cambio, este bloque verifica:\n\n- Que exista una acción estructurada.\n- Que la confirmación pertenezca al usuario correcto.\n- Que el empleado haya sido resuelto.\n- Que el usuario tenga permisos sobre el empleado.\n- Que el payload conserve los datos originalmente confirmados.\n- Que la acción no haya sido cancelada ni ejecutada anteriormente.\n\nDespués de validar la aprobación, el Switch dirige la solicitud hacia la operación correspondiente.\n\n## Actualización de campos básicos\n\nEsta ruta modifica un campo actual del perfil del empleado.\n\nProceso:\n\n1. Consulta el estado actual del empleado.\n2. Construye un payload limitado al campo solicitado.\n3. Envía la actualización a BambooHR.\n4. Consulta nuevamente el perfil.\n5. Verifica el valor almacenado.\n6. Prepara la respuesta final.\n\nReglas:\n\n- No se modifican campos adicionales.\n- No se envían valores vacíos.\n- El valor nuevo debe coincidir con el confirmado.\n- No se informa éxito sin consultar nuevamente al empleado.\n- Los errores de BambooHR deben conservarse en la respuesta.\n\n## Adición de una fila jobInfo\n\nEsta ruta agrega un movimiento al historial laboral del empleado.\n\nPuede incluir:\n\n- Fecha efectiva.\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor.\n\nAntes de insertar:\n\n1. Construye el payload de jobInfo.\n2. Consulta las filas existentes.\n3. Compara el nuevo movimiento con el historial actual.\n4. Bloquea registros duplicados.\n5. Valida que estén presentes los campos obligatorios.\n\nCuando la fila está permitida:\n\n1. Se agrega a BambooHR.\n2. Se consulta nuevamente jobInfo.\n3. Se verifica que el movimiento exista.\n4. Se construye la respuesta con el resultado real.\n\n## Preparación de desvinculación\n\nCuando la solicitud corresponde a preparar una terminación:\n\n- No se modifica todavía el estatus del empleado.\n- Se presenta un resumen de la fecha, motivo y tipo de salida.\n- Se informa claramente que la desvinculación aún no ha sido ejecutada.\n- Se conserva el contexto necesario para la ruta de ejecución definitiva.\n\n## Controles generales\n\nLa acción debe bloquearse cuando:\n\n- No existe confirmación válida.\n- El empleado no fue resuelto.\n- El usuario no está autorizado.\n- El payload está incompleto.\n- Se detecta un movimiento duplicado.\n- Los datos actuales difieren de lo confirmado.\n- BambooHR no confirma el resultado.\n\nConfirmar una acción autoriza únicamente la operación y los valores mostrados al usuario; no permite introducir cambios adicionales durante la ejecución.", + "height": 1824, + "width": 4224, + "color": "#125768" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 3312, + -11264 + ], + "id": "3ee03bca-a471-40ee-a3ed-e25e158b7551", + "name": "Sticky Note11" + }, + { + "parameters": { + "content": "# 🛑 EJECUCIÓN Y AUDITORÍA DE DESVINCULACIÓN\n\nEjecuta de forma controlada la desvinculación real de un empleado mediante la tabla `employmentStatus` de BambooHR.\n\n## Preparación del movimiento\n\nEl bloque construye el payload utilizando exclusivamente los datos confirmados y autorizados:\n\n- Empleado objetivo.\n- Fecha efectiva de salida.\n- Motivo de terminación.\n- Tipo de terminación.\n- Valores requeridos por BambooHR.\n- Usuario que solicitó y confirmó la acción.\n\nAntes de continuar, consulta el historial actual de `employmentStatus` para evitar registrar una desvinculación duplicada.\n\n## Validación de duplicados\n\nLa ejecución se bloquea cuando BambooHR ya contiene un movimiento equivalente para el mismo empleado.\n\nLa comparación puede considerar:\n\n- Fecha efectiva.\n- Estatus registrado.\n- Tipo de terminación.\n- Motivo.\n- Identificador del movimiento.\n\nUna operación bloqueada por duplicado no debe enviarse nuevamente a BambooHR ni presentarse como exitosa.\n\n## Resolución de listas de BambooHR\n\nBambooHR puede requerir identificadores internos en lugar de los textos visibles de motivo, tipo o estatus.\n\nPor eso, el flujo:\n\n1. Consulta las listas de metadatos disponibles.\n2. Busca los valores solicitados.\n3. Resuelve sus IDs internos.\n4. Comprueba que todos los IDs obligatorios estén disponibles.\n\nSi algún valor no puede mapearse, la ejecución queda bloqueada antes del POST.\n\n## Puerta final de ejecución\n\nAntes de modificar BambooHR se realiza una validación final de seguridad:\n\n- Confirmación válida.\n- Usuario autorizado.\n- Empleado correctamente resuelto.\n- Alcance organizacional permitido.\n- Acción no ejecutada anteriormente.\n- Payload completo.\n- IDs de listas resueltos.\n- Datos idénticos a los confirmados.\n- Operación marcada como lista para ejecutar.\n\nTambién se genera una vista previa del payload final para verificar exactamente qué información será enviada.\n\n## Ejecución en BambooHR\n\nCuando todos los controles son correctos:\n\n1. Se agrega una nueva fila a `employmentStatus`.\n2. Se normaliza la respuesta técnica del POST.\n3. Se consulta nuevamente el historial del empleado.\n4. Se verifica que el movimiento haya sido registrado.\n5. Se prepara la respuesta definitiva.\n\nNo se informa éxito únicamente porque el POST respondió; el movimiento debe confirmarse mediante una consulta posterior.\n\n## Rutas bloqueadas\n\nLa desvinculación se detiene cuando:\n\n- Se detecta un movimiento duplicado.\n- Faltan datos obligatorios.\n- No se resuelven los IDs de las listas.\n- La confirmación no es válida.\n- El usuario no tiene autorización.\n- El empleado está fuera de alcance.\n- El payload difiere del aprobado.\n- BambooHR devuelve un error.\n- La verificación posterior no encuentra el movimiento.\n\nCada ruta bloqueada debe explicar el motivo sin indicar que el empleado fue desvinculado.\n\n## Auditoría\n\nToda ejecución o bloqueo genera un evento de auditoría en Supabase con información como:\n\n- Solicitante.\n- Empleado afectado.\n- Acción solicitada.\n- Payload aprobado.\n- Fecha y hora.\n- Resultado de autorización.\n- Resultado de ejecución.\n- Respuesta de BambooHR.\n- Motivo del bloqueo, cuando corresponda.\n\nDespués de registrar la auditoría, se restaura la respuesta original para que pueda continuar hacia la notificación final del usuario.", + "height": 1312, + "width": 7488, + "color": 2 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 5184, + -7376 + ], + "id": "cee3ff9c-1fc7-4727-bb6e-92e07122d203", + "name": "Sticky Note12" + }, + { + "parameters": { + "content": "# 🔎 RESOLUCIÓN DEL EMPLEADO Y CONTROL DE ALCANCE\n\nLocaliza de forma segura al empleado solicitado y comprueba que el usuario tenga autorización para acceder a su información.\n\n## Resolución desde el directorio\n\nEl flujo consulta el directorio de BambooHR y separa cada empleado para evaluar posibles coincidencias.\n\nLa búsqueda puede utilizar:\n\n- ID interno de BambooHR.\n- Número de empleado.\n- Correo laboral.\n- Nombre completo.\n- Nombre parcial normalizado.\n- Variaciones razonables de escritura.\n\nCuando se encuentra una coincidencia confiable:\n\n- Se conserva el ID oficial del empleado.\n- Se normalizan su nombre, correo, ubicación y datos organizacionales.\n- El empleado pasa directamente a la validación de alcance.\n\n## Fallback mediante ID directo\n\nCuando el directorio no devuelve una coincidencia, el flujo comprueba si la solicitud contiene un ID directo.\n\nSi existe:\n\n1. Consulta al empleado directamente por su ID.\n2. Normaliza la respuesta de BambooHR.\n3. Verifica que el empleado realmente exista.\n4. Envía el resultado al control de alcance.\n\nEl ID proporcionado por el usuario nunca se considera válido hasta confirmarlo en BambooHR.\n\n## Fallback mediante listado alternativo\n\nCuando no existe un ID directo, se utiliza un listado alternativo de empleados para intentar resolver la solicitud.\n\nEsta ruta:\n\n- Recupera empleados disponibles para búsqueda.\n- Normaliza nombres, correos e identificadores.\n- Compara la consulta del usuario con los registros.\n- Selecciona solamente una coincidencia suficientemente confiable.\n- Evita continuar cuando existen resultados ambiguos.\n\n## Control de alcance\n\nDespués de resolver al empleado, se valida si el solicitante puede acceder a esa persona.\n\nEl control puede considerar:\n\n- País.\n- Ubicación.\n- División o unidad de negocio.\n- Departamento.\n- Rol del solicitante.\n- Alcance organizacional asignado.\n- Tipo de consulta o acción.\n- Sensibilidad de la información.\n\nCuando el alcance es válido, el flujo continúa hacia el modo correspondiente:\n\n- Consulta.\n- Acción.\n- Reporte relacionado.\n- Operación posterior a resolución.\n\nCuando el alcance no es válido:\n\n- No se consulta información adicional.\n- No se modifica BambooHR.\n- No se revelan datos del empleado.\n- Se prepara una respuesta segura para el usuario.\n- El intento queda disponible para auditoría.\n\n## Reglas\n\n- No continuar cuando el empleado no puede resolverse de manera confiable.\n- No elegir automáticamente entre múltiples coincidencias ambiguas.\n- No confiar únicamente en el nombre escrito por el usuario.\n- Confirmar IDs y correos contra BambooHR.\n- Aplicar el control de alcance a todas las rutas de resolución.\n- Resolver al empleado no implica que el usuario esté autorizado para acceder a él.", + "height": 1920, + "width": 5504, + "color": "#542970" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -3920, + -10080 + ], + "id": "2f290492-0a1d-41af-8da0-8f3da8b1bf89", + "name": "Sticky Note13" + }, + { + "parameters": { + "content": "# 📊 REPORTES GENERALES Y GUARDADOS\n\nEnruta las solicitudes de reportes hacia la fuente y el formato de salida correspondientes.\n\nEste bloque permite:\n\n- Consultar empleados activos e inactivos.\n- Generar reportes de headcount.\n- Listar los Custom Reports disponibles en BambooHR.\n- Ejecutar un reporte guardado por ID.\n- Buscar y ejecutar un reporte por nombre.\n- Mostrar resultados directamente en Google Chat.\n- Exportar reportes extensos a Google Sheets.\n\n## Activos e inactivos\n\nEjecuta el reporte configurado para obtener el estado laboral de los empleados.\n\nLa respuesta puede incluir:\n\n- Empleados activos.\n- Empleados inactivos.\n- Totales por estatus.\n- Resumen general autorizado.\n\nLos resultados se normalizan antes de enviarse al usuario.\n\n## Reportes de headcount\n\nConsulta el directorio de BambooHR y agrupa empleados según el tipo de reporte solicitado.\n\nPuede generar resultados como:\n\n- Headcount total.\n- Headcount por país.\n- Headcount por departamento.\n- Headcount por ubicación.\n- Headcount por país y departamento.\n\nEl conteo debe respetar el alcance organizacional del solicitante.\n\n## Listado de reportes guardados\n\nConsulta los Custom Reports configurados en BambooHR y devuelve información como:\n\n- ID del reporte.\n- Nombre.\n- Descripción disponible.\n- Referencia necesaria para ejecutarlo.\n\nEl agente no debe inventar IDs ni nombres de reportes.\n\n## Resolución del reporte guardado\n\nEl usuario puede solicitar un reporte mediante:\n\n- ID directo.\n- Nombre completo.\n- Parte suficientemente clara del nombre.\n\nCuando se proporciona un ID, el flujo intenta ejecutar directamente ese reporte.\n\nCuando se proporciona un nombre:\n\n1. Consulta el catálogo de reportes.\n2. Normaliza los nombres.\n3. Busca una coincidencia confiable.\n4. Recupera el ID real.\n5. Ejecuta el reporte resuelto.\n\nLa solicitud se bloquea cuando:\n\n- No existe coincidencia.\n- Existen varias coincidencias ambiguas.\n- El ID no es válido.\n- El reporte no está disponible.\n- El usuario no tiene permiso.\n\n## Resultado directo\n\nCuando el reporte puede mostrarse dentro del chat:\n\n- Se formatean encabezados y filas.\n- Se aplica paginación cuando corresponde.\n- Se indica la cantidad de resultados.\n- Se evita exceder el tamaño permitido del mensaje.\n\nUna respuesta vacía debe indicarse como reporte sin registros, no necesariamente como error.\n\n## Exportación a Google Sheets\n\nCuando el usuario solicita exportar o el reporte contiene demasiados registros:\n\n1. Ejecuta el reporte inicial.\n2. Determina la cantidad de páginas.\n3. Construye las solicitudes para recuperar todas las páginas.\n4. Combina los resultados sin duplicarlos.\n5. Mantiene el orden original de las columnas.\n6. Crea un nuevo Google Sheet.\n7. Escribe encabezados y filas.\n8. Aplica formato visual a la hoja.\n9. Devuelve el enlace final al usuario.\n\n## Controles de exportación\n\n- No perder registros durante la paginación.\n- No repetir páginas.\n- Mantener encabezados consistentes.\n- Verificar que el Sheet haya sido creado.\n- Validar que las filas hayan sido escritas.\n- Informar el número total de registros exportados.\n- No presentar un enlace hasta confirmar que el archivo existe.\n\nBambooHR continúa siendo la fuente oficial; Google Sheets funciona únicamente como medio de entrega del reporte.", + "height": 2000, + "width": 7184 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -7104, + -6720 + ], + "id": "cbd8f308-99e2-443b-b457-9ff308f56f72", + "name": "Sticky Note14" } ], "connections": { @@ -8127,11 +8352,6 @@ ] ] }, - "Code - Format Saved Report Export Preview": { - "main": [ - [] - ] - }, "Code - Prepare Saved Report Google Sheet": { "main": [ [ @@ -9248,15 +9468,15 @@ "settings": { "executionOrder": "v1", "binaryMode": "separate", - "availableInMCP": false + "availableInMCP": true }, "staticData": null, "meta": { "templateCredsSetupCompleted": true }, - "versionId": "9a7c1b7e-df7b-4633-b952-6437ecb22ec6", - "activeVersionId": "9a7c1b7e-df7b-4633-b952-6437ecb22ec6", - "versionCounter": 2882, + "versionId": "02437699-5dec-4508-8f05-306ce95217a0", + "activeVersionId": "02437699-5dec-4508-8f05-306ce95217a0", + "versionCounter": 3046, "triggerCount": 1, "shared": [ { @@ -9279,9 +9499,9 @@ ], "tags": [], "activeVersion": { - "updatedAt": "2026-07-06T19:01:01.000Z", - "createdAt": "2026-07-06T19:00:02.537Z", - "versionId": "9a7c1b7e-df7b-4633-b952-6437ecb22ec6", + "updatedAt": "2026-07-16T02:38:36.000Z", + "createdAt": "2026-07-15T16:41:21.099Z", + "versionId": "02437699-5dec-4508-8f05-306ce95217a0", "workflowId": "5VOiadzdgbs2Qq1N", "nodes": [ { @@ -9303,8 +9523,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -4128, - -7072 + -3248, + -8896 ], "id": "276ea916-3fab-46f5-a708-38ce0cce5bbe", "name": "HTTP - Employees Directory", @@ -9321,8 +9541,8 @@ "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [ - -20688, - -2400 + -23328, + -2144 ], "id": "5c339d4f-7a43-497c-813c-10a0fe9edc8a", "name": "BambooHR Agent Core" @@ -9336,8 +9556,8 @@ "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ - -20480, - -2400 + -23120, + -2144 ], "id": "ca88cf95-285f-4389-84c9-458198f82f94", "name": "Set - Incoming Message" @@ -9350,8 +9570,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10320, - -2304 + -11328, + -1168 ], "id": "16424bed-7d3c-4d1d-8da7-8b8c1437a9f6", "name": "Code - Intent Parser" @@ -9364,8 +9584,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -9808, - -2288 + -10816, + -1152 ], "id": "3f7772d0-ca2c-446f-a790-84881562ab84", "name": "Code - Validate Intent" @@ -9469,8 +9689,8 @@ "type": "n8n-nodes-base.splitOut", "typeVersion": 1, "position": [ - -3888, - -7072 + -3008, + -8896 ], "id": "88fcede6-4eea-4b65-b320-1a86d1da10ed", "name": "Split Out - Employees" @@ -9482,8 +9702,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -3680, - -7072 + -2800, + -8896 ], "id": "8fb4ee82-f72d-46f3-b872-2b4bbb51849b", "name": "Code - Resolve Employee" @@ -9575,8 +9795,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -9600, - -2288 + -10608, + -1152 ], "id": "8dfb0930-863b-4b86-905c-c457933efe42", "name": "IF - Can Continue?" @@ -9609,8 +9829,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -3120, - -7072 + -2240, + -8896 ], "id": "b6726c6b-7238-4cd1-92e8-a08306b2333f", "name": "IF - Employee Found?" @@ -9623,8 +9843,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12240, - -2592 + -13248, + -1456 ], "id": "082b337a-96ee-4cc6-960f-06351672df90", "name": "Code - Request Context" @@ -10166,8 +10386,8 @@ "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ - 1408, - -8704 + 1280, + -9920 ], "id": "e79dd7ae-db69-489d-8d77-6a96c9ad7aaa", "name": "Switch - Post Resolve Mode" @@ -10180,8 +10400,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 3840, - -8416 + 4592, + -9920 ], "id": "f769b2b5-7055-4052-9b39-f1b3b85e8b4e", "name": "Code - Action Confirmation Guard" @@ -10214,8 +10434,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 4144, - -8416 + 4896, + -9920 ], "id": "64ab7961-a9fb-435d-b109-c82bb40e2a3a", "name": "IF - Action Approved?" @@ -10459,8 +10679,8 @@ "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ - 4416, - -9040 + 5168, + -10544 ], "id": "6236bf70-4f09-49b9-b11c-6d8f4d1cb88a", "name": "Switch - Action Type" @@ -10714,8 +10934,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -4464, - -6112 + -4448, + -6656 ], "id": "0f430b61-50b6-4d78-9e43-a910091ccaea", "name": "Code - Create Employee Confirmation Guard" @@ -10748,7 +10968,7 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -1504, + -1248, -2800 ], "id": "13549ab1-68a8-4630-b6d9-e08fec43950b", @@ -11114,8 +11334,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 6224, - -8544 + 6496, + -9648 ], "id": "b37a3f61-7d9e-42a2-beb2-760e0ffba872", "name": "Code - Format Prepare Termination Response" @@ -11128,8 +11348,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -1920, - -6352 + -1824, + -8896 ], "id": "126b4979-c77e-4640-8b19-b5c9b2ff323c", "name": "Code - Check Direct Employee ID Fallback" @@ -11162,8 +11382,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -1152, - -6176 + -1056, + -8720 ], "id": "65ffcff8-b8b0-42be-b9b8-965773255145", "name": "IF - Has Direct Employee ID?" @@ -11187,8 +11407,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - 64, - -7152 + 0, + -9632 ], "id": "bb411b52-5759-4802-a6e7-c198f2f4ad2a", "name": "HTTP - Get Employee By Direct ID", @@ -11207,8 +11427,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 384, - -7152 + 320, + -9632 ], "id": "1e200e58-2b3b-4e63-8615-35cc2fad5871", "name": "Code - Normalize Direct Employee Resolve" @@ -11666,8 +11886,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -9216, - -2384 + -10224, + -1248 ], "id": "46fc792b-dd60-4083-a4df-9dfbdf90c3ab", "name": "Supabase - Get User Permissions", @@ -11680,8 +11900,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -8912, - -2624 + -9920, + -1488 ], "id": "5ca00b0d-75b2-4ed4-b8a2-e9c609b8c112", "name": "Code - Authorization Gate" @@ -11714,8 +11934,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -6832, - -2768 + -7840, + -1632 ], "id": "c3ca5e91-a459-44b6-ad32-188f9c829e5c", "name": "IF - User Authorized?" @@ -11728,8 +11948,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 848, - -8560 + 720, + -9776 ], "id": "58501789-c3aa-4161-b738-6b7d66be80a3", "name": "Code - Employee Scope Gate" @@ -11762,8 +11982,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 1056, - -8560 + 928, + -9776 ], "id": "f4885736-891b-4bc4-bee7-8e4328861e8a", "name": "IF - Employee Scope Allowed?" @@ -11823,8 +12043,8 @@ "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ - -22128, - -2880 + -25312, + -2864 ], "id": "f689f4e1-63a4-436c-a15f-9859a9b8a0fd", "name": "Webhook - Google Chat Incoming", @@ -11839,8 +12059,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -20912, - -2880 + -24096, + -2864 ], "id": "ca5fb655-16b5-4211-a983-ced7e5adfec5", "name": "Code - Normalize Google Chat Input" @@ -11960,8 +12180,8 @@ "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ - -20480, - -2880 + -23856, + -2864 ], "id": "a8fb64d3-ace3-4f5f-8f00-03f084efb70a", "name": "Respond - Google Chat Processing" @@ -11974,8 +12194,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -20272, - -2880 + -22928, + -2896 ], "id": "5fb42bef-9c9b-4b09-8f26-a93eb7b9a060", "name": "Code - Detect Confirmation Reply" @@ -12008,8 +12228,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -20064, - -2880 + -22720, + -2896 ], "id": "181f33b5-375d-482f-8119-0ef81d66aab2", "name": "IF - Is Confirmation Reply?" @@ -12043,8 +12263,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -19712, - -2992 + -22368, + -3008 ], "id": "9bad8293-fbd6-4669-ae66-e3712ac3c710", "name": "Supabase - Get Pending Confirmation", @@ -12057,8 +12277,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -19504, - -2992 + -22160, + -3008 ], "id": "38c4d2b8-9b0a-4982-972b-80d8e45fada0", "name": "Code - Resolve Confirmation Reply" @@ -12090,8 +12310,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -13584, - -3504 + -14592, + -2368 ], "id": "2b77cc64-771f-4758-b414-73e036f188be", "name": "IF - Confirmation Cancelled?" @@ -12129,8 +12349,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -12512, - -3456 + -13520, + -2320 ], "id": "8bf1e0b7-767c-464e-b3cd-57cdac1136aa", "name": "Supabase - Mark Confirmation Cancelled" @@ -12244,8 +12464,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12112, - -3456 + -13120, + -2320 ], "id": "919396ee-523d-4db9-8a35-840ee4c7992c", "name": "Code - Restore Cancelled Confirmation Response" @@ -12278,8 +12498,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -12912, - -2896 + -13920, + -1760 ], "id": "85e01b65-8296-486e-bbca-dc856e77dba8", "name": "IF - Confirmation Confirmed?" @@ -12317,8 +12537,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -12704, - -3056 + -13712, + -1920 ], "id": "6406f5d1-5c6f-4d40-961a-1903269d777b", "name": "Supabase - Mark Confirmation Confirmed" @@ -12331,8 +12551,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12496, - -3056 + -13504, + -1920 ], "id": "1d69b228-521c-45a6-bf25-86ae602881a2", "name": "Code - Restore Confirmed Confirmation Response" @@ -12398,8 +12618,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -11312, - -2592 + -12320, + -1456 ], "id": "b354a006-560d-4784-a882-d4a8ffa9fc38", "name": "Code - Build Gemini Intent Prompt" @@ -12414,8 +12634,8 @@ "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini", "typeVersion": 1.1, "position": [ - -11232, - -2384 + -12240, + -1248 ], "id": "5314a9c0-b3fc-4e41-9a69-d1b396e084af", "name": "Google Gemini Chat Model", @@ -12438,8 +12658,8 @@ "type": "@n8n/n8n-nodes-langchain.agent", "typeVersion": 3.1, "position": [ - -11088, - -2592 + -12096, + -1456 ], "id": "3bffe8fe-5617-4805-b10a-f53e50f2af1b", "name": "AI Agent - Gemini Intent Interpreter" @@ -12452,8 +12672,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10736, - -2592 + -11744, + -1456 ], "id": "bab585e3-0cca-40b6-96ef-5e031a6f9759", "name": "Code - Normalize Gemini Intent" @@ -12494,8 +12714,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -688, - -6160 + -592, + -8704 ], "id": "9008719a-7dd9-4791-a6a2-567580c6b0af", "name": "HTTP - List Employees For Resolver", @@ -12514,8 +12734,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 112, - -6192 + 208, + -8736 ], "id": "5d695bb8-10c4-4944-9739-f9c78cf37bd2", "name": "Code - Match Employee From List Employees" @@ -13074,19 +13294,6 @@ } } }, - { - "parameters": { - "jsCode": "const requestContext = $items('Code - Build Saved Report Export Request')[0].json;\n\nfunction isEmptyObject(value) {\n return (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0\n );\n}\n\nfunction normalizeReportBody(items) {\n if (!items || items.length === 0) {\n return {\n rows: [],\n pagination: null\n };\n }\n\n const body = items[0].json;\n\n if (!body || isEmptyObject(body)) {\n return {\n rows: [],\n pagination: null\n };\n }\n\n if (Array.isArray(body)) {\n return {\n rows: body,\n pagination: null\n };\n }\n\n return {\n rows: Array.isArray(body.data)\n ? body.data\n : Array.isArray(body.employees)\n ? body.employees\n : Array.isArray(body.rows)\n ? body.rows\n : [],\n pagination: body.pagination || null\n };\n}\n\nfunction collectColumns(rows) {\n const columns = [];\n const seen = new Set();\n\n for (const row of rows) {\n for (const key of Object.keys(row || {})) {\n if (!seen.has(key)) {\n seen.add(key);\n columns.push(key);\n }\n }\n }\n\n return columns;\n}\n\nconst normalized = normalizeReportBody(items);\nconst rows = normalized.rows;\nconst pagination = normalized.pagination || {};\n\nconst totalRows =\n pagination.total_records ||\n pagination.totalRecords ||\n pagination.total ||\n pagination.count ||\n rows.length;\n\nconst pageSize =\n pagination.page_size ||\n pagination.pageSize ||\n requestContext.saved_report_page_size ||\n rows.length;\n\nconst totalPages =\n totalRows > 0 && pageSize > 0\n ? Math.max(1, Math.ceil(totalRows / pageSize))\n : 1;\n\nconst columns = collectColumns(rows);\n\nconst isCompleteInOneRequest =\n rows.length >= totalRows || totalPages <= 1;\n\nconst responseText =\n `Exportación preparada para reporte guardado ID ${requestContext.saved_report_id}\\n\\n` +\n `Reporte: ${requestContext.saved_report_name || 'Nombre no disponible'}\\n` +\n `Filas recibidas en esta primera consulta: ${rows.length}\\n` +\n `Total reportado por BambooHR: ${totalRows}\\n` +\n `Columnas detectadas: ${columns.length}\\n` +\n `Páginas estimadas: ${totalPages}\\n\\n` +\n (\n isCompleteInOneRequest\n ? `Estado: este reporte cabe completo en una sola consulta. Próximo paso: generar Google Sheet.`\n : `Estado: este reporte tiene varias páginas. Próximo paso: crear paginación completa antes de generar Google Sheet.`\n );\n\nreturn [\n {\n json: {\n ...requestContext,\n\n status: 'saved_report_export_preview_ready',\n can_continue: true,\n response_type: 'chat_message',\n\n saved_report_export_rows_first_page: rows,\n saved_report_export_columns: columns,\n saved_report_export_rows_received: rows.length,\n saved_report_export_total_rows: totalRows,\n saved_report_export_page_size: pageSize,\n saved_report_export_total_pages: totalPages,\n saved_report_export_complete_in_one_request: isCompleteInOneRequest,\n\n response_text: responseText,\n\n audit_event: {\n ...(requestContext.audit_event || {}),\n\n execution_result: 'saved_report_export_preview_ready',\n\n saved_report_export_rows_received: rows.length,\n saved_report_export_total_rows: totalRows,\n saved_report_export_columns_count: columns.length,\n saved_report_export_total_pages: totalPages,\n saved_report_export_complete_in_one_request: isCompleteInOneRequest,\n\n bamboohr_endpoint: `/api/v1/custom-reports/${requestContext.saved_report_id}`\n }\n }\n }\n];" - }, - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - -2096, - -6112 - ], - "id": "22d28c82-863a-48c0-b9be-d09d23531248", - "name": "Code - Format Saved Report Export Preview" - }, { "parameters": { "jsCode": "const requestContext = $json;\n\nfunction cleanText(value) {\n return String(value || '').trim();\n}\n\nfunction isEmptyObject(value) {\n return (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0\n );\n}\n\nfunction normalizeRowsFromCurrentInput(data) {\n if (!data || isEmptyObject(data)) {\n return [];\n }\n\n if (Array.isArray(data.saved_report_export_rows)) {\n return data.saved_report_export_rows;\n }\n\n if (Array.isArray(data.data)) {\n return data.data;\n }\n\n if (Array.isArray(data.employees)) {\n return data.employees;\n }\n\n if (Array.isArray(data.rows)) {\n return data.rows;\n }\n\n return [];\n}\n\nfunction collectColumns(rows) {\n const columns = [];\n const seen = new Set();\n\n for (const row of rows) {\n for (const key of Object.keys(row || {})) {\n if (!seen.has(key)) {\n seen.add(key);\n columns.push(key);\n }\n }\n }\n\n return columns;\n}\n\nfunction getFriendlyColumnName(key) {\n const map = {\n id: 'ID',\n employeeId: 'ID empleado',\n employeeNumber: 'Número empleado',\n\n displayName: 'Nombre',\n lastNameFirstName: 'Nombre',\n firstName: 'Nombre',\n middleName: 'Segundo nombre',\n lastName: 'Apellido',\n preferredName: 'Nombre preferido',\n\n hireDate: 'Fecha de ingreso',\n originalHireDate: 'Fecha de ingreso original',\n terminationDate: 'Fecha de salida',\n\n country: 'País',\n location: 'Ubicación',\n\n department: 'Departamento',\n division: 'División',\n jobTitle: 'Cargo',\n\n jobInformationJobTitle: 'Cargo',\n jobInformationDepartment: 'Departamento',\n jobInformationDivision: 'División',\n jobInformationLocation: 'Ubicación',\n jobInformationReportsTo: 'Supervisor',\n\n supervisor: 'Supervisor',\n supervisorEmail: 'Correo supervisor',\n\n workEmail: 'Correo laboral',\n email: 'Correo',\n workPhone: 'Teléfono laboral',\n workPhoneExtension: 'Extensión laboral',\n\n status: 'Estatus',\n employmentStatus: 'Estatus laboral',\n employmentStatusDate: 'Fecha estatus laboral'\n };\n\n if (map[key]) {\n return map[key];\n }\n\n const customFieldMatch = String(key || '').match(/^customField(\\d+)$/i);\n\n if (customFieldMatch) {\n return `Campo personalizado ${customFieldMatch[1]}`;\n }\n\n return String(key || '')\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/_/g, ' ')\n .trim();\n}\n\nfunction buildSpreadsheetTitle(reportId, reportName) {\n const now = new Date();\n\n const timestamp = now\n .toISOString()\n .replace('T', ' ')\n .slice(0, 16)\n .replace(/:/g, '-');\n\n const safeName = cleanText(reportName || `Reporte ${reportId || 'sin-id'}`)\n .replace(/[\\\\/:*?\"<>|#%{}[\\]~]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 80);\n\n return `BambooHR - ${safeName} - ${timestamp}`;\n}\n\nconst reportId =\n requestContext.saved_report_id ||\n requestContext.report_id ||\n requestContext.filters?.report_id ||\n requestContext.data?.filters?.report_id ||\n 'sin-id';\n\nconst reportName =\n requestContext.saved_report_name ||\n requestContext.filters?.report_name ||\n requestContext.data?.filters?.report_name ||\n requestContext.saved_report_name_query ||\n `Reporte ${reportId}`;\n\nconst rows = normalizeRowsFromCurrentInput(requestContext);\n\nconst columns =\n Array.isArray(requestContext.saved_report_export_columns) &&\n requestContext.saved_report_export_columns.length > 0\n ? requestContext.saved_report_export_columns\n : collectColumns(rows);\n\nconst columnLabels = columns.map(getFriendlyColumnName);\n\nconst totalRows =\n Number(\n requestContext.saved_report_total_records ||\n requestContext.saved_report_export_total_records_expected ||\n rows.length\n ) || rows.length;\n\nconst pageSize =\n Number(\n requestContext.saved_report_page_size ||\n requestContext.saved_report_export_page_size ||\n rows.length\n ) || rows.length;\n\nconst estimatedPages =\n Number(\n requestContext.saved_report_total_pages ||\n requestContext.saved_report_estimated_pages ||\n requestContext.saved_report_export_total_pages_expected ||\n (\n totalRows > 0 && pageSize > 0\n ? Math.max(1, Math.ceil(totalRows / pageSize))\n : 1\n )\n ) || 1;\n\nconst spreadsheetTitle = buildSpreadsheetTitle(reportId, reportName);\n\nif (!rows.length) {\n return [\n {\n json: {\n ...requestContext,\n\n status: 'saved_report_google_sheet_blocked_no_rows',\n can_continue: false,\n response_type: 'error_message',\n\n saved_report_id: String(reportId),\n report_id: String(reportId),\n saved_report_name: reportName,\n\n saved_report_export_rows: [],\n saved_report_export_columns: columns,\n saved_report_export_column_labels: columnLabels,\n\n response_text:\n `No puedo crear el Google Sheet porque el reporte no devolvió filas.\\n\\n` +\n `Reporte: ${reportName}\\n` +\n `ID BambooHR: ${reportId}`,\n\n audit_event: {\n ...(requestContext.audit_event || {}),\n\n execution_result: 'saved_report_google_sheet_blocked_no_rows',\n\n saved_report_id: String(reportId),\n saved_report_name: reportName,\n saved_report_rows_received: 0,\n saved_report_total_records: totalRows,\n saved_report_page_size: pageSize,\n saved_report_estimated_pages: estimatedPages\n }\n }\n }\n ];\n}\n\nreturn [\n {\n json: {\n ...requestContext,\n\n status: 'saved_report_google_sheet_prepared',\n can_continue: true,\n response_type: 'chat_message',\n\n saved_report_id: String(reportId),\n report_id: String(reportId),\n saved_report_name: reportName,\n\n saved_report_export_rows: rows,\n saved_report_export_columns: columns,\n saved_report_export_column_labels: columnLabels,\n\n saved_report_rows_received: rows.length,\n saved_report_total_records: totalRows,\n saved_report_page_size: pageSize,\n saved_report_estimated_pages: estimatedPages,\n\n google_sheet_title: spreadsheetTitle,\n google_sheet_first_sheet_name: 'Reporte',\n\n audit_event: {\n ...(requestContext.audit_event || {}),\n\n execution_result: 'saved_report_google_sheet_prepared',\n\n saved_report_id: String(reportId),\n saved_report_name: reportName,\n\n saved_report_rows_received: rows.length,\n saved_report_total_records: totalRows,\n saved_report_page_size: pageSize,\n saved_report_estimated_pages: estimatedPages,\n\n google_sheet_title: spreadsheetTitle\n }\n }\n }\n];" @@ -13321,8 +13528,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10112, - -2304 + -11120, + -1168 ], "id": "71cc5ff5-97ae-462a-9c2e-4d7052b39b4e", "name": "Code - Apply Gemini Interpretation" @@ -13335,8 +13542,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -8704, - -2624 + -9712, + -1488 ], "id": "f76ccb84-0963-4fa3-9f3b-1b1966ef1fd5", "name": "Code - Sensitive Approval Gate" @@ -13369,8 +13576,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -8496, - -2624 + -9504, + -1488 ], "id": "6649f03d-f59e-478a-afce-ab713886959d", "name": "IF - Needs Maximo Approval?" @@ -13383,8 +13590,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -8240, - -2816 + -9248, + -1680 ], "id": "32ecee10-ec1a-447b-af5f-28f2b3ffbd08", "name": "Code - Build Maximo Approval Record" @@ -13422,8 +13629,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -8032, - -2816 + -9040, + -1680 ], "id": "5e9e8c59-fea4-4ab4-9265-99250cdbc7ce", "name": "HTTP - Insert Pending Maximo Approval", @@ -13437,8 +13644,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -7536, - -2816 + -8544, + -1680 ], "id": "9e478fe8-1785-4b92-9329-34fbe14784d5", "name": "Code - Format Maximo Approval Pending Response" @@ -13472,8 +13679,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -7712, - -3104 + -8720, + -1968 ], "id": "1d3d89fc-ab65-443e-9056-63208876b673", "name": "HTTP - Get Maximo Chat Contact" @@ -13486,8 +13693,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -7504, - -3104 + -8512, + -1968 ], "id": "5606a01e-32b2-4947-9c98-6654fe456347", "name": "Code - Build Maximo Chat Notification" @@ -13520,8 +13727,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -7296, - -3104 + -8304, + -1968 ], "id": "d4ce2dbb-fa57-478c-bc57-c67e0e98ce15", "name": "IF - Maximo Chat Space Available?" @@ -13534,8 +13741,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12000, - -2576 + -13008, + -1440 ], "id": "b035b321-5407-4e17-88d7-31b5a0868092", "name": "Code - Detect Approval Command" @@ -13568,8 +13775,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -11792, - -2576 + -12800, + -1440 ], "id": "6d150e82-0d1d-498b-8ee0-bbdfe06785d2", "name": "IF - Is Approval Command?" @@ -13602,8 +13809,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -11536, - -2736 + -12544, + -1600 ], "id": "0f227f13-e900-4d06-b1b5-23357b94bf6b", "name": "IF - Approval Command Valid?" @@ -13637,8 +13844,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -11200, - -2912 + -12208, + -1776 ], "id": "57fb0092-0cac-4c32-83fa-3355a655f8f7", "name": "HTTP - Get Pending Approval By ID", @@ -13652,8 +13859,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10992, - -2912 + -12000, + -1776 ], "id": "039fb415-91a4-48ea-ba57-c623c2083535", "name": "Code - Validate Approval Decision" @@ -13686,8 +13893,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -10784, - -2912 + -11792, + -1776 ], "id": "d811c9f8-a840-4732-8d75-11f429b9336e", "name": "IF - Approval Decision Valid?" @@ -13729,8 +13936,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -10400, - -3008 + -11408, + -1872 ], "id": "2b4e5e24-6bc0-4454-82bd-6e14d156a273", "name": "HTTP - Update Approval Decision", @@ -13744,8 +13951,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -10192, - -3008 + -11200, + -1872 ], "id": "6523a2b2-b3d0-4616-9c21-8637fdf76ff8", "name": "Code - Format Approval Decision Response" @@ -13760,8 +13967,8 @@ "type": "n8n-nodes-base.googleChat", "typeVersion": 1, "position": [ - -6912, - -3200 + -7920, + -2064 ], "id": "35146802-016a-4d1d-a402-4e4511f1f7d3", "name": "Chat - Enviar notificación aprobación Máximo", @@ -13782,8 +13989,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -21600, - -2880 + -24784, + -2864 ], "id": "ef9025d5-315d-421c-b5d8-abf4ce17dfae", "name": "Code - Detect Approval Button Event" @@ -13816,8 +14023,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -21392, - -2880 + -24576, + -2864 ], "id": "0fc91f75-294a-4e67-af1d-41fbbf90b081", "name": "IF - Is Approval Button Event?" @@ -13850,8 +14057,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -21072, - -3040 + -24256, + -3024 ], "id": "df4922ce-f5fb-4f15-b241-7bfd99eaca8b", "name": "IF - Approval Button Valid?" @@ -13885,8 +14092,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -20720, - -3152 + -23376, + -3168 ], "id": "cbe220e6-26b5-462e-a1d9-b61daffbfc3f", "name": "HTTP - Get Approval By Button ID" @@ -13899,8 +14106,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -20368, - -3152 + -23024, + -3168 ], "id": "54bd1ef8-dad0-4383-876b-566c4e523f1a", "name": "Code - Validate Approval Button Decision" @@ -13933,8 +14140,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -20160, - -3184 + -22816, + -3200 ], "id": "f3317ffe-917c-4029-9684-3d0a1b2e09e9", "name": "IF - Approval Button Decision Valid?" @@ -13976,8 +14183,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -19776, - -3808 + -22432, + -3824 ], "id": "a0fea6e6-2885-4b0e-83aa-7250582b9472", "name": "HTTP - Update Approval From Button" @@ -13990,8 +14197,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -19504, - -3696 + -22160, + -3712 ], "id": "c7902820-7b11-4117-bbb4-392198bb2dc5", "name": "Code - Format Approval Button Response" @@ -14005,8 +14212,8 @@ "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ - -17344, - -6016 + -17568, + -7312 ], "id": "595e726e-ed27-4a86-aa34-abe3e364df96", "name": "Respond - Approval Button Result" @@ -14019,8 +14226,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17600, - -6224 + -17824, + -7520 ], "id": "47e6d5c4-4354-447f-ae2c-7cde06ff2008", "name": "Code - Prepare Requester Approval Notification" @@ -14053,8 +14260,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -17392, - -6224 + -17616, + -7520 ], "id": "6b72019d-6b22-4ce1-806b-6fab4ba68b0f", "name": "IF - Should Notify Requester?" @@ -14088,8 +14295,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17072, - -6352 + -17296, + -7648 ], "id": "9105ea28-b2a3-42d8-bdc6-b11ed7418e7a", "name": "HTTP - Get Requester Chat Contact", @@ -14103,8 +14310,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16864, - -6352 + -17088, + -7648 ], "id": "55eadf4c-a42b-40c6-827e-8f641f5378c5", "name": "Code - Build Requester Decision Notification" @@ -14137,8 +14344,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -16656, - -6352 + -16880, + -7648 ], "id": "73e8dd34-d8e6-4318-90f9-0d056d53e962", "name": "IF - Requester Chat Available?" @@ -14154,8 +14361,8 @@ "type": "n8n-nodes-base.googleChat", "typeVersion": 1, "position": [ - -11056, - -4656 + -11568, + -4800 ], "id": "c976153e-dd92-4eef-905e-4c96d5385c8f", "name": "Chat - Notify Requester Approval Decision", @@ -14195,8 +14402,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -19184, - -3392 + -21840, + -3408 ], "id": "2ac456fb-fb23-47ec-9bca-8152ebc4ec12", "name": "IF - Maximo Approved?" @@ -14209,8 +14416,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -18752, - -4144 + -19248, + -4640 ], "id": "f747051d-5a97-4d7a-af4e-1537e2c4bcfd", "name": "Code - Build Approved Action Execution Context" @@ -14243,8 +14450,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -18544, - -4144 + -19040, + -4640 ], "id": "72e7a2f2-8b69-42bb-9960-79e1105f56e2", "name": "IF - Approved Action Supported?" @@ -14257,8 +14464,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17504, - -3392 + -18000, + -3888 ], "id": "c4e46ef8-6615-450d-ae80-b54a2aca6474", "name": "Code - Format Approved Action Not Supported" @@ -14423,8 +14630,8 @@ "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ - -18320, - -4384 + -18816, + -4880 ], "id": "1e7fd9d2-99d2-4ecc-8850-602b18b286f2", "name": "Switch - Approved Action Route" @@ -14437,8 +14644,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17264, - -5664 + -17760, + -6160 ], "id": "b944e184-6c54-4fb8-8a41-577024fbec32", "name": "Code - Prepare Approved Compensation Preflight" @@ -14462,8 +14669,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -16912, - -5728 + -17408, + -6224 ], "id": "bba62412-ebc9-41d5-b10e-aa8dedf25ae1", "name": "HTTP - Approved Get Employee Directory", @@ -14483,8 +14690,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16704, - -5728 + -17200, + -6224 ], "id": "535885c1-77a3-4ea0-a9f7-ef8ca8dabd9a", "name": "Code - Resolve Approved Employee From Directory" @@ -14517,8 +14724,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -16496, - -5728 + -16992, + -6224 ], "id": "187eee8e-23c5-42db-a998-43a596e68ce1", "name": "IF - Approved Employee Resolved?" @@ -14542,8 +14749,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -14048, - -4144 + -13824, + -4288 ], "id": "5c1b1674-58f1-46a1-a4a0-71797f0f1e16", "name": "HTTP - Approved Get Current Compensation", @@ -14563,8 +14770,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -13840, - -4144 + -13616, + -4288 ], "id": "384ba03f-82bf-4b54-b649-4846e0ed27ca", "name": "Code - Build Approved Compensation Write Request" @@ -14597,8 +14804,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -13632, - -4144 + -13408, + -4288 ], "id": "c1793f86-dbc6-4927-abb6-62025398087c", "name": "IF - Approved Compensation Ready To Execute?" @@ -14611,8 +14818,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -13328, - -4032 + -13104, + -4176 ], "id": "72721d77-a1a3-4e93-8dd7-6768ddae99c0", "name": "Code - Format Compensation Execution Blocked" @@ -14650,8 +14857,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -13072, - -3920 + -12848, + -4064 ], "id": "5712e9ec-27dd-4ab5-aa58-6e48e65ea91a", "name": "HTTP - Mark Approval Execution Blocked", @@ -14684,8 +14891,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -13424, - -4240 + -13200, + -4384 ], "id": "1974427f-a8db-4cd7-a289-4d190158e1f7", "name": "HTTP - Execute Approved Compensation Update", @@ -14705,8 +14912,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -13216, - -4240 + -12992, + -4384 ], "id": "dd1cb3da-c560-4fc7-ad68-f8d5cd078fbd", "name": "Code - Format Compensation Execution Result" @@ -14744,8 +14951,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -13040, - -4256 + -12816, + -4400 ], "id": "fabd1fe2-2dd2-4d5d-b567-f4af69afbef1", "name": "HTTP - Mark Approval Executed", @@ -14759,8 +14966,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -12592, - -4288 + -12368, + -4432 ], "id": "4ae2afb1-e4d7-4cc1-bfba-df1d4351a09c", "name": "Code - Build Requester Final Execution Notification" @@ -14793,8 +15000,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -12384, - -4288 + -12160, + -4432 ], "id": "0f7323f7-9d63-4dcb-971f-06cf54b0e513", "name": "IF - Requester Final Chat Available?" @@ -14828,8 +15035,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -12832, - -4256 + -12608, + -4400 ], "id": "3db19bb6-7ac7-420e-9dd2-46d31f900af5", "name": "HTTP - Get Requester Final Chat Contact", @@ -14843,8 +15050,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17744, - -3744 + -18240, + -4240 ], "id": "f1b3bb9b-c32f-46bf-a961-cb4386f18b11", "name": "Code - Format Approved Action Not Supported1" @@ -14882,8 +15089,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17520, - -3744 + -18016, + -4240 ], "id": "a4838fbd-ed49-4e7f-a7ef-42ff663cfff4", "name": "HTTP - Mark Approved Action Not Supported", @@ -14897,8 +15104,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -18016, - -4064 + -18512, + -4560 ], "id": "5b534071-4084-4b7c-9176-e36a2caf2b75", "name": "Code - Prepare Approved PDF Upload" @@ -14931,8 +15138,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -17792, - -4096 + -18288, + -4592 ], "id": "60b17828-2c0e-4f9c-8f10-093d9b1f704d", "name": "IF - Approved PDF Upload Ready?" @@ -14945,8 +15152,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17408, - -4000 + -17904, + -4496 ], "id": "5bc08e8d-5ad0-4bc0-b4a7-27dc3e947ca5", "name": "Code - Format PDF Upload Blocked" @@ -14984,8 +15191,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17200, - -4000 + -17696, + -4496 ], "id": "06c7229e-a917-4aa7-92b2-4bf0fdf24ca8", "name": "HTTP - Mark PDF Upload Blocked", @@ -15007,8 +15214,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17056, - -4944 + -17552, + -5440 ], "id": "980a99a3-7c3e-442f-b856-dc5d793f4af6", "name": "HTTP - Download Approved PDF From Google Chat", @@ -15036,8 +15243,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16800, - -4944 + -17296, + -5440 ], "id": "18f3510c-55a1-4ea6-bd1c-17d31ec3f4df", "name": "Code - Validate Downloaded PDF Binary" @@ -15070,8 +15277,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -16640, - -4944 + -17136, + -5440 ], "id": "d9d5f9ea-8fa4-4cf8-a93d-7afb1fa23871", "name": "IF - Downloaded PDF Ready?" @@ -15084,8 +15291,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16432, - -4848 + -16928, + -5344 ], "id": "dda5f754-63b3-45d9-be12-9e767dee108e", "name": "Code - Format PDF Download Blocked" @@ -15102,8 +15309,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -16224, - -4848 + -16720, + -5344 ], "id": "fccb68ee-e3e7-416b-a140-4a30f10d3c66", "name": "HTTP - Mark PDF Download Blocked", @@ -15117,8 +15324,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16480, - -5136 + -16976, + -5632 ], "id": "6d9a58cd-c46d-4609-a284-c16421d047ec", "name": "Code - Prepare BambooHR PDF Upload Request" @@ -15151,8 +15358,8 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - -15712, - -4336 + -15488, + -4480 ], "id": "223e685e-1544-4e6e-a874-5862b723ec12", "name": "IF - BambooHR PDF Upload Ready?" @@ -15165,8 +15372,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -15504, - -4240 + -15280, + -4384 ], "id": "ea50aaa2-1c16-4775-9fe4-9da45f2faaa1", "name": "Code - Format BambooHR PDF Upload Precheck Blocked" @@ -15204,8 +15411,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -15296, - -4240 + -15072, + -4384 ], "id": "57e82484-beb9-4050-9f5a-3b457fe95b36", "name": "HTTP - Mark BambooHR PDF Upload Precheck Blocked", @@ -15258,8 +15465,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -15488, - -4496 + -15264, + -4640 ], "id": "8ee9bb94-9a80-417a-9036-54284f4cf578", "name": "HTTP - Upload PDF To BambooHR", @@ -15279,8 +15486,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -15280, - -4496 + -15056, + -4640 ], "id": "3e3a4d95-812b-4704-98af-58a7a0d5e45d", "name": "Code - Format PDF Upload Result" @@ -15318,8 +15525,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -15072, - -4496 + -14848, + -4640 ], "id": "e1e4f580-b6bd-45b8-9557-ec3375deb38b", "name": "HTTP - Mark PDF Upload Finished", @@ -15333,8 +15540,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -14944, - -4688 + -14720, + -4832 ], "id": "6e63b7d9-182d-4ed9-96b9-ec1e3ea287e3", "name": "Code - Build PDF Upload Metadata Log" @@ -15372,8 +15579,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -14736, - -4688 + -14512, + -4832 ], "id": "e4b522ab-a8cd-4b70-a0c2-c3dc34f61193", "name": "Supabase - Log PDF Upload Metadata", @@ -15387,8 +15594,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -14112, - -4528 + -13888, + -4672 ], "id": "948083aa-269e-4f2d-9f4d-76e4e6edfd01", "name": "Code - Build PDF Blocked Metadata Log" @@ -15426,8 +15633,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -13904, - -4528 + -13680, + -4672 ], "id": "ddfdb8cb-5f64-41a0-8ecc-099213f8c51f", "name": "Supabase - Log PDF Blocked Metadata", @@ -15456,8 +15663,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -16272, - -5136 + -16768, + -5632 ], "id": "12327d4c-bfc0-4658-96ec-1be2ccb34ad0", "name": "HTTP - List BambooHR Employee File Categories", @@ -15477,8 +15684,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -16064, - -5136 + -16560, + -5632 ], "id": "1b404504-66a1-4151-a2ed-fa616ae0bf63", "name": "Code - Resolve BambooHR PDF Category ID" @@ -15491,8 +15698,8 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17552, - -4320 + -18048, + -4816 ], "id": "f5bebc99-90b7-411f-8ef6-94544373506f", "name": "Code - Decide Approved PDF Download Source" @@ -15582,8 +15789,8 @@ "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ - -17360, - -4448 + -17856, + -4944 ], "id": "33ee8521-e9fb-4095-9a32-11f7fbd9bc0d", "name": "Switch - Approved PDF Download Source" @@ -15613,8 +15820,8 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ - -17104, - -4592 + -17600, + -5088 ], "id": "ff2a8f74-1b3f-4296-ba14-feba62b13330", "name": "HTTP - Download Approved PDF From Google Drive", @@ -15634,11 +15841,249 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - -17072, - -4384 + -17568, + -4880 ], "id": "42b9d5b3-55b3-438f-a158-505d3fbe9917", "name": "Code - Prepare PDF Download Source Blocker" + }, + { + "parameters": { + "content": "# 🤖 BAMBOOHR AGENT CORE\n\nAgente conversacional de RR. HH. para consultar y ejecutar operaciones en BambooHR mediante Google Chat.\n\nArquitectura:\n\n- **Google Chat:** canal de comunicación con el usuario.\n- **Gemini:** interpreta lenguaje natural y genera intención estructurada.\n- **Supabase:** permisos, confirmaciones, aprobaciones y auditoría.\n- **BambooHR API:** fuente oficial y sistema de ejecución.\n- **Google Sheets:** exportación de reportes extensos.\n- **n8n:** validación, autorización, enrutamiento y control del proceso.\n\nModos disponibles:\n\n1. Consultas de empleados.\n2. Consultas históricas y sensibles.\n3. Actualizaciones de información.\n4. Creación de empleados.\n5. Desvinculaciones.\n6. Reportes generales y guardados.\n7. Subida de documentos PDF.\n8. Aprobaciones sensibles por Máximo.\n\nToda acción sensible requiere autorización, confirmación y trazabilidad.", + "height": 912, + "width": 1632, + "color": "#537628" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -2864, + -14784 + ], + "id": "c0c5c70f-0b0f-4066-8a72-fb3e1cb6d6bf", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# 🧪 ENTRADA MANUAL DE PRUEBAS\n\nPunto de entrada utilizado para ejecutar pruebas controladas directamente desde el editor de n8n.\n\nEste bloque permite definir manualmente:\n\n- Correo del usuario.\n- Nombre del usuario.\n- Canal de origen.\n- Ambiente de ejecución.\n- Mensaje que será interpretado por el agente.\n\nSu objetivo es validar la lógica del core sin depender de Google Chat.\n\nImportante:\n\n- Esta entrada corresponde al ambiente de desarrollo.\n- Los permisos utilizados deben representar usuarios de prueba.\n- No debe confundirse con el Webhook oficial de producción.\n- Los resultados deben pasar por las mismas reglas de interpretación, autorización y auditoría que una solicitud real.", + "height": 544, + "width": 720, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -23488, + -2528 + ], + "id": "53563542-d38a-4a09-ba71-49321da5d1ae", + "name": "Sticky Note1" + }, + { + "parameters": { + "content": "# 💬 RECEPCIÓN DESDE GOOGLE CHAT\n\nRecibe los eventos enviados por Google Chat y determina qué tipo de interacción inició el usuario.\n\nEl bloque:\n\n- Detecta mensajes normales.\n- Detecta botones de aprobación.\n- Extrae usuario, correo, espacio, hilo y mensaje.\n- Identifica documentos adjuntos.\n- Normaliza el evento al formato interno del agente.\n- Genera un request_id para trazabilidad.\n- Responde inmediatamente a Google Chat para evitar timeout.\n\nEventos especiales:\n\n- Mensaje de texto.\n- Documento adjunto.\n- Botón de aprobación o rechazo.\n- Incorporación del bot a un espacio.\n- Respuesta a una confirmación pendiente.\n\nLa respuesta inmediata no representa el resultado final; solamente confirma que la solicitud comenzó a procesarse.", + "height": 816, + "width": 1680, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -25376, + -3472 + ], + "id": "debf5c52-e267-4b21-9db3-dc1d315ad90a", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "# 🔘 DECISIÓN MEDIANTE BOTÓN DE APROBACIÓN\n\nProcesa los botones de aprobar o rechazar enviados a Máximo en Google Chat.\n\nValidaciones:\n\n- El botón debe contener un approval_id válido.\n- La aprobación debe existir en Supabase.\n- La solicitud debe continuar pendiente.\n- La decisión debe corresponder a aprobar o rechazar.\n- El usuario que responde debe estar autorizado.\n- No se permiten decisiones duplicadas.\n\nCuando la decisión es válida:\n\n1. Se actualiza la aprobación en Supabase.\n2. Se registra quién tomó la decisión.\n3. Se guarda la fecha y el resultado.\n4. Se prepara una respuesta para el botón.\n5. Si Máximo aprobó, se inicia la ejecución de la acción autorizada.\n\nUn rechazo finaliza la solicitud sin modificar BambooHR.", + "height": 1392, + "width": 1808, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -23440, + -4064 + ], + "id": "21a54585-774f-48f6-88b8-14936787e432", + "name": "Sticky Note3" + }, + { + "parameters": { + "content": "# 🔔 NOTIFICACIÓN DE DECISIÓN AL SOLICITANTE\n\nInforma al solicitante cuando Máximo aprueba o rechaza una acción sensible.\n\nEste bloque:\n\n1. Recupera el resultado de la aprobación procesada.\n2. Determina si corresponde notificar al solicitante.\n3. Busca en Supabase su contacto o espacio de Google Chat.\n4. Construye un mensaje con la decisión tomada.\n5. Verifica que exista un espacio válido antes de intentar enviarlo.\n6. Devuelve inmediatamente el resultado técnico de la interacción con el botón.\n\nLa notificación puede indicar:\n\n- Solicitud aprobada.\n- Solicitud rechazada.\n- Acción pendiente de ejecución.\n- Acción que no requiere notificación adicional.\n- Imposibilidad de localizar al solicitante en Google Chat.\n\nReglas:\n\n- Solo se notifica cuando la decisión pertenece a una aprobación válida.\n- La respuesta del botón y la notificación al solicitante son procesos distintos.\n- La aprobación no significa necesariamente que BambooHR ya fue modificado.\n- El mensaje debe diferenciar entre APROBADA, RECHAZADA y EJECUTADA.\n- Si no existe contacto de Google Chat, el workflow continúa sin provocar error.\n- No se deben exponer datos sensibles adicionales en la notificación.", + "height": 976, + "width": 1520, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -18064, + -8112 + ], + "id": "da3dd64e-c645-4e2f-9b55-c71af898a9c8", + "name": "Sticky Note4" + }, + { + "parameters": { + "content": "# 🧭 EJECUCIÓN DE ACCIONES APROBADAS\n\nRecibe una solicitud previamente aprobada y determina qué operación debe ejecutarse en BambooHR.\n\nAntes de continuar, este bloque:\n\n- Reconstruye el contexto original de la aprobación.\n- Recupera la acción, el empleado y el payload autorizado.\n- Verifica que la aprobación continúe vigente.\n- Confirma que la acción tenga una ruta implementada.\n- Impide modificar los datos después de la aprobación.\n- Dirige la solicitud hacia compensación, carga de PDF u otra ruta autorizada.\n\n## Ruta de compensación\n\nCuando la acción aprobada corresponde a una modificación salarial:\n\n1. Prepara el contexto de ejecución.\n2. Consulta nuevamente el directorio de BambooHR.\n3. Resuelve al empleado objetivo.\n4. Verifica que exista una coincidencia confiable.\n5. Solamente entonces permite continuar con las validaciones y la actualización.\n\n## Ruta de documento PDF\n\nCuando la acción aprobada corresponde a cargar un documento:\n\n1. Valida empleado, archivo, nombre y categoría.\n2. Determina si el archivo proviene de Google Chat o Google Drive.\n3. Descarga el documento desde la fuente autorizada.\n4. Comprueba que exista contenido binario válido.\n5. Verifica que el documento sea un PDF.\n6. Consulta las categorías de archivos disponibles en BambooHR.\n7. Resuelve la categoría correcta antes de preparar la carga.\n\n## Controles de seguridad\n\nLa operación se bloquea cuando:\n\n- La acción no está implementada.\n- La aprobación no contiene todos los datos.\n- No puede resolverse el empleado.\n- La fuente del documento no está soportada.\n- El archivo no puede descargarse.\n- El contenido no es un PDF válido.\n- No puede resolverse la categoría de BambooHR.\n- El payload actual difiere del aprobado.\n\nToda ruta bloqueada actualiza el estado de la aprobación para dejar trazabilidad y evitar que el sistema informe una ejecución exitosa incorrectamente.", + "height": 2656, + "width": 2976, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -19344, + -6320 + ], + "id": "919811d0-612f-4bfd-85e2-f9892cb14f8a", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "# ✅ EJECUCIÓN FINAL Y TRAZABILIDAD\n\nCompleta la ejecución de las acciones aprobadas, registra el resultado y notifica al solicitante.\n\nEste bloque administra dos operaciones sensibles:\n\n- Carga de documentos PDF al expediente de un empleado.\n- Actualización aprobada de compensación.\n\n## Documento PDF\n\nAntes de cargar el archivo se valida que:\n\n- El empleado esté correctamente identificado.\n- El documento haya sido descargado.\n- Exista contenido binario válido.\n- El archivo sea un PDF.\n- La categoría de BambooHR esté resuelta.\n- Los datos coincidan con la solicitud aprobada.\n\nCuando la carga es exitosa:\n\n1. El PDF se incorpora al expediente del empleado.\n2. Se formatea la respuesta de BambooHR.\n3. La aprobación se marca como finalizada.\n4. Se construyen los metadatos de la operación.\n5. El resultado se registra en Supabase.\n\nCuando la carga queda bloqueada:\n\n- No se envía ningún archivo a BambooHR.\n- Se registra el motivo del bloqueo.\n- La aprobación queda marcada con el resultado correspondiente.\n- Los metadatos del intento también se guardan en Supabase.\n\n## Compensación\n\nAntes de actualizar la compensación:\n\n- Se consulta el valor vigente.\n- Se construye el request con el payload aprobado.\n- Se validan monto, moneda, frecuencia y fecha efectiva.\n- Se comprueba que los valores no hayan cambiado después de la aprobación.\n\nCuando la ejecución es válida:\n\n1. Se actualiza la compensación en BambooHR.\n2. Se interpreta la respuesta de la API.\n3. La aprobación se marca como EJECUTADA.\n\nCuando una validación falla:\n\n- La modificación no se realiza.\n- La aprobación se marca como BLOQUEADA.\n- Se conserva el motivo para auditoría.\n\n## Notificación final\n\nDespués de completar o bloquear la operación:\n\n1. Se busca el contacto del solicitante en Google Chat.\n2. Se construye el mensaje con el resultado real.\n3. Se verifica que exista un espacio de Chat disponible.\n4. Se envía la notificación final.\n\nEl mensaje debe diferenciar claramente entre:\n\n- Acción ejecutada correctamente.\n- Acción aprobada pero bloqueada.\n- Error devuelto por BambooHR.\n- Documento cargado.\n- Documento no cargado.\n- Compensación actualizada.\n- Compensación no modificada.\n\nUna aprobación no debe presentarse como exitosa hasta confirmar que la operación terminó correctamente en BambooHR.", + "height": 2320, + "width": 4384, + "color": "#1F7489" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -15712, + -6208 + ], + "id": "57e114ca-8869-4f3a-8609-6a881da21da0", + "name": "Sticky Note6" + }, + { + "parameters": { + "content": "# 🧠 INTERPRETACIÓN, AUTORIZACIÓN Y APROBACIÓN\n\nControla la etapa central de seguridad del BambooHR Agent antes de consultar o modificar información.\n\nEste bloque procesa:\n\n- Confirmaciones y cancelaciones del usuario.\n- Comandos escritos de aprobación o rechazo.\n- Interpretación de solicitudes con Gemini.\n- Validación determinística de la intención.\n- Permisos almacenados en Supabase.\n- Acceso a información sensible.\n- Solicitudes que requieren aprobación de Máximo.\n\n## Confirmaciones pendientes\n\nCuando el usuario responde CONFIRMAR o CANCELAR:\n\n- Se actualiza la confirmación correspondiente en Supabase.\n- Se recupera el contexto original de la solicitud.\n- Una cancelación detiene la operación.\n- Una confirmación restaura la acción para continuar con los controles de autorización.\n\nConfirmar una acción no sustituye los permisos ni una aprobación superior obligatoria.\n\n## Comandos de aprobación\n\nDetecta mensajes como:\n\n- APROBAR 123\n- RECHAZAR 123\n- APROBAR SOLICITUD 123\n\nAntes de registrar la decisión se valida:\n\n- Identificador de aprobación.\n- Estado pendiente.\n- Decisión solicitada.\n- Usuario que responde.\n- Integridad del registro.\n- Ausencia de una decisión anterior.\n\nUna aprobación inválida, vencida o ya procesada no puede ejecutarse nuevamente.\n\n## Interpretación con Gemini\n\nGemini convierte el mensaje del usuario en una intención estructurada con datos como:\n\n- Modo: consulta, acción o reporte.\n- Empleado objetivo.\n- Campos solicitados.\n- Acción y payload.\n- Nivel de riesgo.\n- Necesidad de confirmación.\n- Confianza de interpretación.\n\nDespués, los nodos de código normalizan y validan el resultado.\n\nGemini solamente interpreta; no concede permisos, no aprueba solicitudes y no ejecuta cambios en BambooHR.\n\n## Autorización\n\nSupabase proporciona los permisos oficiales del usuario.\n\nEl workflow verifica:\n\n- Identidad y correo del solicitante.\n- Rol y alcance organizacional.\n- País o unidad permitida.\n- Tipo de consulta o acción.\n- Sensibilidad de la información.\n- Nivel de riesgo.\n- Necesidad de aprobación superior.\n\nResultados posibles:\n\n1. Solicitud autorizada para continuar.\n2. Solicitud bloqueada por falta de permisos.\n3. Solicitud pendiente de aclaración.\n4. Solicitud enviada a Máximo para aprobación.\n\n## Aprobación de Máximo\n\nCuando la acción es sensible:\n\n1. Se construye el registro de aprobación.\n2. Se guarda la solicitud pendiente en Supabase.\n3. Se conserva el payload exacto que fue solicitado.\n4. Se busca el espacio de Google Chat de Máximo.\n5. Se genera una notificación con el resumen de la acción.\n6. Se envían las opciones para aprobar o rechazar.\n7. El solicitante queda informado de que la operación está pendiente.\n\nLa acción no puede ejecutarse hasta que exista una aprobación válida.\n\nReglas de seguridad:\n\n- No confiar únicamente en la interpretación de Gemini.\n- No modificar el payload después de enviarlo a aprobación.\n- No ejecutar acciones con permisos incompletos.\n- No revelar datos sensibles a usuarios fuera de alcance.\n- No presentar una aprobación como una ejecución completada.\n- Mantener en Supabase la trazabilidad de confirmaciones, decisiones y autorizaciones.", + "height": 1760, + "width": 7584, + "color": "#682727" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -15248, + -2688 + ], + "id": "dceb1721-9612-4620-bc3b-76db78c36cd4", + "name": "Sticky Note7" + }, + { + "parameters": { + "content": "# 📬 CONFIRMACIÓN PENDIENTE Y RESPUESTA FINAL\n\nCentraliza la respuesta producida por el agente y determina si debe guardar una confirmación antes de responder al usuario.\n\n## Preparación de la respuesta\n\nEl bloque recibe el resultado de cualquiera de las rutas anteriores y construye una respuesta uniforme con información como:\n\n- Estado de la solicitud.\n- Resultado de la consulta o acción.\n- Empleado relacionado.\n- Datos que requieren confirmación.\n- Mensaje de error o bloqueo.\n- Próximo paso que debe realizar el usuario.\n\n## Confirmaciones pendientes\n\nCuando una acción necesita confirmación expresa:\n\n1. Se prepara el registro con la intención, el empleado y el payload.\n2. Se invalidan las confirmaciones pendientes anteriores del mismo usuario.\n3. Se restaura el contexto después de actualizar los registros anteriores.\n4. Se inserta la nueva confirmación pendiente en Supabase.\n5. Se conserva la respuesta original para continuar hacia Google Chat.\n\nEl registro pendiente permite que una respuesta posterior como **CONFIRMAR** o **CANCELAR** recupere exactamente la acción original.\n\n## Reglas de confirmación\n\n- Solamente puede existir una confirmación activa por usuario.\n- Una nueva confirmación reemplaza las anteriores aún pendientes.\n- El payload debe conservarse sin modificaciones.\n- Guardar la confirmación no ejecuta la acción.\n- La operación se realizará únicamente después de una confirmación válida y de superar los controles de autorización.\n- Las consultas que no requieren confirmación pasan directamente a la respuesta final.\n\n## Respuesta por Google Chat\n\nAntes de enviar el mensaje:\n\n1. Se comprueba que el canal de origen sea Google Chat.\n2. Se construye el formato final del mensaje.\n3. Se valida que exista contenido apto para enviar.\n4. Se publica la respuesta en el espacio o hilo correspondiente.\n\nLa respuesta puede representar:\n\n- Consulta completada.\n- Solicitud pendiente de confirmación.\n- Solicitud pendiente de aprobación.\n- Acción ejecutada.\n- Acción cancelada.\n- Acción bloqueada.\n- Error técnico.\n\nNo se debe presentar una acción como ejecutada cuando únicamente fue guardada como confirmación pendiente.", + "height": 1104, + "width": 3712, + "color": "#5B2559" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 18560, + -4848 + ], + "id": "8fa31a95-003c-483d-bec9-54095524e9c5", + "name": "Sticky Note8" + }, + { + "parameters": { + "content": "# 👤 CREACIÓN Y VERIFICACIÓN DE EMPLEADO\n\nCrea un nuevo empleado en BambooHR después de validar la aprobación, los datos obligatorios y la ausencia de duplicados.\n\n## Validación previa\n\nAntes de crear el empleado, este bloque:\n\n1. Comprueba que la acción haya sido confirmada y aprobada.\n2. Construye el payload del perfil principal.\n3. Consulta el directorio actual de BambooHR.\n4. Busca coincidencias por correo, nombre u otros identificadores.\n5. Bloquea la creación cuando detecta un posible duplicado.\n6. Verifica que estén presentes todos los datos obligatorios.\n\nDatos principales esperados:\n\n- Nombre.\n- Apellido.\n- Correo laboral.\n- País o ubicación.\n- Departamento.\n- División.\n- Cargo.\n- Fecha de ingreso.\n\n## Creación del perfil\n\nCuando la solicitud es válida:\n\n1. Crea el perfil base en BambooHR.\n2. Recupera y normaliza el ID generado.\n3. Conserva ese ID para todas las operaciones posteriores.\n4. Prepara la información laboral inicial del empleado.\n\nNo se debe continuar cuando BambooHR no devuelve un ID de empleado válido.\n\n## Registro inicial de jobInfo\n\nDespués de crear el perfil:\n\n1. Construye la fila inicial de jobInfo.\n2. Consulta el historial existente del nuevo empleado.\n3. Verifica que la fila no haya sido agregada previamente.\n4. Valida la fecha efectiva y los campos laborales.\n5. Inserta la fila inicial cuando corresponde.\n\nLa fila de jobInfo puede incluir:\n\n- Fecha efectiva.\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor, cuando esté disponible.\n\n## Verificación final\n\nAl terminar:\n\n1. Consulta nuevamente el empleado por su ID.\n2. Consulta su tabla jobInfo.\n3. Comprueba que el perfil y la información laboral existan.\n4. Construye una respuesta final con el resultado real.\n\nLa respuesta debe indicar claramente:\n\n- Empleado creado correctamente.\n- ID generado por BambooHR.\n- Datos principales registrados.\n- Estado de la fila inicial de jobInfo.\n- Creación bloqueada por duplicado.\n- Datos incompletos.\n- Error devuelto por BambooHR.\n\nNo se debe informar éxito hasta verificar el perfil creado y su información laboral inicial.", + "height": 1440, + "width": 5392, + "color": "#7B7023" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1920, + -4048 + ], + "id": "d8a4e06c-688b-40f3-8acb-a58e73704760", + "name": "Sticky Note9" + }, + { + "parameters": { + "content": "# 🔎 CONSULTAS DE EXPEDIENTE E HISTORIALES\n\nEnruta las consultas autorizadas hacia la sección correspondiente del expediente del empleado en BambooHR.\n\n## Campos básicos\n\nConsulta la información actual del perfil del empleado y devuelve únicamente los campos solicitados, por ejemplo:\n\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor.\n- Correo laboral.\n- Teléfono y extensión.\n- Estatus.\n- Fecha de ingreso.\n- Información general.\n\nLa respuesta se transforma a un formato claro para el usuario, sin mostrar información adicional que no haya sido solicitada.\n\n## Historial jobInfo\n\nConsulta los cambios históricos relacionados con:\n\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor.\n- Fecha efectiva de cada modificación.\n\nLos registros se ordenan y presentan como una secuencia de movimientos laborales.\n\n## Historial employmentStatus\n\nConsulta los cambios de estatus laboral del empleado, incluyendo:\n\n- Estado registrado.\n- Fecha efectiva.\n- Motivo o tipo de movimiento, cuando BambooHR lo proporciona.\n- Historial de activación, inactivación o desvinculación.\n\nEsta ruta es solamente de consulta y no modifica el estatus del empleado.\n\n## Historial de compensación\n\nLa compensación se considera información altamente sensible.\n\nAntes de consultar BambooHR se valida:\n\n1. Permiso específico del solicitante.\n2. Alcance sobre el empleado.\n3. Rol autorizado.\n4. Nivel de sensibilidad permitido.\n\nCuando el acceso es aprobado:\n\n- Se consulta la tabla de compensación.\n- Se formatean fechas, montos, monedas y frecuencias.\n- Se devuelve únicamente la información autorizada.\n\nCuando el acceso es rechazado, no se consulta ni se revela información salarial.\n\n## Datos faltantes\n\nRevisa los campos relevantes del expediente y determina cuáles están vacíos o incompletos.\n\nPuede validar información como:\n\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor.\n- Correo y teléfono laboral.\n- Estatus.\n- Fecha de ingreso.\n\nLa respuesta distingue entre campos completos, faltantes y no disponibles.\n\n## Archivos del empleado\n\nConsulta los documentos registrados en el expediente de BambooHR y prepara una respuesta con:\n\n- Nombre del archivo.\n- Categoría.\n- Fecha disponible.\n- Identificador o referencia permitida.\n- Cantidad total de documentos encontrados.\n\nLa consulta de archivos también queda sujeta al alcance y permisos del usuario.\n\n## Reglas generales\n\n- El empleado debe estar resuelto antes de entrar a estas rutas.\n- Solamente se consultan los datos solicitados.\n- BambooHR es la fuente oficial de la información.\n- Una respuesta vacía no debe presentarse como un error de ejecución.\n- No se inventan campos, historiales, documentos ni valores.\n- La información sensible requiere validación adicional.\n- Ninguna de estas rutas modifica el expediente del empleado.", + "height": 2256, + "width": 3456 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 5200, + -14048 + ], + "id": "e6abb8c2-e45d-45f5-98c7-da2439653862", + "name": "Sticky Note10" + }, + { + "parameters": { + "content": "# ⚙️ EJECUCIÓN DE ACCIONES CONFIRMADAS\n\nProcesa las acciones que ya fueron confirmadas por el usuario y superaron los controles de autorización.\n\nAntes de ejecutar cualquier cambio, este bloque verifica:\n\n- Que exista una acción estructurada.\n- Que la confirmación pertenezca al usuario correcto.\n- Que el empleado haya sido resuelto.\n- Que el usuario tenga permisos sobre el empleado.\n- Que el payload conserve los datos originalmente confirmados.\n- Que la acción no haya sido cancelada ni ejecutada anteriormente.\n\nDespués de validar la aprobación, el Switch dirige la solicitud hacia la operación correspondiente.\n\n## Actualización de campos básicos\n\nEsta ruta modifica un campo actual del perfil del empleado.\n\nProceso:\n\n1. Consulta el estado actual del empleado.\n2. Construye un payload limitado al campo solicitado.\n3. Envía la actualización a BambooHR.\n4. Consulta nuevamente el perfil.\n5. Verifica el valor almacenado.\n6. Prepara la respuesta final.\n\nReglas:\n\n- No se modifican campos adicionales.\n- No se envían valores vacíos.\n- El valor nuevo debe coincidir con el confirmado.\n- No se informa éxito sin consultar nuevamente al empleado.\n- Los errores de BambooHR deben conservarse en la respuesta.\n\n## Adición de una fila jobInfo\n\nEsta ruta agrega un movimiento al historial laboral del empleado.\n\nPuede incluir:\n\n- Fecha efectiva.\n- Cargo.\n- Departamento.\n- División.\n- Ubicación.\n- Supervisor.\n\nAntes de insertar:\n\n1. Construye el payload de jobInfo.\n2. Consulta las filas existentes.\n3. Compara el nuevo movimiento con el historial actual.\n4. Bloquea registros duplicados.\n5. Valida que estén presentes los campos obligatorios.\n\nCuando la fila está permitida:\n\n1. Se agrega a BambooHR.\n2. Se consulta nuevamente jobInfo.\n3. Se verifica que el movimiento exista.\n4. Se construye la respuesta con el resultado real.\n\n## Preparación de desvinculación\n\nCuando la solicitud corresponde a preparar una terminación:\n\n- No se modifica todavía el estatus del empleado.\n- Se presenta un resumen de la fecha, motivo y tipo de salida.\n- Se informa claramente que la desvinculación aún no ha sido ejecutada.\n- Se conserva el contexto necesario para la ruta de ejecución definitiva.\n\n## Controles generales\n\nLa acción debe bloquearse cuando:\n\n- No existe confirmación válida.\n- El empleado no fue resuelto.\n- El usuario no está autorizado.\n- El payload está incompleto.\n- Se detecta un movimiento duplicado.\n- Los datos actuales difieren de lo confirmado.\n- BambooHR no confirma el resultado.\n\nConfirmar una acción autoriza únicamente la operación y los valores mostrados al usuario; no permite introducir cambios adicionales durante la ejecución.", + "height": 1824, + "width": 4224, + "color": "#125768" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 3312, + -11264 + ], + "id": "3ee03bca-a471-40ee-a3ed-e25e158b7551", + "name": "Sticky Note11" + }, + { + "parameters": { + "content": "# 🛑 EJECUCIÓN Y AUDITORÍA DE DESVINCULACIÓN\n\nEjecuta de forma controlada la desvinculación real de un empleado mediante la tabla `employmentStatus` de BambooHR.\n\n## Preparación del movimiento\n\nEl bloque construye el payload utilizando exclusivamente los datos confirmados y autorizados:\n\n- Empleado objetivo.\n- Fecha efectiva de salida.\n- Motivo de terminación.\n- Tipo de terminación.\n- Valores requeridos por BambooHR.\n- Usuario que solicitó y confirmó la acción.\n\nAntes de continuar, consulta el historial actual de `employmentStatus` para evitar registrar una desvinculación duplicada.\n\n## Validación de duplicados\n\nLa ejecución se bloquea cuando BambooHR ya contiene un movimiento equivalente para el mismo empleado.\n\nLa comparación puede considerar:\n\n- Fecha efectiva.\n- Estatus registrado.\n- Tipo de terminación.\n- Motivo.\n- Identificador del movimiento.\n\nUna operación bloqueada por duplicado no debe enviarse nuevamente a BambooHR ni presentarse como exitosa.\n\n## Resolución de listas de BambooHR\n\nBambooHR puede requerir identificadores internos en lugar de los textos visibles de motivo, tipo o estatus.\n\nPor eso, el flujo:\n\n1. Consulta las listas de metadatos disponibles.\n2. Busca los valores solicitados.\n3. Resuelve sus IDs internos.\n4. Comprueba que todos los IDs obligatorios estén disponibles.\n\nSi algún valor no puede mapearse, la ejecución queda bloqueada antes del POST.\n\n## Puerta final de ejecución\n\nAntes de modificar BambooHR se realiza una validación final de seguridad:\n\n- Confirmación válida.\n- Usuario autorizado.\n- Empleado correctamente resuelto.\n- Alcance organizacional permitido.\n- Acción no ejecutada anteriormente.\n- Payload completo.\n- IDs de listas resueltos.\n- Datos idénticos a los confirmados.\n- Operación marcada como lista para ejecutar.\n\nTambién se genera una vista previa del payload final para verificar exactamente qué información será enviada.\n\n## Ejecución en BambooHR\n\nCuando todos los controles son correctos:\n\n1. Se agrega una nueva fila a `employmentStatus`.\n2. Se normaliza la respuesta técnica del POST.\n3. Se consulta nuevamente el historial del empleado.\n4. Se verifica que el movimiento haya sido registrado.\n5. Se prepara la respuesta definitiva.\n\nNo se informa éxito únicamente porque el POST respondió; el movimiento debe confirmarse mediante una consulta posterior.\n\n## Rutas bloqueadas\n\nLa desvinculación se detiene cuando:\n\n- Se detecta un movimiento duplicado.\n- Faltan datos obligatorios.\n- No se resuelven los IDs de las listas.\n- La confirmación no es válida.\n- El usuario no tiene autorización.\n- El empleado está fuera de alcance.\n- El payload difiere del aprobado.\n- BambooHR devuelve un error.\n- La verificación posterior no encuentra el movimiento.\n\nCada ruta bloqueada debe explicar el motivo sin indicar que el empleado fue desvinculado.\n\n## Auditoría\n\nToda ejecución o bloqueo genera un evento de auditoría en Supabase con información como:\n\n- Solicitante.\n- Empleado afectado.\n- Acción solicitada.\n- Payload aprobado.\n- Fecha y hora.\n- Resultado de autorización.\n- Resultado de ejecución.\n- Respuesta de BambooHR.\n- Motivo del bloqueo, cuando corresponda.\n\nDespués de registrar la auditoría, se restaura la respuesta original para que pueda continuar hacia la notificación final del usuario.", + "height": 1312, + "width": 7488, + "color": 2 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + 5184, + -7376 + ], + "id": "cee3ff9c-1fc7-4727-bb6e-92e07122d203", + "name": "Sticky Note12" + }, + { + "parameters": { + "content": "# 🔎 RESOLUCIÓN DEL EMPLEADO Y CONTROL DE ALCANCE\n\nLocaliza de forma segura al empleado solicitado y comprueba que el usuario tenga autorización para acceder a su información.\n\n## Resolución desde el directorio\n\nEl flujo consulta el directorio de BambooHR y separa cada empleado para evaluar posibles coincidencias.\n\nLa búsqueda puede utilizar:\n\n- ID interno de BambooHR.\n- Número de empleado.\n- Correo laboral.\n- Nombre completo.\n- Nombre parcial normalizado.\n- Variaciones razonables de escritura.\n\nCuando se encuentra una coincidencia confiable:\n\n- Se conserva el ID oficial del empleado.\n- Se normalizan su nombre, correo, ubicación y datos organizacionales.\n- El empleado pasa directamente a la validación de alcance.\n\n## Fallback mediante ID directo\n\nCuando el directorio no devuelve una coincidencia, el flujo comprueba si la solicitud contiene un ID directo.\n\nSi existe:\n\n1. Consulta al empleado directamente por su ID.\n2. Normaliza la respuesta de BambooHR.\n3. Verifica que el empleado realmente exista.\n4. Envía el resultado al control de alcance.\n\nEl ID proporcionado por el usuario nunca se considera válido hasta confirmarlo en BambooHR.\n\n## Fallback mediante listado alternativo\n\nCuando no existe un ID directo, se utiliza un listado alternativo de empleados para intentar resolver la solicitud.\n\nEsta ruta:\n\n- Recupera empleados disponibles para búsqueda.\n- Normaliza nombres, correos e identificadores.\n- Compara la consulta del usuario con los registros.\n- Selecciona solamente una coincidencia suficientemente confiable.\n- Evita continuar cuando existen resultados ambiguos.\n\n## Control de alcance\n\nDespués de resolver al empleado, se valida si el solicitante puede acceder a esa persona.\n\nEl control puede considerar:\n\n- País.\n- Ubicación.\n- División o unidad de negocio.\n- Departamento.\n- Rol del solicitante.\n- Alcance organizacional asignado.\n- Tipo de consulta o acción.\n- Sensibilidad de la información.\n\nCuando el alcance es válido, el flujo continúa hacia el modo correspondiente:\n\n- Consulta.\n- Acción.\n- Reporte relacionado.\n- Operación posterior a resolución.\n\nCuando el alcance no es válido:\n\n- No se consulta información adicional.\n- No se modifica BambooHR.\n- No se revelan datos del empleado.\n- Se prepara una respuesta segura para el usuario.\n- El intento queda disponible para auditoría.\n\n## Reglas\n\n- No continuar cuando el empleado no puede resolverse de manera confiable.\n- No elegir automáticamente entre múltiples coincidencias ambiguas.\n- No confiar únicamente en el nombre escrito por el usuario.\n- Confirmar IDs y correos contra BambooHR.\n- Aplicar el control de alcance a todas las rutas de resolución.\n- Resolver al empleado no implica que el usuario esté autorizado para acceder a él.", + "height": 1920, + "width": 5504, + "color": "#542970" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -3920, + -10080 + ], + "id": "2f290492-0a1d-41af-8da0-8f3da8b1bf89", + "name": "Sticky Note13" + }, + { + "parameters": { + "content": "# 📊 REPORTES GENERALES Y GUARDADOS\n\nEnruta las solicitudes de reportes hacia la fuente y el formato de salida correspondientes.\n\nEste bloque permite:\n\n- Consultar empleados activos e inactivos.\n- Generar reportes de headcount.\n- Listar los Custom Reports disponibles en BambooHR.\n- Ejecutar un reporte guardado por ID.\n- Buscar y ejecutar un reporte por nombre.\n- Mostrar resultados directamente en Google Chat.\n- Exportar reportes extensos a Google Sheets.\n\n## Activos e inactivos\n\nEjecuta el reporte configurado para obtener el estado laboral de los empleados.\n\nLa respuesta puede incluir:\n\n- Empleados activos.\n- Empleados inactivos.\n- Totales por estatus.\n- Resumen general autorizado.\n\nLos resultados se normalizan antes de enviarse al usuario.\n\n## Reportes de headcount\n\nConsulta el directorio de BambooHR y agrupa empleados según el tipo de reporte solicitado.\n\nPuede generar resultados como:\n\n- Headcount total.\n- Headcount por país.\n- Headcount por departamento.\n- Headcount por ubicación.\n- Headcount por país y departamento.\n\nEl conteo debe respetar el alcance organizacional del solicitante.\n\n## Listado de reportes guardados\n\nConsulta los Custom Reports configurados en BambooHR y devuelve información como:\n\n- ID del reporte.\n- Nombre.\n- Descripción disponible.\n- Referencia necesaria para ejecutarlo.\n\nEl agente no debe inventar IDs ni nombres de reportes.\n\n## Resolución del reporte guardado\n\nEl usuario puede solicitar un reporte mediante:\n\n- ID directo.\n- Nombre completo.\n- Parte suficientemente clara del nombre.\n\nCuando se proporciona un ID, el flujo intenta ejecutar directamente ese reporte.\n\nCuando se proporciona un nombre:\n\n1. Consulta el catálogo de reportes.\n2. Normaliza los nombres.\n3. Busca una coincidencia confiable.\n4. Recupera el ID real.\n5. Ejecuta el reporte resuelto.\n\nLa solicitud se bloquea cuando:\n\n- No existe coincidencia.\n- Existen varias coincidencias ambiguas.\n- El ID no es válido.\n- El reporte no está disponible.\n- El usuario no tiene permiso.\n\n## Resultado directo\n\nCuando el reporte puede mostrarse dentro del chat:\n\n- Se formatean encabezados y filas.\n- Se aplica paginación cuando corresponde.\n- Se indica la cantidad de resultados.\n- Se evita exceder el tamaño permitido del mensaje.\n\nUna respuesta vacía debe indicarse como reporte sin registros, no necesariamente como error.\n\n## Exportación a Google Sheets\n\nCuando el usuario solicita exportar o el reporte contiene demasiados registros:\n\n1. Ejecuta el reporte inicial.\n2. Determina la cantidad de páginas.\n3. Construye las solicitudes para recuperar todas las páginas.\n4. Combina los resultados sin duplicarlos.\n5. Mantiene el orden original de las columnas.\n6. Crea un nuevo Google Sheet.\n7. Escribe encabezados y filas.\n8. Aplica formato visual a la hoja.\n9. Devuelve el enlace final al usuario.\n\n## Controles de exportación\n\n- No perder registros durante la paginación.\n- No repetir páginas.\n- Mantener encabezados consistentes.\n- Verificar que el Sheet haya sido creado.\n- Validar que las filas hayan sido escritas.\n- Informar el número total de registros exportados.\n- No presentar un enlace hasta confirmar que el archivo existe.\n\nBambooHR continúa siendo la fuente oficial; Google Sheets funciona únicamente como medio de entrega del reporte.", + "height": 2000, + "width": 7184 + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -7104, + -6720 + ], + "id": "cbd8f308-99e2-443b-b457-9ff308f56f72", + "name": "Sticky Note14" } ], "connections": { @@ -17404,11 +17849,6 @@ ] ] }, - "Code - Format Saved Report Export Preview": { - "main": [ - [] - ] - }, "Code - Prepare Saved Report Google Sheet": { "main": [ [ @@ -18523,15 +18963,15 @@ } }, "authors": "Isaac Aracena", - "name": "Version 9a7c1b7e", + "name": "Version 02437699", "description": "", "autosaved": true, "workflowPublishHistory": [ { - "createdAt": "2026-07-06T19:01:01.401Z", - "id": 2553, + "createdAt": "2026-07-16T02:38:35.949Z", + "id": 3024, "workflowId": "5VOiadzdgbs2Qq1N", - "versionId": "9a7c1b7e-df7b-4633-b952-6437ecb22ec6", + "versionId": "02437699-5dec-4508-8f05-306ce95217a0", "event": "activated", "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" }