{ "updatedAt": "2026-08-08T13:39:36.105Z", "createdAt": "2026-07-09T13:29:42.391Z", "id": "KuJTvjNNx9XmoazA", "name": "Portal de Verificación de Nómina Guatemala - Envío de Reporte", "description": "Gestiona la resolución de reportes del Portal de Verificación de Nómina de Guatemala: valida la solicitud recibida desde la aplicación, marca el reporte como resuelto en Supabase, registra los datos de la resolución, envía una notificación por correo a Yanelly y devuelve la confirmación final al portal, incluyendo manejo de solicitudes inválidas.", "active": true, "isArchived": false, "nodes": [ { "parameters": { "httpMethod": "POST", "path": "cruce-cuentas-gt-marcar-resuelto", "responseMode": "responseNode", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ -560, 32 ], "id": "681b06d3-4b12-441e-b5b2-61b283b8f418", "name": "Webhook Marcar Resuelto", "webhookId": "5b7d6bd6-caa6-47e6-b9b8-982f6e7e0a0f" }, { "parameters": { "jsCode": "const body = $input.first().json.body || $input.first().json || {};\n\nfunction clean(value) {\n return String(value ?? '').replace(/\\s+/g, ' ').trim();\n}\n\nconst reportId = clean(body.reportId || body.id);\nconst comentarioResolucion = clean(body.comentarioResolucion || body.comentario || '');\nconst resueltoPorNombre = clean(body.resueltoPorNombre || body.userName || 'Usuario GLM');\nconst resueltoPorEmail = clean(body.resueltoPorEmail || body.userEmail || '');\n\nconst errors = [];\n\nif (!reportId) {\n errors.push('No se recibió el ID del reporte.');\n}\n\nif (!comentarioResolucion) {\n errors.push('Debe indicar un comentario de resolución.');\n}\n\nif (comentarioResolucion.length < 10) {\n errors.push('El comentario de resolución debe ser más descriptivo.');\n}\n\nif (errors.length > 0) {\n return [\n {\n json: {\n ok: false,\n stage: 'validacion_resolucion',\n errors,\n reportId,\n },\n },\n ];\n}\n\nreturn [\n {\n json: {\n ok: true,\n stage: 'resolucion_validada',\n reportId,\n comentarioResolucion,\n resueltoPorNombre,\n resueltoPorEmail,\n resolvedAt: new Date().toISOString(),\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -352, 32 ], "id": "7df13555-97eb-4c62-be7d-13b12a631cd9", "name": "Validar resolución" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "c89884c6-de96-4b08-bd20-54644bb95bc7", "leftValue": "={{ $json.ok }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -144, 32 ], "id": "fdaa242f-9e63-4c95-82cd-27f645805f66", "name": "¿Solicitud válida?" }, { "parameters": { "method": "PATCH", "url": "={{ 'https://dbit.digitalcompass.agency/rest/v1/cruces_cuentas_gt_reportes?id=eq.' + $json.reportId }}", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Content-Type", "value": "application/json" }, { "name": "Prefer", "value": "return=representation" } ] }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{\n {\n estado: 'resuelto',\n resuelto_por_nombre: $json.resueltoPorNombre,\n resuelto_por_email: $json.resueltoPorEmail,\n resuelto_en: $json.resolvedAt,\n comentario_resolucion: $json.comentarioResolucion,\n updated_at: $json.resolvedAt\n }\n}}", "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 192, -80 ], "id": "31b2de6a-89f6-4d69-9712-59e5ee3957e8", "name": "Actualizar reporte Supabase" }, { "parameters": { "jsCode": "const input = $input.first().json || {};\nconst report = Array.isArray(input) ? input[0] : input;\nconst original = $('Validar resolución').first().json || {};\n\nif (!report || !report.id) {\n throw new Error('No se encontró el reporte para preparar la notificación.');\n}\n\nfunction escapeHtml(value) {\n return String(value ?? '')\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction numberValue(value) {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : 0;\n}\n\nconst periodo = report.period_label || 'Período no especificado';\nconst reportUrl = report.report_url || '';\n\nconst discrepancias = numberValue(report.discrepancias);\nconst bancoSinBamboo = numberValue(report.banco_sin_bamboo);\nconst nominaSinBamboo = numberValue(report.nomina_sin_bamboo);\nconst bambooSinNomina = numberValue(report.bamboo_sin_nomina);\n\nconst resolvedAt = original.resolvedAt || new Date().toISOString();\n\nconst resolvedDateLabel = new Date(resolvedAt).toLocaleString('es-GT', {\n timeZone: 'America/Guatemala',\n dateStyle: 'long',\n timeStyle: 'short',\n});\n\nconst resolvedByName =\n original.resueltoPorNombre || 'Usuario GLM';\n\nconst resolvedByEmail =\n original.resueltoPorEmail || '';\n\nconst resolutionComment =\n original.comentarioResolucion || 'Sin comentario registrado.';\n\nconst logoUrl =\n 'https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM_completo.png';\n\nconst subject =\n `Reporte resuelto - Cruce de Cuentas Guatemala - ${periodo}`;\n\nconst textBody = `\nHola Yanelly,\n\nSe marcó como resuelto un reporte de Cruce de Cuentas Guatemala.\n\nPeríodo:\n${periodo}\n\nResuelto por:\n${resolvedByName}${resolvedByEmail ? ` (${resolvedByEmail})` : ''}\n\nFecha/hora:\n${resolvedDateLabel}\n\nResumen:\n- Discrepancias: ${discrepancias}\n- Banco sin Bamboo: ${bancoSinBamboo}\n- Nómina sin Bamboo: ${nominaSinBamboo}\n- Bamboo sin Nómina: ${bambooSinNomina}\n\nComentario:\n${resolutionComment}\n\nGoogle Sheet:\n${reportUrl || 'No disponible'}\n\nSaludos,\nPortal Cruce de Cuentas GLM\n`.trim();\n\nconst htmlBody = `\n\n\n\n \n \n ${escapeHtml(subject)}\n\n\n\n\n \n \n \n\n \n\n \n \n \n \n \n\n \n  \n \n\n \n \n
\n Cruce de Cuentas Guatemala\n
\n\n

\n Reporte marcado como resuelto\n

\n \n \n\n \n \n

\n Hola Yanelly,\n

\n\n

\n El siguiente reporte fue revisado y marcado como\n resuelto.\n

\n \n \n\n \n \n \n \n \n Período\n \n\n \n ${escapeHtml(periodo)}\n \n \n\n \n \n Resuelto por\n \n\n \n ${escapeHtml(resolvedByName)}\n ${\n resolvedByEmail\n ? `
${escapeHtml(resolvedByEmail)}`\n : ''\n }\n \n \n\n \n \n Fecha y hora\n \n\n \n ${escapeHtml(resolvedDateLabel)}\n \n \n \n \n \n\n \n \n
\n Resumen del reporte\n
\n \n \n\n \n \n \n \n \n
\n ${discrepancias}\n
\n\n
\n Discrepancias\n
\n \n\n \n
\n ${bancoSinBamboo}\n
\n\n
\n Banco sin Bamboo\n
\n \n\n \n
\n ${nominaSinBamboo}\n
\n\n
\n Nómina sin Bamboo\n
\n \n\n \n
\n ${bambooSinNomina}\n
\n\n
\n Bamboo sin Nómina\n
\n \n \n \n \n \n\n \n \n
\n Comentario de resolución\n
\n \n \n\n \n \n
\n ${escapeHtml(resolutionComment).replace(/\\n/g, '
')}\n
\n \n \n\n ${\n reportUrl\n ? `\n \n \n \n Abrir Google Sheet\n \n \n \n `\n : ''\n }\n\n \n \n Mensaje automático del Portal Cruce de Cuentas GLM.\n \n \n\n \n \n \n \n\n\n\n`.trim();\n\nreturn [\n {\n json: {\n to: 'ymadera@gomezleemarketing.com, jgomez@gomezleemarketing.com, mgomez@gomezleemarketing.com',\n cc: 'iaracena@gomezleemarketing.com',\n subject,\n textBody,\n htmlBody,\n report,\n resolution: original,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 400, -80 ], "id": "fb3f529f-164b-4987-a2af-e99e8544c9b1", "name": "Preparar correo Yanelly" }, { "parameters": { "sendTo": "={{$json.to}}", "subject": "={{$json.subject}}", "message": "={{ $json.htmlBody }}", "options": { "appendAttribution": false, "ccList": "={{ $json.cc }}" } }, "type": "n8n-nodes-base.gmail", "typeVersion": 2.2, "position": [ 608, -80 ], "id": "86252093-a337-4c18-a733-d7f80a954a1f", "name": "Enviar correo Yanelly", "webhookId": "da3b90f2-2e52-4a99-beea-5bc580486f75", "credentials": { "gmailOAuth2": { "id": "UDcO1FLJqA453V2D", "name": "Gmail account 3" } } }, { "parameters": { "jsCode": "const original = $('Validar resolución').first().json || {};\nconst updatedResponse = $('Actualizar reporte Supabase').first().json || {};\nconst updatedReport = Array.isArray(updatedResponse) ? updatedResponse[0] : updatedResponse;\n\nreturn [\n {\n json: {\n ok: true,\n stage: 'reporte_resuelto',\n message: 'Reporte marcado como resuelto correctamente.',\n report: {\n id: original.reportId,\n estado: 'resuelto',\n estadoLabel: 'RESUELTO',\n resueltoPorNombre: original.resueltoPorNombre,\n resueltoPorEmail: original.resueltoPorEmail,\n resueltoEn: original.resolvedAt,\n comentarioResolucion: original.comentarioResolucion,\n reportUrl: updatedReport.report_url || '',\n },\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 816, -80 ], "id": "43dd0a1d-3829-46a6-bcf2-349ad4f5754a", "name": "Preparar respuesta resuelto" }, { "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": { "responseCode": 200, "responseHeaders": { "entries": [ { "name": "Content-Type", "value": "application/json" }, { "name": "Access-Control-Allow-Origin", "value": "*" }, { "name": "Access-Control-Allow-Methods", "value": "GET,POST,OPTIONS" }, { "name": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" } ] } } }, "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ 1024, -80 ], "id": "4fcda86c-0bd9-4797-a85a-273ce6876ad8", "name": "Responder resuelto app" }, { "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": { "responseCode": 400, "responseHeaders": { "entries": [ { "name": "Content-Type", "value": "application/json" }, { "name": "Access-Control-Allow-Origin", "value": "*" }, { "name": "Access-Control-Allow-Methods", "value": "GET,POST,OPTIONS" }, { "name": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" } ] } } }, "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ 64, 128 ], "id": "15a0d2a5-d5f7-4da0-ba2b-ea08dc77dc23", "name": "Responder error resolución" }, { "parameters": { "content": "# ✅ CIERRE DE REPORTE — GUATEMALA\n\nRecibe desde el Portal de Verificación de Nóminas la solicitud para marcar un reporte de Guatemala como resuelto.\n\n## Validación de la solicitud\n\nEl Webhook recibe la información enviada por la aplicación y verifica que existan los datos necesarios para procesar el cierre, como:\n\n- Identificador del reporte.\n- Usuario que realizó la resolución.\n- Nuevo estado solicitado.\n- Comentario o detalle de la resolución.\n- Información necesaria para localizar el registro en Supabase.\n\nLa solicitud solamente continúa cuando los datos son válidos y suficientes.\n\n## Actualización en Supabase\n\nCuando la solicitud es correcta:\n\n1. Localiza el reporte correspondiente.\n2. Actualiza su estado a RESUELTO.\n3. Registra el usuario responsable del cierre.\n4. Guarda la fecha y hora de resolución.\n5. Conserva el comentario o detalle proporcionado.\n\nSupabase es la fuente oficial del estado mostrado en el Portal de Verificación de Nóminas.\n\n## Notificación a Yanelly\n\nDespués de confirmar la actualización:\n\n1. Se prepara un correo con los datos principales del reporte.\n2. Se informa que el caso de Guatemala fue marcado como resuelto.\n3. Se identifica al usuario que realizó el cierre.\n4. Se incluye el comentario de resolución, cuando esté disponible.\n5. Se envía la notificación a Yanelly mediante Gmail.\n\nEl correo solamente debe enviarse después de comprobar que Supabase fue actualizado correctamente.\n\n## Respuesta a la aplicación\n\nCuando el proceso termina correctamente:\n\n- Se construye una respuesta de éxito.\n- Se confirma el nuevo estado del reporte.\n- Se devuelve el resultado a la aplicación.\n- El portal puede actualizar inmediatamente la interfaz.\n\n## Ruta de error\n\nCuando la solicitud no supera la validación:\n\n- No se modifica el reporte en Supabase.\n- No se envía ningún correo.\n- Se devuelve una respuesta de error a la aplicación.\n- Se informa que la solicitud contiene datos incompletos o inválidos.\n\n## Reglas\n\n- No marcar un reporte como resuelto sin un identificador válido.\n- No enviar el correo antes de confirmar la actualización.\n- No declarar éxito si Supabase devolvió un error.\n- Evitar cambios parciales entre la base de datos y la notificación.\n- Ambas rutas deben responder al Webhook para evitar solicitudes abiertas.\n- La ruta inválida debe finalizar sin modificar información.", "height": 1248, "width": 2624, "color": "#832F2F" }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -1376, -624 ], "id": "b9408dc9-bbab-4585-bf2a-9f88f5fe8eaa", "name": "Sticky Note" } ], "connections": { "Webhook Marcar Resuelto": { "main": [ [ { "node": "Validar resolución", "type": "main", "index": 0 } ] ] }, "Validar resolución": { "main": [ [ { "node": "¿Solicitud válida?", "type": "main", "index": 0 } ] ] }, "¿Solicitud válida?": { "main": [ [ { "node": "Actualizar reporte Supabase", "type": "main", "index": 0 } ], [ { "node": "Responder error resolución", "type": "main", "index": 0 } ] ] }, "Actualizar reporte Supabase": { "main": [ [ { "node": "Preparar correo Yanelly", "type": "main", "index": 0 } ] ] }, "Preparar correo Yanelly": { "main": [ [ { "node": "Enviar correo Yanelly", "type": "main", "index": 0 } ] ] }, "Enviar correo Yanelly": { "main": [ [ { "node": "Preparar respuesta resuelto", "type": "main", "index": 0 } ] ] }, "Preparar respuesta resuelto": { "main": [ [ { "node": "Responder resuelto app", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "binaryMode": "separate", "availableInMCP": true, "timeSavedMode": "fixed", "errorWorkflow": "puF4LUczoSz3hcek", "timezone": "America/Santo_Domingo", "callerPolicy": "workflowsFromSameOwner" }, "staticData": null, "meta": { "templateCredsSetupCompleted": true }, "versionId": "afe39c11-be81-4a06-8937-36adaf938182", "activeVersionId": "afe39c11-be81-4a06-8937-36adaf938182", "versionCounter": 160, "triggerCount": 1, "shared": [ { "updatedAt": "2026-07-09T13:29:42.395Z", "createdAt": "2026-07-09T13:29:42.395Z", "role": "workflow:owner", "workflowId": "KuJTvjNNx9XmoazA", "projectId": "PJpTANzTXIFibWsW", "project": { "updatedAt": "2026-04-22T14:25:09.686Z", "createdAt": "2026-04-22T14:22:54.790Z", "id": "PJpTANzTXIFibWsW", "name": "Isaac Aracena ", "type": "personal", "icon": null, "description": null, "creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" } } ], "tags": [], "activeVersion": { "updatedAt": "2026-07-21T21:09:53.000Z", "createdAt": "2026-07-21T21:09:51.188Z", "versionId": "afe39c11-be81-4a06-8937-36adaf938182", "workflowId": "KuJTvjNNx9XmoazA", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "cruce-cuentas-gt-marcar-resuelto", "responseMode": "responseNode", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ -560, 32 ], "id": "681b06d3-4b12-441e-b5b2-61b283b8f418", "name": "Webhook Marcar Resuelto", "webhookId": "5b7d6bd6-caa6-47e6-b9b8-982f6e7e0a0f" }, { "parameters": { "jsCode": "const body = $input.first().json.body || $input.first().json || {};\n\nfunction clean(value) {\n return String(value ?? '').replace(/\\s+/g, ' ').trim();\n}\n\nconst reportId = clean(body.reportId || body.id);\nconst comentarioResolucion = clean(body.comentarioResolucion || body.comentario || '');\nconst resueltoPorNombre = clean(body.resueltoPorNombre || body.userName || 'Usuario GLM');\nconst resueltoPorEmail = clean(body.resueltoPorEmail || body.userEmail || '');\n\nconst errors = [];\n\nif (!reportId) {\n errors.push('No se recibió el ID del reporte.');\n}\n\nif (!comentarioResolucion) {\n errors.push('Debe indicar un comentario de resolución.');\n}\n\nif (comentarioResolucion.length < 10) {\n errors.push('El comentario de resolución debe ser más descriptivo.');\n}\n\nif (errors.length > 0) {\n return [\n {\n json: {\n ok: false,\n stage: 'validacion_resolucion',\n errors,\n reportId,\n },\n },\n ];\n}\n\nreturn [\n {\n json: {\n ok: true,\n stage: 'resolucion_validada',\n reportId,\n comentarioResolucion,\n resueltoPorNombre,\n resueltoPorEmail,\n resolvedAt: new Date().toISOString(),\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -352, 32 ], "id": "7df13555-97eb-4c62-be7d-13b12a631cd9", "name": "Validar resolución" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "c89884c6-de96-4b08-bd20-54644bb95bc7", "leftValue": "={{ $json.ok }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ -144, 32 ], "id": "fdaa242f-9e63-4c95-82cd-27f645805f66", "name": "¿Solicitud válida?" }, { "parameters": { "method": "PATCH", "url": "={{ 'https://dbit.digitalcompass.agency/rest/v1/cruces_cuentas_gt_reportes?id=eq.' + $json.reportId }}", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Authorization", "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q" }, { "name": "Content-Type", "value": "application/json" }, { "name": "Prefer", "value": "return=representation" } ] }, "sendBody": true, "specifyBody": "json", "jsonBody": "={{\n {\n estado: 'resuelto',\n resuelto_por_nombre: $json.resueltoPorNombre,\n resuelto_por_email: $json.resueltoPorEmail,\n resuelto_en: $json.resolvedAt,\n comentario_resolucion: $json.comentarioResolucion,\n updated_at: $json.resolvedAt\n }\n}}", "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 192, -80 ], "id": "31b2de6a-89f6-4d69-9712-59e5ee3957e8", "name": "Actualizar reporte Supabase" }, { "parameters": { "jsCode": "const input = $input.first().json || {};\nconst report = Array.isArray(input) ? input[0] : input;\nconst original = $('Validar resolución').first().json || {};\n\nif (!report || !report.id) {\n throw new Error('No se encontró el reporte para preparar la notificación.');\n}\n\nfunction escapeHtml(value) {\n return String(value ?? '')\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction numberValue(value) {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : 0;\n}\n\nconst periodo = report.period_label || 'Período no especificado';\nconst reportUrl = report.report_url || '';\n\nconst discrepancias = numberValue(report.discrepancias);\nconst bancoSinBamboo = numberValue(report.banco_sin_bamboo);\nconst nominaSinBamboo = numberValue(report.nomina_sin_bamboo);\nconst bambooSinNomina = numberValue(report.bamboo_sin_nomina);\n\nconst resolvedAt = original.resolvedAt || new Date().toISOString();\n\nconst resolvedDateLabel = new Date(resolvedAt).toLocaleString('es-GT', {\n timeZone: 'America/Guatemala',\n dateStyle: 'long',\n timeStyle: 'short',\n});\n\nconst resolvedByName =\n original.resueltoPorNombre || 'Usuario GLM';\n\nconst resolvedByEmail =\n original.resueltoPorEmail || '';\n\nconst resolutionComment =\n original.comentarioResolucion || 'Sin comentario registrado.';\n\nconst logoUrl =\n 'https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM_completo.png';\n\nconst subject =\n `Reporte resuelto - Cruce de Cuentas Guatemala - ${periodo}`;\n\nconst textBody = `\nHola Yanelly,\n\nSe marcó como resuelto un reporte de Cruce de Cuentas Guatemala.\n\nPeríodo:\n${periodo}\n\nResuelto por:\n${resolvedByName}${resolvedByEmail ? ` (${resolvedByEmail})` : ''}\n\nFecha/hora:\n${resolvedDateLabel}\n\nResumen:\n- Discrepancias: ${discrepancias}\n- Banco sin Bamboo: ${bancoSinBamboo}\n- Nómina sin Bamboo: ${nominaSinBamboo}\n- Bamboo sin Nómina: ${bambooSinNomina}\n\nComentario:\n${resolutionComment}\n\nGoogle Sheet:\n${reportUrl || 'No disponible'}\n\nSaludos,\nPortal Cruce de Cuentas GLM\n`.trim();\n\nconst htmlBody = `\n\n\n\n \n \n ${escapeHtml(subject)}\n\n\n\n\n \n \n \n\n \n\n \n \n \n \n \n\n \n  \n \n\n \n \n
\n Cruce de Cuentas Guatemala\n
\n\n

\n Reporte marcado como resuelto\n

\n \n \n\n \n \n

\n Hola Yanelly,\n

\n\n

\n El siguiente reporte fue revisado y marcado como\n resuelto.\n

\n \n \n\n \n \n \n \n \n Período\n \n\n \n ${escapeHtml(periodo)}\n \n \n\n \n \n Resuelto por\n \n\n \n ${escapeHtml(resolvedByName)}\n ${\n resolvedByEmail\n ? `
${escapeHtml(resolvedByEmail)}`\n : ''\n }\n \n \n\n \n \n Fecha y hora\n \n\n \n ${escapeHtml(resolvedDateLabel)}\n \n \n \n \n \n\n \n \n
\n Resumen del reporte\n
\n \n \n\n \n \n \n \n \n
\n ${discrepancias}\n
\n\n
\n Discrepancias\n
\n \n\n \n
\n ${bancoSinBamboo}\n
\n\n
\n Banco sin Bamboo\n
\n \n\n \n
\n ${nominaSinBamboo}\n
\n\n
\n Nómina sin Bamboo\n
\n \n\n \n
\n ${bambooSinNomina}\n
\n\n
\n Bamboo sin Nómina\n
\n \n \n \n \n \n\n \n \n
\n Comentario de resolución\n
\n \n \n\n \n \n
\n ${escapeHtml(resolutionComment).replace(/\\n/g, '
')}\n
\n \n \n\n ${\n reportUrl\n ? `\n \n \n \n Abrir Google Sheet\n \n \n \n `\n : ''\n }\n\n \n \n Mensaje automático del Portal Cruce de Cuentas GLM.\n \n \n\n \n \n \n \n\n\n\n`.trim();\n\nreturn [\n {\n json: {\n to: 'ymadera@gomezleemarketing.com, jgomez@gomezleemarketing.com, mgomez@gomezleemarketing.com',\n cc: 'iaracena@gomezleemarketing.com',\n subject,\n textBody,\n htmlBody,\n report,\n resolution: original,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 400, -80 ], "id": "fb3f529f-164b-4987-a2af-e99e8544c9b1", "name": "Preparar correo Yanelly" }, { "parameters": { "sendTo": "={{$json.to}}", "subject": "={{$json.subject}}", "message": "={{ $json.htmlBody }}", "options": { "appendAttribution": false, "ccList": "={{ $json.cc }}" } }, "type": "n8n-nodes-base.gmail", "typeVersion": 2.2, "position": [ 608, -80 ], "id": "86252093-a337-4c18-a733-d7f80a954a1f", "name": "Enviar correo Yanelly", "webhookId": "da3b90f2-2e52-4a99-beea-5bc580486f75", "credentials": { "gmailOAuth2": { "id": "UDcO1FLJqA453V2D", "name": "Gmail account 3" } } }, { "parameters": { "jsCode": "const original = $('Validar resolución').first().json || {};\nconst updatedResponse = $('Actualizar reporte Supabase').first().json || {};\nconst updatedReport = Array.isArray(updatedResponse) ? updatedResponse[0] : updatedResponse;\n\nreturn [\n {\n json: {\n ok: true,\n stage: 'reporte_resuelto',\n message: 'Reporte marcado como resuelto correctamente.',\n report: {\n id: original.reportId,\n estado: 'resuelto',\n estadoLabel: 'RESUELTO',\n resueltoPorNombre: original.resueltoPorNombre,\n resueltoPorEmail: original.resueltoPorEmail,\n resueltoEn: original.resolvedAt,\n comentarioResolucion: original.comentarioResolucion,\n reportUrl: updatedReport.report_url || '',\n },\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 816, -80 ], "id": "43dd0a1d-3829-46a6-bcf2-349ad4f5754a", "name": "Preparar respuesta resuelto" }, { "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": { "responseCode": 200, "responseHeaders": { "entries": [ { "name": "Content-Type", "value": "application/json" }, { "name": "Access-Control-Allow-Origin", "value": "*" }, { "name": "Access-Control-Allow-Methods", "value": "GET,POST,OPTIONS" }, { "name": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" } ] } } }, "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ 1024, -80 ], "id": "4fcda86c-0bd9-4797-a85a-273ce6876ad8", "name": "Responder resuelto app" }, { "parameters": { "respondWith": "json", "responseBody": "={{ $json }}", "options": { "responseCode": 400, "responseHeaders": { "entries": [ { "name": "Content-Type", "value": "application/json" }, { "name": "Access-Control-Allow-Origin", "value": "*" }, { "name": "Access-Control-Allow-Methods", "value": "GET,POST,OPTIONS" }, { "name": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" } ] } } }, "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ 64, 128 ], "id": "15a0d2a5-d5f7-4da0-ba2b-ea08dc77dc23", "name": "Responder error resolución" }, { "parameters": { "content": "# ✅ CIERRE DE REPORTE — GUATEMALA\n\nRecibe desde el Portal de Verificación de Nóminas la solicitud para marcar un reporte de Guatemala como resuelto.\n\n## Validación de la solicitud\n\nEl Webhook recibe la información enviada por la aplicación y verifica que existan los datos necesarios para procesar el cierre, como:\n\n- Identificador del reporte.\n- Usuario que realizó la resolución.\n- Nuevo estado solicitado.\n- Comentario o detalle de la resolución.\n- Información necesaria para localizar el registro en Supabase.\n\nLa solicitud solamente continúa cuando los datos son válidos y suficientes.\n\n## Actualización en Supabase\n\nCuando la solicitud es correcta:\n\n1. Localiza el reporte correspondiente.\n2. Actualiza su estado a RESUELTO.\n3. Registra el usuario responsable del cierre.\n4. Guarda la fecha y hora de resolución.\n5. Conserva el comentario o detalle proporcionado.\n\nSupabase es la fuente oficial del estado mostrado en el Portal de Verificación de Nóminas.\n\n## Notificación a Yanelly\n\nDespués de confirmar la actualización:\n\n1. Se prepara un correo con los datos principales del reporte.\n2. Se informa que el caso de Guatemala fue marcado como resuelto.\n3. Se identifica al usuario que realizó el cierre.\n4. Se incluye el comentario de resolución, cuando esté disponible.\n5. Se envía la notificación a Yanelly mediante Gmail.\n\nEl correo solamente debe enviarse después de comprobar que Supabase fue actualizado correctamente.\n\n## Respuesta a la aplicación\n\nCuando el proceso termina correctamente:\n\n- Se construye una respuesta de éxito.\n- Se confirma el nuevo estado del reporte.\n- Se devuelve el resultado a la aplicación.\n- El portal puede actualizar inmediatamente la interfaz.\n\n## Ruta de error\n\nCuando la solicitud no supera la validación:\n\n- No se modifica el reporte en Supabase.\n- No se envía ningún correo.\n- Se devuelve una respuesta de error a la aplicación.\n- Se informa que la solicitud contiene datos incompletos o inválidos.\n\n## Reglas\n\n- No marcar un reporte como resuelto sin un identificador válido.\n- No enviar el correo antes de confirmar la actualización.\n- No declarar éxito si Supabase devolvió un error.\n- Evitar cambios parciales entre la base de datos y la notificación.\n- Ambas rutas deben responder al Webhook para evitar solicitudes abiertas.\n- La ruta inválida debe finalizar sin modificar información.", "height": 1248, "width": 2624, "color": "#832F2F" }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -1376, -624 ], "id": "b9408dc9-bbab-4585-bf2a-9f88f5fe8eaa", "name": "Sticky Note" } ], "connections": { "Webhook Marcar Resuelto": { "main": [ [ { "node": "Validar resolución", "type": "main", "index": 0 } ] ] }, "Validar resolución": { "main": [ [ { "node": "¿Solicitud válida?", "type": "main", "index": 0 } ] ] }, "¿Solicitud válida?": { "main": [ [ { "node": "Actualizar reporte Supabase", "type": "main", "index": 0 } ], [ { "node": "Responder error resolución", "type": "main", "index": 0 } ] ] }, "Actualizar reporte Supabase": { "main": [ [ { "node": "Preparar correo Yanelly", "type": "main", "index": 0 } ] ] }, "Preparar correo Yanelly": { "main": [ [ { "node": "Enviar correo Yanelly", "type": "main", "index": 0 } ] ] }, "Enviar correo Yanelly": { "main": [ [ { "node": "Preparar respuesta resuelto", "type": "main", "index": 0 } ] ] }, "Preparar respuesta resuelto": { "main": [ [ { "node": "Responder resuelto app", "type": "main", "index": 0 } ] ] } }, "authors": "Isaac Aracena", "name": "Version afe39c11", "description": "", "autosaved": true, "workflowPublishHistory": [ { "createdAt": "2026-07-21T21:09:53.675Z", "id": 3318, "workflowId": "KuJTvjNNx9XmoazA", "versionId": "afe39c11-be81-4a06-8937-36adaf938182", "event": "activated", "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" }, { "createdAt": "2026-07-25T17:28:43.729Z", "id": 3462, "workflowId": "KuJTvjNNx9XmoazA", "versionId": "afe39c11-be81-4a06-8937-36adaf938182", "event": "activated", "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" }, { "createdAt": "2026-07-25T17:28:43.681Z", "id": 3461, "workflowId": "KuJTvjNNx9XmoazA", "versionId": "afe39c11-be81-4a06-8937-36adaf938182", "event": "deactivated", "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" } ] } }