2124 lines
97 KiB
JSON
2124 lines
97 KiB
JSON
{
|
|
"updatedAt": "2026-08-06T17:47:19.077Z",
|
|
"createdAt": "2026-07-29T13:08:05.444Z",
|
|
"id": "RaNkDfE2YCrZgCbx",
|
|
"name": "GLM Hub - Solicitudes de acceso y aprobación",
|
|
"description": null,
|
|
"active": true,
|
|
"isArchived": false,
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "glm-hub-solicitar-acceso",
|
|
"responseMode": "responseNode",
|
|
"options": {
|
|
"allowedOrigins": "*"
|
|
}
|
|
},
|
|
"id": "80c8fbc9-c126-447e-a2b3-b2c9ac6e9468",
|
|
"name": "Webhook - Solicitar acceso",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
-928,
|
|
192
|
|
],
|
|
"webhookId": "ad3e98b9-baf2-400f-90e7-dcd4c5c20457"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $json.body ?? $json;\nlet payload = source;\nif (typeof payload === 'string') {\n try { payload = JSON.parse(payload); } catch { payload = {}; }\n}\nconst accessToken = String(payload?.accessToken ?? '').trim();\nconst appId = String(payload?.appId ?? '').trim();\nconst jwtPattern = /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/;\nconst uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\nlet error = '';\nif (!jwtPattern.test(accessToken)) error = 'La sesión recibida no es válida.';\nelse if (!uuidPattern.test(appId)) error = 'Selecciona una aplicación válida.';\nreturn [{ json: { valid: !error, error, authorization: `Bearer ${accessToken}`, appId } }];"
|
|
},
|
|
"id": "7d5b3911-3db7-4023-a89b-ac690bc99101",
|
|
"name": "Validar solicitud de acceso",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-704,
|
|
192
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "b5933422-7722-4c98-b34b-262a20d3ac05",
|
|
"leftValue": "={{ $json.valid }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "3c1cd649-6e3a-4751-ba1f-e775e8040f1d",
|
|
"name": "¿Solicitud de acceso válida?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
-480,
|
|
192
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: false, error: $json.error } }}",
|
|
"options": {
|
|
"responseCode": 400,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "ff32cfc2-c5ed-4e10-8804-2209cd17ef69",
|
|
"name": "Responder solicitud inválida",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
-160,
|
|
352
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_prepare_access_request",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "apikey",
|
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "={{ $('Validar solicitud de acceso').first().json.authorization }}"
|
|
},
|
|
{
|
|
"name": "Accept",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ {\n p_app_id: $('Validar solicitud de acceso').first().json.appId\n} }}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"neverError": true,
|
|
"responseFormat": "json"
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"id": "260ee2e0-d0d7-4395-a186-b6cec2a766db",
|
|
"name": "Crear solicitud en Supabase",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
304,
|
|
112
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $input.first()?.json ?? {};\nconst parseJson = (value) => {\n if (typeof value !== 'string') return value;\n try { return JSON.parse(value); } catch { return value; }\n};\nlet payload = source.body ?? source.data ?? source.response?.body ?? source;\npayload = parseJson(payload);\nif (Array.isArray(payload)) payload = payload[0] ?? {};\nif (payload && typeof payload === 'object' && payload.body !== undefined && payload.ok === undefined) {\n payload = parseJson(payload.body);\n if (Array.isArray(payload)) payload = payload[0] ?? {};\n}\nif (payload?.ok !== true) {\n return [{ json: {\n created: false,\n publicError: 'La solicitud fue registrada, pero no se pudo enviar el correo. Intenta nuevamente en unos minutos.',\n technicalError: String(payload?.error ?? payload?.message ?? 'No se pudieron emitir los enlaces de aprobación.')\n } }];\n}\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst encode = encodeURIComponent;\nconst confirmBase = 'https://agenteit.digitalcompass.agency/webhook/glm-hub-confirmar-solicitud';\nconst requesterName = String(payload.requesterName ?? 'Usuario GLM');\nconst requesterEmail = String(payload.requesterEmail ?? '');\nconst appName = String(payload.appName ?? 'Aplicación GLM');\nconst appCategory = String(payload.appCategory ?? '');\nconst requestId = String(payload.requestId ?? '');\nconst approvers = Array.isArray(payload.approvers) ? payload.approvers : [];\nif (approvers.length !== 3) {\n return [{ json: {\n created: false,\n publicError: 'No se pudo completar el envío de la solicitud. Intenta nuevamente en unos minutos.',\n technicalError: `Se esperaban 3 aprobadores y se recibieron ${approvers.length}.`\n } }];\n}\nreturn approvers.map((approver) => {\n const approveUrl = `${confirmBase}?token=${encode(String(approver.token ?? ''))}&decision=approved&app=${encode(appName)}`;\n const rejectUrl = `${confirmBase}?token=${encode(String(approver.token ?? ''))}&decision=rejected&app=${encode(appName)}`;\n const html = `<!doctype html><html><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif;color:#4A4A4A\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"background:#F5F5F5\"><tr><td align=\"center\" style=\"padding:32px 16px\"><table role=\"presentation\" width=\"640\" cellspacing=\"0\" cellpadding=\"0\" style=\"width:100%;max-width:640px;background:#FFFFFF;border-collapse:collapse;border-top:4px solid #6CC24A\"><tr><td style=\"padding:28px 34px 16px\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\" style=\"display:block;width:220px;max-width:100%;height:auto\"></td></tr><tr><td style=\"padding:8px 34px 0\"><div style=\"font-size:11px;font-weight:700;letter-spacing:2px;color:#4F758B\">GLM HUB · SOLICITUD DE ACCESO</div><h1 style=\"margin:12px 0 8px;font-size:30px;line-height:1.12;color:#4F758B\">Una persona necesita acceso.</h1><p style=\"margin:0;font-size:15px;line-height:1.6;color:#4A4A4A\">Hola ${escapeHtml(approver.name)}, revisa la siguiente solicitud.</p></td></tr><tr><td style=\"padding:24px 34px\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:collapse;border:1px solid #D0D0D0\"><tr><td style=\"width:34%;padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Solicitante</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(requesterName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Correo</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(requesterEmail)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Aplicación</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px;font-weight:700;color:#4F758B\">${escapeHtml(appName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;font-size:13px;font-weight:700;color:#4F758B\">Área</td><td style=\"padding:12px 14px;font-size:14px\">${escapeHtml(appCategory)}</td></tr></table></td></tr><tr><td style=\"padding:0 34px 30px\"><p style=\"margin:0 0 16px;font-size:13px;line-height:1.55;color:#6B8FA3\">Por seguridad, cada botón abre una pantalla de confirmación. La primera decisión confirmada será definitiva para los tres destinatarios.</p><table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\"><tr><td style=\"padding-right:10px\"><a href=\"${approveUrl}\" style=\"display:inline-block;padding:13px 22px;background:#6CC24A;color:#FFFFFF;text-decoration:none;font-size:14px;font-weight:700;border-radius:3px\">Aceptar solicitud</a></td><td><a href=\"${rejectUrl}\" style=\"display:inline-block;padding:12px 22px;background:#FFFFFF;color:#E87722;text-decoration:none;font-size:14px;font-weight:700;border:1px solid #E87722;border-radius:3px\">Rechazar solicitud</a></td></tr></table></td></tr><tr><td style=\"padding:18px 34px;border-top:1px solid #D0D0D0;font-size:11px;line-height:1.5;color:#6B8FA3\">Solicitud ${escapeHtml(requestId)} · GomezLee Marketing · Uso interno</td></tr></table></td></tr></table></body></html>`;\n return { json: { created: true, requestId, sendTo: String(approver.email ?? ''), subject: `Solicitud de acceso | ${appName} | ${requesterName}`, html } };\n});"
|
|
},
|
|
"id": "c4bd59ac-3233-4d09-81af-17f08b9325f5",
|
|
"name": "Preparar correos de aprobación",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1440,
|
|
64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "02a636e0-63a0-4a56-b108-65029e555646",
|
|
"leftValue": "={{ $json.created }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "eae9bf96-c598-42e0-b4df-c58319e3c354",
|
|
"name": "¿Solicitud registrada?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
1680,
|
|
64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"sendTo": "={{ $json.sendTo }}",
|
|
"subject": "={{ $json.subject }}",
|
|
"message": "={{ $json.html }}",
|
|
"options": {
|
|
"appendAttribution": false,
|
|
"senderName": "GLM Hub"
|
|
}
|
|
},
|
|
"id": "f3f7f82d-d96e-499f-a2d6-9e26f5496bb1",
|
|
"name": "Enviar solicitud a aprobadores",
|
|
"type": "n8n-nodes-base.gmail",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
1920,
|
|
-32
|
|
],
|
|
"webhookId": "7dc65e7b-4fb6-4a2f-b685-34171dd9ce1a",
|
|
"credentials": {
|
|
"gmailOAuth2": {
|
|
"id": "UDcO1FLJqA453V2D",
|
|
"name": "Gmail account 3"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: true, message: 'La solicitud fue enviada a Isaac Aracena, José Leopoldo Gómez y Máximo Gómez.', requestId: $('Preparar correos de aprobación').first().json.requestId } }}",
|
|
"options": {
|
|
"responseCode": 200,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "af925eb2-9fb8-4261-8652-4e162969e512",
|
|
"name": "Responder solicitud enviada",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
2160,
|
|
-32
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ {\n ok: false,\n error: $json.publicError || 'No pudimos enviar la solicitud. Intenta nuevamente.'\n} }}",
|
|
"options": {
|
|
"responseCode": 409,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "0a104b37-5336-44ce-953b-979ce5faaa87",
|
|
"name": "Responder error al registrar",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
1920,
|
|
176
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"path": "glm-hub-confirmar-solicitud",
|
|
"responseMode": "responseNode",
|
|
"options": {}
|
|
},
|
|
"id": "569dfd6e-b94f-4db8-8c88-4a890cff1ea3",
|
|
"name": "Webhook - Confirmar decisión",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
432,
|
|
896
|
|
],
|
|
"webhookId": "cba1c38c-37bc-4acb-bd61-48035604a5c4"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const query = $json.query ?? {};\nconst token = String(query.token ?? '').trim();\nconst decision = String(query.decision ?? '').trim().toLowerCase();\nconst appName = String(query.app ?? 'aplicación GLM').trim();\nconst valid = /^[0-9a-f]{64}$/i.test(token) && ['approved','rejected'].includes(decision);\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst actionUrl = 'https://agenteit.digitalcompass.agency/webhook/glm-hub-resolver-solicitud';\nconst approved = decision === 'approved';\nconst title = approved ? 'Confirmar aprobación' : 'Confirmar rechazo';\nconst actionLabel = approved ? 'Sí, aprobar solicitud' : 'Sí, rechazar solicitud';\nconst actionColor = approved ? '#6CC24A' : '#E87722';\nconst html = valid\n ? `<!doctype html><html lang=\"es\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>${title} · GLM Hub</title></head><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif;color:#4A4A4A\"><main style=\"min-height:100vh;display:grid;place-items:center;padding:24px\"><section style=\"width:100%;max-width:560px;background:#FFFFFF;border-top:4px solid #6CC24A;padding:36px;box-sizing:border-box;box-shadow:0 24px 70px rgba(0,0,0,.14)\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\" style=\"display:block;width:220px;max-width:100%;height:auto\"><p style=\"margin:28px 0 8px;font-size:11px;font-weight:700;letter-spacing:2px;color:#4F758B\">GLM HUB · DECISIÓN DE ACCESO</p><h1 style=\"margin:0;color:#4F758B;font-size:34px;line-height:1.1\">${title}</h1><p style=\"margin:16px 0 24px;font-size:15px;line-height:1.6\">Vas a ${approved ? 'aprobar' : 'rechazar'} la solicitud de acceso a <strong>${escapeHtml(appName)}</strong>. La decisión será definitiva y los otros enlaces quedarán sin efecto.</p><form method=\"post\" action=\"${actionUrl}\"><input type=\"hidden\" name=\"token\" value=\"${escapeHtml(token)}\"><input type=\"hidden\" name=\"decision\" value=\"${escapeHtml(decision)}\"><button type=\"submit\" style=\"width:100%;min-height:50px;border:0;border-radius:3px;background:${actionColor};color:#FFFFFF;font-family:Arial,sans-serif;font-size:15px;font-weight:700;cursor:pointer\">${actionLabel}</button></form><p style=\"margin:18px 0 0;font-size:12px;line-height:1.5;color:#6B8FA3\">Cierra esta pestaña para cancelar sin registrar ninguna decisión.</p></section></main></body></html>`\n : `<!doctype html><html lang=\"es\"><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif\"><main style=\"min-height:100vh;display:grid;place-items:center;padding:24px\"><section style=\"max-width:520px;background:#FFFFFF;border-top:4px solid #E87722;padding:36px\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\"><h1 style=\"color:#4F758B\">Enlace no válido</h1><p style=\"color:#4A4A4A;line-height:1.6\">El enlace está incompleto o no corresponde a una solicitud de GLM Hub.</p></section></main></body></html>`;\nreturn [{ json: { html } }];"
|
|
},
|
|
"id": "66f986d2-7c8b-4c25-9a5c-4310f5c37d3a",
|
|
"name": "Construir página de confirmación",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
656,
|
|
896
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "text",
|
|
"responseBody": "={{ $json.html }}",
|
|
"options": {
|
|
"responseCode": 200,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "text/html; charset=utf-8"
|
|
},
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
},
|
|
{
|
|
"name": "X-Frame-Options",
|
|
"value": "DENY"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "abba0ac3-e1fc-4c2a-a6fc-ecba5e924cdf",
|
|
"name": "Mostrar confirmación",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
880,
|
|
896
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "glm-hub-resolver-solicitud",
|
|
"responseMode": "responseNode",
|
|
"options": {}
|
|
},
|
|
"id": "9baccfdb-9561-4e85-bf43-7993eb6bc65d",
|
|
"name": "Webhook - Resolver solicitud",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
-544,
|
|
1584
|
|
],
|
|
"webhookId": "54eeee1b-b24b-46ae-84dc-70c4103e3a3a"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $json.body ?? $json;\nlet payload = source;\nif (typeof payload === 'string') {\n try {\n payload = Object.fromEntries(new URLSearchParams(payload));\n } catch { payload = {}; }\n}\nconst token = String(payload?.token ?? '').trim();\nconst decision = String(payload?.decision ?? '').trim().toLowerCase();\nlet error = '';\nif (!/^[0-9a-f]{64}$/i.test(token)) error = 'El enlace de decisión no es válido.';\nelse if (!['approved','rejected'].includes(decision)) error = 'La decisión recibida no es válida.';\nreturn [{ json: { valid: !error, error, token, decision } }];"
|
|
},
|
|
"id": "0760c143-2039-428e-85d2-f9fdec1ed4f7",
|
|
"name": "Validar decisión",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-320,
|
|
1584
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "5c42c35a-903b-4a18-a7ef-aab6e6bb5c26",
|
|
"leftValue": "={{ $json.valid }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "dfacee52-d608-4086-b287-560ce870c980",
|
|
"name": "¿Decisión válida?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
-96,
|
|
1584
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const message = String($json.error ?? 'El enlace de decisión no es válido.');\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst html = `<!doctype html><html lang=\"es\"><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif\"><main style=\"min-height:100vh;display:grid;place-items:center;padding:24px\"><section style=\"max-width:520px;background:#FFFFFF;border-top:4px solid #E87722;padding:36px\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\"><h1 style=\"color:#4F758B\">No se pudo procesar</h1><p style=\"color:#4A4A4A;line-height:1.6\">${escapeHtml(message)}</p></section></main></body></html>`;\nreturn [{ json: { html } }];"
|
|
},
|
|
"id": "8bebdbfd-3ee5-4a87-a0d4-c0b3a9bd17d4",
|
|
"name": "Preparar decisión inválida",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
128,
|
|
1776
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "text",
|
|
"responseBody": "={{ $json.html }}",
|
|
"options": {
|
|
"responseCode": 400,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "text/html; charset=utf-8"
|
|
},
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
},
|
|
{
|
|
"name": "X-Frame-Options",
|
|
"value": "DENY"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "bdaa54d2-e20d-4523-963b-1cc88f4d8669",
|
|
"name": "Responder decisión inválida",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
368,
|
|
1776
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_decide_access_request",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "apikey",
|
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Accept",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ {\n p_token: $('Validar decisión').first().json.token,\n p_decision: $('Validar decisión').first().json.decision\n} }}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"neverError": true,
|
|
"responseFormat": "json"
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"id": "df9c350b-10f7-494a-8c5e-e0354c1606db",
|
|
"name": "Registrar decisión en Supabase",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
128,
|
|
1504
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $input.first()?.json ?? {};\nlet payload = source.body ?? source;\nif (typeof payload === 'string') { try { payload = JSON.parse(payload); } catch { payload = {}; } }\nif (Array.isArray(payload)) payload = payload[0] ?? {};\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst status = String(payload.status ?? '');\nconst approved = status === 'approved';\nconst statusLabel = approved ? 'APROBADA' : status === 'rejected' ? 'RECHAZADA' : 'SIN PROCESAR';\nconst statusColor = approved ? '#6CC24A' : '#E87722';\nconst ok = payload.ok === true;\nconst alreadyDecided = payload.alreadyDecided === true;\nconst requesterName = String(payload.requesterName ?? '');\nconst requesterEmail = String(payload.requesterEmail ?? '');\nconst appName = String(payload.appName ?? 'Aplicación GLM');\nconst decidedByName = String(payload.decidedByName ?? '');\nconst decidedByEmail = String(payload.decidedByEmail ?? '');\nconst responseTitle = ok ? (approved ? 'Solicitud aprobada' : 'Solicitud rechazada') : alreadyDecided ? 'Solicitud ya atendida' : 'No se pudo procesar';\nconst responseMessage = ok\n ? `La decisión fue registrada. IT Support recibirá los datos para continuar con el acceso.`\n : alreadyDecided\n ? `Esta solicitud ya fue ${status === 'approved' ? 'aprobada' : 'rechazada'} por ${escapeHtml(decidedByName || decidedByEmail || 'otra persona')}. No se realizó ningún cambio adicional.`\n : escapeHtml(payload.error ?? 'El enlace no pudo ser validado.');\nconst responseHtml = `<!doctype html><html lang=\"es\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>${responseTitle} · GLM Hub</title></head><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif;color:#4A4A4A\"><main style=\"min-height:100vh;display:grid;place-items:center;padding:24px\"><section style=\"width:100%;max-width:560px;background:#FFFFFF;border-top:4px solid ${ok ? statusColor : '#4F758B'};padding:36px;box-sizing:border-box;box-shadow:0 24px 70px rgba(0,0,0,.14)\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\" style=\"display:block;width:220px;max-width:100%;height:auto\"><p style=\"margin:28px 0 8px;font-size:11px;font-weight:700;letter-spacing:2px;color:#4F758B\">GLM HUB · SOLICITUD DE ACCESO</p><h1 style=\"margin:0;color:#4F758B;font-size:34px;line-height:1.1\">${responseTitle}</h1><p style=\"margin:16px 0 0;font-size:15px;line-height:1.65\">${responseMessage}</p><p style=\"margin:24px 0 0;font-size:12px;color:#6B8FA3\">Ya puedes cerrar esta pestaña.</p></section></main></body></html>`;\nconst itHtml = ok ? `<!doctype html><html><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif;color:#4A4A4A\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"background:#F5F5F5\"><tr><td align=\"center\" style=\"padding:32px 16px\"><table role=\"presentation\" width=\"680\" cellspacing=\"0\" cellpadding=\"0\" style=\"width:100%;max-width:680px;background:#FFFFFF;border-collapse:collapse;border-top:4px solid ${statusColor}\"><tr><td style=\"padding:28px 34px 16px\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\" style=\"display:block;width:220px;max-width:100%;height:auto\"></td></tr><tr><td style=\"padding:8px 34px 0\"><div style=\"font-size:11px;font-weight:700;letter-spacing:2px;color:#4F758B\">GLM HUB · DECISIÓN REGISTRADA</div><h1 style=\"margin:12px 0 8px;font-size:30px;line-height:1.12;color:#4F758B\">Solicitud ${statusLabel.toLowerCase()}.</h1><p style=\"margin:0;font-size:15px;line-height:1.6\">IT Support debe continuar según la decisión indicada.</p></td></tr><tr><td style=\"padding:24px 34px\"><div style=\"display:inline-block;margin-bottom:16px;padding:7px 12px;border:1px solid ${statusColor};color:${statusColor};font-size:12px;font-weight:700;letter-spacing:1px\">${statusLabel}</div><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:collapse;border:1px solid #D0D0D0\"><tr><td style=\"width:38%;padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Persona solicitante</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(requesterName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Correo solicitante</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(requesterEmail)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Aplicación</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px;font-weight:700;color:#4F758B\">${escapeHtml(appName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Decisión</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px;font-weight:700;color:${statusColor}\">${statusLabel}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Decidido por</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(decidedByName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;font-size:13px;font-weight:700;color:#4F758B\">Correo de quien decidió</td><td style=\"padding:12px 14px;font-size:14px\">${escapeHtml(decidedByEmail)}</td></tr></table></td></tr><tr><td style=\"padding:18px 34px;border-top:1px solid #D0D0D0;font-size:11px;line-height:1.5;color:#6B8FA3\">GomezLee Marketing · GLM Hub · Uso interno</td></tr></table></td></tr></table></body></html>` : '';\nreturn [{ json: { notifyIt: ok, responseHtml, itHtml, subject: `Acceso ${approved ? 'aprobado' : 'rechazado'} | ${appName} | ${requesterName}` } }];"
|
|
},
|
|
"id": "72ee2fcd-bf95-4542-aad1-90ca4d83a2e7",
|
|
"name": "Preparar resultado de decisión",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1344,
|
|
1520
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "2ef854a5-d270-49bf-9a6a-5bb92aeab434",
|
|
"leftValue": "={{ $json.notifyIt }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "9f4e6707-3cb1-4c0e-833c-05be87602e07",
|
|
"name": "¿Notificar a IT Support?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
1584,
|
|
1520
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"sendTo": "itsupport@gomezleemarketing.com",
|
|
"subject": "={{ $json.subject }}",
|
|
"message": "={{ $json.itHtml }}",
|
|
"options": {
|
|
"appendAttribution": false,
|
|
"senderName": "GLM Hub"
|
|
}
|
|
},
|
|
"id": "56d73834-fc03-43c7-9844-dd4e691439b2",
|
|
"name": "Enviar decisión a IT Support",
|
|
"type": "n8n-nodes-base.gmail",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
1824,
|
|
1424
|
|
],
|
|
"webhookId": "234c8e3b-b904-41b8-8a74-9ca835f759df",
|
|
"credentials": {
|
|
"gmailOAuth2": {
|
|
"id": "UDcO1FLJqA453V2D",
|
|
"name": "Gmail account 3"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "text",
|
|
"responseBody": "={{ $('Preparar resultado de decisión').first().json.responseHtml }}",
|
|
"options": {
|
|
"responseCode": 200,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "text/html; charset=utf-8"
|
|
},
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
},
|
|
{
|
|
"name": "X-Frame-Options",
|
|
"value": "DENY"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "e1d4a5ba-34fe-479a-a670-e07ebd87d0cf",
|
|
"name": "Mostrar resultado final",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
2064,
|
|
1520
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $input.first()?.json ?? {};\n\nconst parseJson = (value) => {\n if (typeof value !== 'string') {\n return value;\n }\n\n try {\n return JSON.parse(value);\n } catch {\n return {};\n }\n};\n\nlet payload = source;\n\n// Compatibilidad por si n8n todavía entrega algún envoltorio.\nif (source.body !== undefined) {\n payload = source.body;\n} else if (source.data !== undefined) {\n payload = source.data;\n}\n\npayload = parseJson(payload);\n\nif (Array.isArray(payload)) {\n payload = payload[0] ?? {};\n}\n\nconst registered = payload?.ok === true;\nconst code = String(payload?.code ?? '').toUpperCase();\nconst internalMessage = String(\n payload?.error ??\n payload?.message ??\n ''\n).trim();\n\nconst normalizedMessage = internalMessage.toLowerCase();\n\nlet publicError = 'No pudimos enviar la solicitud. Intenta nuevamente.';\n\nif (\n code === 'PENDING_EXISTS' ||\n normalizedMessage.includes('solicitud pendiente')\n) {\n publicError = 'Ya tienes una solicitud pendiente para esta aplicación.';\n} else if (\n normalizedMessage.includes('sesión') ||\n normalizedMessage.includes('jwt')\n) {\n publicError = 'Tu sesión venció. Cierra sesión e inicia nuevamente.';\n} else if (\n normalizedMessage.includes('administradores no necesitan')\n) {\n publicError =\n 'Tu cuenta administrativa no necesita solicitar acceso desde el Hub.';\n} else if (\n normalizedMessage.includes('no está autorizado')\n) {\n publicError =\n 'Tu cuenta no está habilitada para enviar solicitudes de acceso.';\n} else if (\n normalizedMessage.includes('ya no está publicada')\n) {\n publicError =\n 'La aplicación seleccionada ya no está disponible.';\n}\n\nreturn [\n {\n json: {\n registered,\n publicError,\n internalMessage,\n requestId: String(payload?.requestId ?? ''),\n requesterName: String(payload?.requesterName ?? ''),\n requesterEmail: String(payload?.requesterEmail ?? ''),\n appName: String(payload?.appName ?? ''),\n appCategory: String(payload?.appCategory ?? '')\n }\n }\n];"
|
|
},
|
|
"id": "e2cc29db-5db2-46ea-a388-054c320ecfc2",
|
|
"name": "Confirmar registro de solicitud",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
512,
|
|
112
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "aa091e1c-09f8-4cdb-b8ac-3cbc4a7a33c8",
|
|
"leftValue": "={{ $json.registered }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "7f6803be-bc3a-45e1-b1a4-c04c33ca25b9",
|
|
"name": "¿Solicitud registrada en Supabase?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
736,
|
|
112
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_issue_access_request_tokens",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "apikey",
|
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Accept",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ {\n p_request_id: $('Confirmar registro de solicitud').first().json.requestId\n} }}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"neverError": true,
|
|
"responseFormat": "json"
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"id": "d684c268-bbd8-4a3b-8aab-b21510423c95",
|
|
"name": "Emitir enlaces de aprobación",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
1200,
|
|
64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 1. RECEPCIÓN Y VALIDACIÓN\n\nEste bloque recibe desde GLM Hub una nueva solicitud de acceso y valida que la información obligatoria sea correcta.\n\n- Verifica los datos del usuario y de la aplicación solicitada.\n- Rechaza inmediatamente solicitudes incompletas o inválidas.\n- Solo las solicitudes válidas continúan hacia el registro en Supabase.",
|
|
"height": 560,
|
|
"width": 1104,
|
|
"color": 6
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1024,
|
|
-16
|
|
],
|
|
"id": "55489511-4594-4d60-8b68-87f0210e4b9e",
|
|
"name": "Sticky Note"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 2. REGISTRO DE LA SOLICITUD\n\nGuarda la solicitud de acceso en Supabase y confirma que el registro se haya creado correctamente.\n\n- Almacena la información de la solicitud y su estado inicial.\n- Recupera y normaliza los datos devueltos por Supabase.\n- Solo permite continuar cuando existe una confirmación válida del registro.",
|
|
"height": 384,
|
|
"width": 736,
|
|
"color": 5
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
192,
|
|
-64
|
|
],
|
|
"id": "729f2bb0-c1f2-424f-ae39-bf69ea94317c",
|
|
"name": "Sticky Note1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 3. ENVÍO A LOS APROBADORES\n\nGenera los enlaces de decisión, prepara los correos y notifica a las personas responsables de aprobar o rechazar la solicitud.\n\n- Construye enlaces únicos para la aprobación y el rechazo.\n- Genera el contenido personalizado de los correos.\n- Envía la solicitud a los aprobadores mediante Gmail.\n- Confirma al GLM Hub que la solicitud fue enviada correctamente.\n- Devuelve un error controlado si no fue posible completar el registro.",
|
|
"height": 560,
|
|
"width": 1264,
|
|
"color": "#357C27"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1104,
|
|
-224
|
|
],
|
|
"id": "32a9a9ad-bd7c-4d67-93db-d6537fd3a978",
|
|
"name": "Sticky Note2"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 4. CONFIRMACIÓN DE LA DECISIÓN\n\nRecibe al aprobador desde el enlace enviado por correo y muestra una página previa antes de registrar la decisión definitiva.\n\n- Lee los parámetros incluidos en el enlace de aprobación o rechazo.\n- Construye una página con los datos de la solicitud.\n- Solicita una confirmación final para evitar decisiones accidentales.",
|
|
"height": 352,
|
|
"width": 1062,
|
|
"color": "#8F622D"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
192,
|
|
720
|
|
],
|
|
"id": "305bb9cd-16f5-4f87-a3b4-f6009a683085",
|
|
"name": "Sticky Note3"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 5. VALIDACIÓN Y REGISTRO DE LA DECISIÓN\n\nValida la decisión confirmada por el aprobador y actualiza la solicitud correspondiente en Supabase.\n\n- Comprueba que la solicitud, el enlace y la decisión sean válidos.\n- Registra en Supabase la aprobación o el rechazo.\n- Impide procesar decisiones incorrectas, incompletas o manipuladas.\n- Muestra una respuesta controlada cuando la decisión no puede ser aceptada.",
|
|
"height": 624,
|
|
"width": 1184,
|
|
"color": "#52500F"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-624,
|
|
1328
|
|
],
|
|
"id": "0289d494-ec4a-4f09-89ed-02006e0fcc4e",
|
|
"name": "Sticky Note4"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 6. NOTIFICACIÓN Y RESULTADO FINAL\n\nPrepara el resultado de la decisión, determina si se necesita intervención de IT Support y finaliza el proceso.\n\n- Construye el mensaje final de aprobación o rechazo.\n- Evalúa si la decisión debe notificarse al equipo de IT.\n- Envía por Gmail la información necesaria cuando corresponde.\n- Muestra al aprobador el resultado definitivo de la operación.",
|
|
"height": 432,
|
|
"width": 1104,
|
|
"color": "#276D4F"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1184,
|
|
1264
|
|
],
|
|
"id": "7881e095-8b12-4b96-83fd-a174d8dd0703",
|
|
"name": "Sticky Note5"
|
|
}
|
|
],
|
|
"connections": {
|
|
"Webhook - Solicitar acceso": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Validar solicitud de acceso",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Validar solicitud de acceso": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Solicitud de acceso válida?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Solicitud de acceso válida?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Crear solicitud en Supabase",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder solicitud inválida",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Crear solicitud en Supabase": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Confirmar registro de solicitud",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar correos de aprobación": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Solicitud registrada?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Solicitud registrada?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Enviar solicitud a aprobadores",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder error al registrar",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Enviar solicitud a aprobadores": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Responder solicitud enviada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Webhook - Confirmar decisión": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Construir página de confirmación",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Construir página de confirmación": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Mostrar confirmación",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Webhook - Resolver solicitud": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Validar decisión",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Validar decisión": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Decisión válida?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Decisión válida?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Registrar decisión en Supabase",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Preparar decisión inválida",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar decisión inválida": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Responder decisión inválida",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Registrar decisión en Supabase": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar resultado de decisión",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar resultado de decisión": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Notificar a IT Support?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Notificar a IT Support?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Enviar decisión a IT Support",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Mostrar resultado final",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Enviar decisión a IT Support": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Mostrar resultado final",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Confirmar registro de solicitud": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Solicitud registrada en Supabase?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Solicitud registrada en Supabase?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Emitir enlaces de aprobación",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder error al registrar",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Emitir enlaces de aprobación": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar correos de aprobación",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"binaryMode": "separate",
|
|
"availableInMCP": true
|
|
},
|
|
"staticData": null,
|
|
"meta": {
|
|
"templateCredsSetupCompleted": true
|
|
},
|
|
"versionId": "dc3ed690-5728-414e-a322-137e35e8f9b6",
|
|
"activeVersionId": "dc3ed690-5728-414e-a322-137e35e8f9b6",
|
|
"versionCounter": 137,
|
|
"triggerCount": 3,
|
|
"shared": [
|
|
{
|
|
"updatedAt": "2026-07-29T13:08:05.454Z",
|
|
"createdAt": "2026-07-29T13:08:05.454Z",
|
|
"role": "workflow:owner",
|
|
"workflowId": "RaNkDfE2YCrZgCbx",
|
|
"projectId": "PJpTANzTXIFibWsW",
|
|
"project": {
|
|
"updatedAt": "2026-04-22T14:25:09.686Z",
|
|
"createdAt": "2026-04-22T14:22:54.790Z",
|
|
"id": "PJpTANzTXIFibWsW",
|
|
"name": "Isaac Aracena <iaracena@gomezleemarketing.com>",
|
|
"type": "personal",
|
|
"icon": null,
|
|
"description": null,
|
|
"creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
|
|
}
|
|
}
|
|
],
|
|
"tags": [],
|
|
"activeVersion": {
|
|
"updatedAt": "2026-08-06T17:47:21.000Z",
|
|
"createdAt": "2026-08-06T17:47:19.081Z",
|
|
"versionId": "dc3ed690-5728-414e-a322-137e35e8f9b6",
|
|
"workflowId": "RaNkDfE2YCrZgCbx",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "glm-hub-solicitar-acceso",
|
|
"responseMode": "responseNode",
|
|
"options": {
|
|
"allowedOrigins": "*"
|
|
}
|
|
},
|
|
"id": "80c8fbc9-c126-447e-a2b3-b2c9ac6e9468",
|
|
"name": "Webhook - Solicitar acceso",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
-928,
|
|
192
|
|
],
|
|
"webhookId": "ad3e98b9-baf2-400f-90e7-dcd4c5c20457"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $json.body ?? $json;\nlet payload = source;\nif (typeof payload === 'string') {\n try { payload = JSON.parse(payload); } catch { payload = {}; }\n}\nconst accessToken = String(payload?.accessToken ?? '').trim();\nconst appId = String(payload?.appId ?? '').trim();\nconst jwtPattern = /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/;\nconst uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\nlet error = '';\nif (!jwtPattern.test(accessToken)) error = 'La sesión recibida no es válida.';\nelse if (!uuidPattern.test(appId)) error = 'Selecciona una aplicación válida.';\nreturn [{ json: { valid: !error, error, authorization: `Bearer ${accessToken}`, appId } }];"
|
|
},
|
|
"id": "7d5b3911-3db7-4023-a89b-ac690bc99101",
|
|
"name": "Validar solicitud de acceso",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-704,
|
|
192
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "b5933422-7722-4c98-b34b-262a20d3ac05",
|
|
"leftValue": "={{ $json.valid }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "3c1cd649-6e3a-4751-ba1f-e775e8040f1d",
|
|
"name": "¿Solicitud de acceso válida?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
-480,
|
|
192
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: false, error: $json.error } }}",
|
|
"options": {
|
|
"responseCode": 400,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "ff32cfc2-c5ed-4e10-8804-2209cd17ef69",
|
|
"name": "Responder solicitud inválida",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
-160,
|
|
352
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_prepare_access_request",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "apikey",
|
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "={{ $('Validar solicitud de acceso').first().json.authorization }}"
|
|
},
|
|
{
|
|
"name": "Accept",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ {\n p_app_id: $('Validar solicitud de acceso').first().json.appId\n} }}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"neverError": true,
|
|
"responseFormat": "json"
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"id": "260ee2e0-d0d7-4395-a186-b6cec2a766db",
|
|
"name": "Crear solicitud en Supabase",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
304,
|
|
112
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $input.first()?.json ?? {};\nconst parseJson = (value) => {\n if (typeof value !== 'string') return value;\n try { return JSON.parse(value); } catch { return value; }\n};\nlet payload = source.body ?? source.data ?? source.response?.body ?? source;\npayload = parseJson(payload);\nif (Array.isArray(payload)) payload = payload[0] ?? {};\nif (payload && typeof payload === 'object' && payload.body !== undefined && payload.ok === undefined) {\n payload = parseJson(payload.body);\n if (Array.isArray(payload)) payload = payload[0] ?? {};\n}\nif (payload?.ok !== true) {\n return [{ json: {\n created: false,\n publicError: 'La solicitud fue registrada, pero no se pudo enviar el correo. Intenta nuevamente en unos minutos.',\n technicalError: String(payload?.error ?? payload?.message ?? 'No se pudieron emitir los enlaces de aprobación.')\n } }];\n}\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst encode = encodeURIComponent;\nconst confirmBase = 'https://agenteit.digitalcompass.agency/webhook/glm-hub-confirmar-solicitud';\nconst requesterName = String(payload.requesterName ?? 'Usuario GLM');\nconst requesterEmail = String(payload.requesterEmail ?? '');\nconst appName = String(payload.appName ?? 'Aplicación GLM');\nconst appCategory = String(payload.appCategory ?? '');\nconst requestId = String(payload.requestId ?? '');\nconst approvers = Array.isArray(payload.approvers) ? payload.approvers : [];\nif (approvers.length !== 3) {\n return [{ json: {\n created: false,\n publicError: 'No se pudo completar el envío de la solicitud. Intenta nuevamente en unos minutos.',\n technicalError: `Se esperaban 3 aprobadores y se recibieron ${approvers.length}.`\n } }];\n}\nreturn approvers.map((approver) => {\n const approveUrl = `${confirmBase}?token=${encode(String(approver.token ?? ''))}&decision=approved&app=${encode(appName)}`;\n const rejectUrl = `${confirmBase}?token=${encode(String(approver.token ?? ''))}&decision=rejected&app=${encode(appName)}`;\n const html = `<!doctype html><html><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif;color:#4A4A4A\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"background:#F5F5F5\"><tr><td align=\"center\" style=\"padding:32px 16px\"><table role=\"presentation\" width=\"640\" cellspacing=\"0\" cellpadding=\"0\" style=\"width:100%;max-width:640px;background:#FFFFFF;border-collapse:collapse;border-top:4px solid #6CC24A\"><tr><td style=\"padding:28px 34px 16px\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\" style=\"display:block;width:220px;max-width:100%;height:auto\"></td></tr><tr><td style=\"padding:8px 34px 0\"><div style=\"font-size:11px;font-weight:700;letter-spacing:2px;color:#4F758B\">GLM HUB · SOLICITUD DE ACCESO</div><h1 style=\"margin:12px 0 8px;font-size:30px;line-height:1.12;color:#4F758B\">Una persona necesita acceso.</h1><p style=\"margin:0;font-size:15px;line-height:1.6;color:#4A4A4A\">Hola ${escapeHtml(approver.name)}, revisa la siguiente solicitud.</p></td></tr><tr><td style=\"padding:24px 34px\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:collapse;border:1px solid #D0D0D0\"><tr><td style=\"width:34%;padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Solicitante</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(requesterName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Correo</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(requesterEmail)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Aplicación</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px;font-weight:700;color:#4F758B\">${escapeHtml(appName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;font-size:13px;font-weight:700;color:#4F758B\">Área</td><td style=\"padding:12px 14px;font-size:14px\">${escapeHtml(appCategory)}</td></tr></table></td></tr><tr><td style=\"padding:0 34px 30px\"><p style=\"margin:0 0 16px;font-size:13px;line-height:1.55;color:#6B8FA3\">Por seguridad, cada botón abre una pantalla de confirmación. La primera decisión confirmada será definitiva para los tres destinatarios.</p><table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\"><tr><td style=\"padding-right:10px\"><a href=\"${approveUrl}\" style=\"display:inline-block;padding:13px 22px;background:#6CC24A;color:#FFFFFF;text-decoration:none;font-size:14px;font-weight:700;border-radius:3px\">Aceptar solicitud</a></td><td><a href=\"${rejectUrl}\" style=\"display:inline-block;padding:12px 22px;background:#FFFFFF;color:#E87722;text-decoration:none;font-size:14px;font-weight:700;border:1px solid #E87722;border-radius:3px\">Rechazar solicitud</a></td></tr></table></td></tr><tr><td style=\"padding:18px 34px;border-top:1px solid #D0D0D0;font-size:11px;line-height:1.5;color:#6B8FA3\">Solicitud ${escapeHtml(requestId)} · GomezLee Marketing · Uso interno</td></tr></table></td></tr></table></body></html>`;\n return { json: { created: true, requestId, sendTo: String(approver.email ?? ''), subject: `Solicitud de acceso | ${appName} | ${requesterName}`, html } };\n});"
|
|
},
|
|
"id": "c4bd59ac-3233-4d09-81af-17f08b9325f5",
|
|
"name": "Preparar correos de aprobación",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1440,
|
|
64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "02a636e0-63a0-4a56-b108-65029e555646",
|
|
"leftValue": "={{ $json.created }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "eae9bf96-c598-42e0-b4df-c58319e3c354",
|
|
"name": "¿Solicitud registrada?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
1680,
|
|
64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"sendTo": "={{ $json.sendTo }}",
|
|
"subject": "={{ $json.subject }}",
|
|
"message": "={{ $json.html }}",
|
|
"options": {
|
|
"appendAttribution": false,
|
|
"senderName": "GLM Hub"
|
|
}
|
|
},
|
|
"id": "f3f7f82d-d96e-499f-a2d6-9e26f5496bb1",
|
|
"name": "Enviar solicitud a aprobadores",
|
|
"type": "n8n-nodes-base.gmail",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
1920,
|
|
-32
|
|
],
|
|
"webhookId": "7dc65e7b-4fb6-4a2f-b685-34171dd9ce1a",
|
|
"credentials": {
|
|
"gmailOAuth2": {
|
|
"id": "UDcO1FLJqA453V2D",
|
|
"name": "Gmail account 3"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: true, message: 'La solicitud fue enviada a Isaac Aracena, José Leopoldo Gómez y Máximo Gómez.', requestId: $('Preparar correos de aprobación').first().json.requestId } }}",
|
|
"options": {
|
|
"responseCode": 200,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "af925eb2-9fb8-4261-8652-4e162969e512",
|
|
"name": "Responder solicitud enviada",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
2160,
|
|
-32
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ {\n ok: false,\n error: $json.publicError || 'No pudimos enviar la solicitud. Intenta nuevamente.'\n} }}",
|
|
"options": {
|
|
"responseCode": 409,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "0a104b37-5336-44ce-953b-979ce5faaa87",
|
|
"name": "Responder error al registrar",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
1920,
|
|
176
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"path": "glm-hub-confirmar-solicitud",
|
|
"responseMode": "responseNode",
|
|
"options": {}
|
|
},
|
|
"id": "569dfd6e-b94f-4db8-8c88-4a890cff1ea3",
|
|
"name": "Webhook - Confirmar decisión",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
432,
|
|
896
|
|
],
|
|
"webhookId": "cba1c38c-37bc-4acb-bd61-48035604a5c4"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const query = $json.query ?? {};\nconst token = String(query.token ?? '').trim();\nconst decision = String(query.decision ?? '').trim().toLowerCase();\nconst appName = String(query.app ?? 'aplicación GLM').trim();\nconst valid = /^[0-9a-f]{64}$/i.test(token) && ['approved','rejected'].includes(decision);\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst actionUrl = 'https://agenteit.digitalcompass.agency/webhook/glm-hub-resolver-solicitud';\nconst approved = decision === 'approved';\nconst title = approved ? 'Confirmar aprobación' : 'Confirmar rechazo';\nconst actionLabel = approved ? 'Sí, aprobar solicitud' : 'Sí, rechazar solicitud';\nconst actionColor = approved ? '#6CC24A' : '#E87722';\nconst html = valid\n ? `<!doctype html><html lang=\"es\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>${title} · GLM Hub</title></head><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif;color:#4A4A4A\"><main style=\"min-height:100vh;display:grid;place-items:center;padding:24px\"><section style=\"width:100%;max-width:560px;background:#FFFFFF;border-top:4px solid #6CC24A;padding:36px;box-sizing:border-box;box-shadow:0 24px 70px rgba(0,0,0,.14)\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\" style=\"display:block;width:220px;max-width:100%;height:auto\"><p style=\"margin:28px 0 8px;font-size:11px;font-weight:700;letter-spacing:2px;color:#4F758B\">GLM HUB · DECISIÓN DE ACCESO</p><h1 style=\"margin:0;color:#4F758B;font-size:34px;line-height:1.1\">${title}</h1><p style=\"margin:16px 0 24px;font-size:15px;line-height:1.6\">Vas a ${approved ? 'aprobar' : 'rechazar'} la solicitud de acceso a <strong>${escapeHtml(appName)}</strong>. La decisión será definitiva y los otros enlaces quedarán sin efecto.</p><form method=\"post\" action=\"${actionUrl}\"><input type=\"hidden\" name=\"token\" value=\"${escapeHtml(token)}\"><input type=\"hidden\" name=\"decision\" value=\"${escapeHtml(decision)}\"><button type=\"submit\" style=\"width:100%;min-height:50px;border:0;border-radius:3px;background:${actionColor};color:#FFFFFF;font-family:Arial,sans-serif;font-size:15px;font-weight:700;cursor:pointer\">${actionLabel}</button></form><p style=\"margin:18px 0 0;font-size:12px;line-height:1.5;color:#6B8FA3\">Cierra esta pestaña para cancelar sin registrar ninguna decisión.</p></section></main></body></html>`\n : `<!doctype html><html lang=\"es\"><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif\"><main style=\"min-height:100vh;display:grid;place-items:center;padding:24px\"><section style=\"max-width:520px;background:#FFFFFF;border-top:4px solid #E87722;padding:36px\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\"><h1 style=\"color:#4F758B\">Enlace no válido</h1><p style=\"color:#4A4A4A;line-height:1.6\">El enlace está incompleto o no corresponde a una solicitud de GLM Hub.</p></section></main></body></html>`;\nreturn [{ json: { html } }];"
|
|
},
|
|
"id": "66f986d2-7c8b-4c25-9a5c-4310f5c37d3a",
|
|
"name": "Construir página de confirmación",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
656,
|
|
896
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "text",
|
|
"responseBody": "={{ $json.html }}",
|
|
"options": {
|
|
"responseCode": 200,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "text/html; charset=utf-8"
|
|
},
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
},
|
|
{
|
|
"name": "X-Frame-Options",
|
|
"value": "DENY"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "abba0ac3-e1fc-4c2a-a6fc-ecba5e924cdf",
|
|
"name": "Mostrar confirmación",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
880,
|
|
896
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "glm-hub-resolver-solicitud",
|
|
"responseMode": "responseNode",
|
|
"options": {}
|
|
},
|
|
"id": "9baccfdb-9561-4e85-bf43-7993eb6bc65d",
|
|
"name": "Webhook - Resolver solicitud",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
-544,
|
|
1584
|
|
],
|
|
"webhookId": "54eeee1b-b24b-46ae-84dc-70c4103e3a3a"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $json.body ?? $json;\nlet payload = source;\nif (typeof payload === 'string') {\n try {\n payload = Object.fromEntries(new URLSearchParams(payload));\n } catch { payload = {}; }\n}\nconst token = String(payload?.token ?? '').trim();\nconst decision = String(payload?.decision ?? '').trim().toLowerCase();\nlet error = '';\nif (!/^[0-9a-f]{64}$/i.test(token)) error = 'El enlace de decisión no es válido.';\nelse if (!['approved','rejected'].includes(decision)) error = 'La decisión recibida no es válida.';\nreturn [{ json: { valid: !error, error, token, decision } }];"
|
|
},
|
|
"id": "0760c143-2039-428e-85d2-f9fdec1ed4f7",
|
|
"name": "Validar decisión",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-320,
|
|
1584
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "5c42c35a-903b-4a18-a7ef-aab6e6bb5c26",
|
|
"leftValue": "={{ $json.valid }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "dfacee52-d608-4086-b287-560ce870c980",
|
|
"name": "¿Decisión válida?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
-96,
|
|
1584
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const message = String($json.error ?? 'El enlace de decisión no es válido.');\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst html = `<!doctype html><html lang=\"es\"><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif\"><main style=\"min-height:100vh;display:grid;place-items:center;padding:24px\"><section style=\"max-width:520px;background:#FFFFFF;border-top:4px solid #E87722;padding:36px\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\"><h1 style=\"color:#4F758B\">No se pudo procesar</h1><p style=\"color:#4A4A4A;line-height:1.6\">${escapeHtml(message)}</p></section></main></body></html>`;\nreturn [{ json: { html } }];"
|
|
},
|
|
"id": "8bebdbfd-3ee5-4a87-a0d4-c0b3a9bd17d4",
|
|
"name": "Preparar decisión inválida",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
128,
|
|
1776
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "text",
|
|
"responseBody": "={{ $json.html }}",
|
|
"options": {
|
|
"responseCode": 400,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "text/html; charset=utf-8"
|
|
},
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
},
|
|
{
|
|
"name": "X-Frame-Options",
|
|
"value": "DENY"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "bdaa54d2-e20d-4523-963b-1cc88f4d8669",
|
|
"name": "Responder decisión inválida",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
368,
|
|
1776
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_decide_access_request",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "apikey",
|
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Accept",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ {\n p_token: $('Validar decisión').first().json.token,\n p_decision: $('Validar decisión').first().json.decision\n} }}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"neverError": true,
|
|
"responseFormat": "json"
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"id": "df9c350b-10f7-494a-8c5e-e0354c1606db",
|
|
"name": "Registrar decisión en Supabase",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
128,
|
|
1504
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $input.first()?.json ?? {};\nlet payload = source.body ?? source;\nif (typeof payload === 'string') { try { payload = JSON.parse(payload); } catch { payload = {}; } }\nif (Array.isArray(payload)) payload = payload[0] ?? {};\nconst escapeHtml = (value) => String(value ?? '').replace(/[&<>\"']/g, (c) => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));\nconst status = String(payload.status ?? '');\nconst approved = status === 'approved';\nconst statusLabel = approved ? 'APROBADA' : status === 'rejected' ? 'RECHAZADA' : 'SIN PROCESAR';\nconst statusColor = approved ? '#6CC24A' : '#E87722';\nconst ok = payload.ok === true;\nconst alreadyDecided = payload.alreadyDecided === true;\nconst requesterName = String(payload.requesterName ?? '');\nconst requesterEmail = String(payload.requesterEmail ?? '');\nconst appName = String(payload.appName ?? 'Aplicación GLM');\nconst decidedByName = String(payload.decidedByName ?? '');\nconst decidedByEmail = String(payload.decidedByEmail ?? '');\nconst responseTitle = ok ? (approved ? 'Solicitud aprobada' : 'Solicitud rechazada') : alreadyDecided ? 'Solicitud ya atendida' : 'No se pudo procesar';\nconst responseMessage = ok\n ? `La decisión fue registrada. IT Support recibirá los datos para continuar con el acceso.`\n : alreadyDecided\n ? `Esta solicitud ya fue ${status === 'approved' ? 'aprobada' : 'rechazada'} por ${escapeHtml(decidedByName || decidedByEmail || 'otra persona')}. No se realizó ningún cambio adicional.`\n : escapeHtml(payload.error ?? 'El enlace no pudo ser validado.');\nconst responseHtml = `<!doctype html><html lang=\"es\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>${responseTitle} · GLM Hub</title></head><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif;color:#4A4A4A\"><main style=\"min-height:100vh;display:grid;place-items:center;padding:24px\"><section style=\"width:100%;max-width:560px;background:#FFFFFF;border-top:4px solid ${ok ? statusColor : '#4F758B'};padding:36px;box-sizing:border-box;box-shadow:0 24px 70px rgba(0,0,0,.14)\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\" style=\"display:block;width:220px;max-width:100%;height:auto\"><p style=\"margin:28px 0 8px;font-size:11px;font-weight:700;letter-spacing:2px;color:#4F758B\">GLM HUB · SOLICITUD DE ACCESO</p><h1 style=\"margin:0;color:#4F758B;font-size:34px;line-height:1.1\">${responseTitle}</h1><p style=\"margin:16px 0 0;font-size:15px;line-height:1.65\">${responseMessage}</p><p style=\"margin:24px 0 0;font-size:12px;color:#6B8FA3\">Ya puedes cerrar esta pestaña.</p></section></main></body></html>`;\nconst itHtml = ok ? `<!doctype html><html><body style=\"margin:0;background:#F5F5F5;font-family:Arial,sans-serif;color:#4A4A4A\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"background:#F5F5F5\"><tr><td align=\"center\" style=\"padding:32px 16px\"><table role=\"presentation\" width=\"680\" cellspacing=\"0\" cellpadding=\"0\" style=\"width:100%;max-width:680px;background:#FFFFFF;border-collapse:collapse;border-top:4px solid ${statusColor}\"><tr><td style=\"padding:28px 34px 16px\"><img src=\"https://dbit.digitalcompass.agency/storage/v1/object/public/public-assets/GLM.png\" width=\"220\" alt=\"GomezLee Marketing\" style=\"display:block;width:220px;max-width:100%;height:auto\"></td></tr><tr><td style=\"padding:8px 34px 0\"><div style=\"font-size:11px;font-weight:700;letter-spacing:2px;color:#4F758B\">GLM HUB · DECISIÓN REGISTRADA</div><h1 style=\"margin:12px 0 8px;font-size:30px;line-height:1.12;color:#4F758B\">Solicitud ${statusLabel.toLowerCase()}.</h1><p style=\"margin:0;font-size:15px;line-height:1.6\">IT Support debe continuar según la decisión indicada.</p></td></tr><tr><td style=\"padding:24px 34px\"><div style=\"display:inline-block;margin-bottom:16px;padding:7px 12px;border:1px solid ${statusColor};color:${statusColor};font-size:12px;font-weight:700;letter-spacing:1px\">${statusLabel}</div><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"border-collapse:collapse;border:1px solid #D0D0D0\"><tr><td style=\"width:38%;padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Persona solicitante</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(requesterName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Correo solicitante</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(requesterEmail)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Aplicación</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px;font-weight:700;color:#4F758B\">${escapeHtml(appName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Decisión</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px;font-weight:700;color:${statusColor}\">${statusLabel}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;border-bottom:1px solid #D0D0D0;font-size:13px;font-weight:700;color:#4F758B\">Decidido por</td><td style=\"padding:12px 14px;border-bottom:1px solid #D0D0D0;font-size:14px\">${escapeHtml(decidedByName)}</td></tr><tr><td style=\"padding:12px 14px;background:#EEF6E8;font-size:13px;font-weight:700;color:#4F758B\">Correo de quien decidió</td><td style=\"padding:12px 14px;font-size:14px\">${escapeHtml(decidedByEmail)}</td></tr></table></td></tr><tr><td style=\"padding:18px 34px;border-top:1px solid #D0D0D0;font-size:11px;line-height:1.5;color:#6B8FA3\">GomezLee Marketing · GLM Hub · Uso interno</td></tr></table></td></tr></table></body></html>` : '';\nreturn [{ json: { notifyIt: ok, responseHtml, itHtml, subject: `Acceso ${approved ? 'aprobado' : 'rechazado'} | ${appName} | ${requesterName}` } }];"
|
|
},
|
|
"id": "72ee2fcd-bf95-4542-aad1-90ca4d83a2e7",
|
|
"name": "Preparar resultado de decisión",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1344,
|
|
1520
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "2ef854a5-d270-49bf-9a6a-5bb92aeab434",
|
|
"leftValue": "={{ $json.notifyIt }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "9f4e6707-3cb1-4c0e-833c-05be87602e07",
|
|
"name": "¿Notificar a IT Support?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
1584,
|
|
1520
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"sendTo": "itsupport@gomezleemarketing.com",
|
|
"subject": "={{ $json.subject }}",
|
|
"message": "={{ $json.itHtml }}",
|
|
"options": {
|
|
"appendAttribution": false,
|
|
"senderName": "GLM Hub"
|
|
}
|
|
},
|
|
"id": "56d73834-fc03-43c7-9844-dd4e691439b2",
|
|
"name": "Enviar decisión a IT Support",
|
|
"type": "n8n-nodes-base.gmail",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
1824,
|
|
1424
|
|
],
|
|
"webhookId": "234c8e3b-b904-41b8-8a74-9ca835f759df",
|
|
"credentials": {
|
|
"gmailOAuth2": {
|
|
"id": "UDcO1FLJqA453V2D",
|
|
"name": "Gmail account 3"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "text",
|
|
"responseBody": "={{ $('Preparar resultado de decisión').first().json.responseHtml }}",
|
|
"options": {
|
|
"responseCode": 200,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "text/html; charset=utf-8"
|
|
},
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
},
|
|
{
|
|
"name": "X-Frame-Options",
|
|
"value": "DENY"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "e1d4a5ba-34fe-479a-a670-e07ebd87d0cf",
|
|
"name": "Mostrar resultado final",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
2064,
|
|
1520
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $input.first()?.json ?? {};\n\nconst parseJson = (value) => {\n if (typeof value !== 'string') {\n return value;\n }\n\n try {\n return JSON.parse(value);\n } catch {\n return {};\n }\n};\n\nlet payload = source;\n\n// Compatibilidad por si n8n todavía entrega algún envoltorio.\nif (source.body !== undefined) {\n payload = source.body;\n} else if (source.data !== undefined) {\n payload = source.data;\n}\n\npayload = parseJson(payload);\n\nif (Array.isArray(payload)) {\n payload = payload[0] ?? {};\n}\n\nconst registered = payload?.ok === true;\nconst code = String(payload?.code ?? '').toUpperCase();\nconst internalMessage = String(\n payload?.error ??\n payload?.message ??\n ''\n).trim();\n\nconst normalizedMessage = internalMessage.toLowerCase();\n\nlet publicError = 'No pudimos enviar la solicitud. Intenta nuevamente.';\n\nif (\n code === 'PENDING_EXISTS' ||\n normalizedMessage.includes('solicitud pendiente')\n) {\n publicError = 'Ya tienes una solicitud pendiente para esta aplicación.';\n} else if (\n normalizedMessage.includes('sesión') ||\n normalizedMessage.includes('jwt')\n) {\n publicError = 'Tu sesión venció. Cierra sesión e inicia nuevamente.';\n} else if (\n normalizedMessage.includes('administradores no necesitan')\n) {\n publicError =\n 'Tu cuenta administrativa no necesita solicitar acceso desde el Hub.';\n} else if (\n normalizedMessage.includes('no está autorizado')\n) {\n publicError =\n 'Tu cuenta no está habilitada para enviar solicitudes de acceso.';\n} else if (\n normalizedMessage.includes('ya no está publicada')\n) {\n publicError =\n 'La aplicación seleccionada ya no está disponible.';\n}\n\nreturn [\n {\n json: {\n registered,\n publicError,\n internalMessage,\n requestId: String(payload?.requestId ?? ''),\n requesterName: String(payload?.requesterName ?? ''),\n requesterEmail: String(payload?.requesterEmail ?? ''),\n appName: String(payload?.appName ?? ''),\n appCategory: String(payload?.appCategory ?? '')\n }\n }\n];"
|
|
},
|
|
"id": "e2cc29db-5db2-46ea-a388-054c320ecfc2",
|
|
"name": "Confirmar registro de solicitud",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
512,
|
|
112
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "aa091e1c-09f8-4cdb-b8ac-3cbc4a7a33c8",
|
|
"leftValue": "={{ $json.registered }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "7f6803be-bc3a-45e1-b1a4-c04c33ca25b9",
|
|
"name": "¿Solicitud registrada en Supabase?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
736,
|
|
112
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_issue_access_request_tokens",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "apikey",
|
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Accept",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ {\n p_request_id: $('Confirmar registro de solicitud').first().json.requestId\n} }}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"neverError": true,
|
|
"responseFormat": "json"
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"id": "d684c268-bbd8-4a3b-8aab-b21510423c95",
|
|
"name": "Emitir enlaces de aprobación",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
1200,
|
|
64
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 1. RECEPCIÓN Y VALIDACIÓN\n\nEste bloque recibe desde GLM Hub una nueva solicitud de acceso y valida que la información obligatoria sea correcta.\n\n- Verifica los datos del usuario y de la aplicación solicitada.\n- Rechaza inmediatamente solicitudes incompletas o inválidas.\n- Solo las solicitudes válidas continúan hacia el registro en Supabase.",
|
|
"height": 560,
|
|
"width": 1104,
|
|
"color": 6
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1024,
|
|
-16
|
|
],
|
|
"id": "55489511-4594-4d60-8b68-87f0210e4b9e",
|
|
"name": "Sticky Note"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 2. REGISTRO DE LA SOLICITUD\n\nGuarda la solicitud de acceso en Supabase y confirma que el registro se haya creado correctamente.\n\n- Almacena la información de la solicitud y su estado inicial.\n- Recupera y normaliza los datos devueltos por Supabase.\n- Solo permite continuar cuando existe una confirmación válida del registro.",
|
|
"height": 384,
|
|
"width": 736,
|
|
"color": 5
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
192,
|
|
-64
|
|
],
|
|
"id": "729f2bb0-c1f2-424f-ae39-bf69ea94317c",
|
|
"name": "Sticky Note1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 3. ENVÍO A LOS APROBADORES\n\nGenera los enlaces de decisión, prepara los correos y notifica a las personas responsables de aprobar o rechazar la solicitud.\n\n- Construye enlaces únicos para la aprobación y el rechazo.\n- Genera el contenido personalizado de los correos.\n- Envía la solicitud a los aprobadores mediante Gmail.\n- Confirma al GLM Hub que la solicitud fue enviada correctamente.\n- Devuelve un error controlado si no fue posible completar el registro.",
|
|
"height": 560,
|
|
"width": 1264,
|
|
"color": "#357C27"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1104,
|
|
-224
|
|
],
|
|
"id": "32a9a9ad-bd7c-4d67-93db-d6537fd3a978",
|
|
"name": "Sticky Note2"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 4. CONFIRMACIÓN DE LA DECISIÓN\n\nRecibe al aprobador desde el enlace enviado por correo y muestra una página previa antes de registrar la decisión definitiva.\n\n- Lee los parámetros incluidos en el enlace de aprobación o rechazo.\n- Construye una página con los datos de la solicitud.\n- Solicita una confirmación final para evitar decisiones accidentales.",
|
|
"height": 352,
|
|
"width": 1062,
|
|
"color": "#8F622D"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
192,
|
|
720
|
|
],
|
|
"id": "305bb9cd-16f5-4f87-a3b4-f6009a683085",
|
|
"name": "Sticky Note3"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 5. VALIDACIÓN Y REGISTRO DE LA DECISIÓN\n\nValida la decisión confirmada por el aprobador y actualiza la solicitud correspondiente en Supabase.\n\n- Comprueba que la solicitud, el enlace y la decisión sean válidos.\n- Registra en Supabase la aprobación o el rechazo.\n- Impide procesar decisiones incorrectas, incompletas o manipuladas.\n- Muestra una respuesta controlada cuando la decisión no puede ser aceptada.",
|
|
"height": 624,
|
|
"width": 1184,
|
|
"color": "#52500F"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-624,
|
|
1328
|
|
],
|
|
"id": "0289d494-ec4a-4f09-89ed-02006e0fcc4e",
|
|
"name": "Sticky Note4"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 6. NOTIFICACIÓN Y RESULTADO FINAL\n\nPrepara el resultado de la decisión, determina si se necesita intervención de IT Support y finaliza el proceso.\n\n- Construye el mensaje final de aprobación o rechazo.\n- Evalúa si la decisión debe notificarse al equipo de IT.\n- Envía por Gmail la información necesaria cuando corresponde.\n- Muestra al aprobador el resultado definitivo de la operación.",
|
|
"height": 432,
|
|
"width": 1104,
|
|
"color": "#276D4F"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1184,
|
|
1264
|
|
],
|
|
"id": "7881e095-8b12-4b96-83fd-a174d8dd0703",
|
|
"name": "Sticky Note5"
|
|
}
|
|
],
|
|
"connections": {
|
|
"Webhook - Solicitar acceso": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Validar solicitud de acceso",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Validar solicitud de acceso": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Solicitud de acceso válida?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Solicitud de acceso válida?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Crear solicitud en Supabase",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder solicitud inválida",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Crear solicitud en Supabase": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Confirmar registro de solicitud",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar correos de aprobación": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Solicitud registrada?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Solicitud registrada?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Enviar solicitud a aprobadores",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder error al registrar",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Enviar solicitud a aprobadores": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Responder solicitud enviada",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Webhook - Confirmar decisión": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Construir página de confirmación",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Construir página de confirmación": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Mostrar confirmación",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Webhook - Resolver solicitud": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Validar decisión",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Validar decisión": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Decisión válida?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Decisión válida?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Registrar decisión en Supabase",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Preparar decisión inválida",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar decisión inválida": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Responder decisión inválida",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Registrar decisión en Supabase": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar resultado de decisión",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar resultado de decisión": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Notificar a IT Support?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Notificar a IT Support?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Enviar decisión a IT Support",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Mostrar resultado final",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Enviar decisión a IT Support": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Mostrar resultado final",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Confirmar registro de solicitud": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Solicitud registrada en Supabase?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Solicitud registrada en Supabase?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Emitir enlaces de aprobación",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder error al registrar",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Emitir enlaces de aprobación": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar correos de aprobación",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"authors": "Isaac Aracena",
|
|
"name": "Version dc3ed690",
|
|
"description": "",
|
|
"autosaved": false,
|
|
"workflowPublishHistory": [
|
|
{
|
|
"createdAt": "2026-08-06T17:47:21.133Z",
|
|
"id": 4229,
|
|
"workflowId": "RaNkDfE2YCrZgCbx",
|
|
"versionId": "dc3ed690-5728-414e-a322-137e35e8f9b6",
|
|
"event": "activated",
|
|
"userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
|
|
}
|
|
]
|
|
}
|
|
} |