1118 lines
39 KiB
JSON
1118 lines
39 KiB
JSON
{
|
|
"updatedAt": "2026-08-03T13:45:01.318Z",
|
|
"createdAt": "2026-07-28T16:18:07.824Z",
|
|
"id": "F2rMlVS9mBypNNn7",
|
|
"name": "GLM Hub - Generar ícono con Gemini",
|
|
"description": null,
|
|
"active": true,
|
|
"isArchived": false,
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "glm-hub-generar-icono",
|
|
"responseMode": "responseNode",
|
|
"options": {
|
|
"allowedOrigins": "*"
|
|
}
|
|
},
|
|
"id": "e9002643-523d-436d-8c57-3d37f80f7ec3",
|
|
"name": "Webhook - Generar ícono",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
-3712,
|
|
464
|
|
],
|
|
"webhookId": "bc20a398-5ecf-42a2-9d17-9396aa278708"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const request = $input.first().json ?? {};\nconst rawBody = request.body ?? request;\nlet body = rawBody;\n\nif (typeof rawBody === 'string') {\n try {\n body = JSON.parse(rawBody);\n } catch {\n body = {};\n }\n}\n\nconst headers = request.headers ?? {};\nconst headerAuthorization = String(headers.authorization ?? headers.Authorization ?? '').trim();\nconst accessToken = String(body.accessToken ?? '').trim();\nconst authorization = headerAuthorization.toLowerCase().startsWith('bearer ')\n ? headerAuthorization\n : accessToken\n ? `Bearer ${accessToken}`\n : '';\nconst name = String(body.name ?? '').trim();\nconst description = String(body.description ?? '').trim();\n\nlet error = '';\nif (!authorization.toLowerCase().startsWith('bearer ')) {\n error = 'La sesión de Supabase no fue enviada o no es válida.';\n} else if (name.length < 2 || name.length > 60) {\n error = 'El nombre debe contener entre 2 y 60 caracteres.';\n} else if (!description || description.length > 180) {\n error = 'La descripción debe contener entre 1 y 180 caracteres.';\n}\n\nreturn [{ json: { valid: !error, error, authorization, name, description } }];"
|
|
},
|
|
"id": "f7b0a22e-2651-4479-86c8-25262faa38c4",
|
|
"name": "Validar solicitud",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-3456,
|
|
464
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "308c498d-7c3f-4414-a21f-571276aee28b",
|
|
"leftValue": "={{ $json.valid }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "0313d3ca-0439-4c2d-bd34-4da5ef6c3950",
|
|
"name": "¿Solicitud válida?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
-3232,
|
|
464
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: false, error: $json.error } }}",
|
|
"options": {
|
|
"responseCode": 400,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "bcfcc3ea-bbb3-4346-a5b4-1681d226a40c",
|
|
"name": "Responder 400",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
-2928,
|
|
576
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_icon_generation_access",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "apikey",
|
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "={{ $('Validar solicitud').first().json.authorization }}"
|
|
},
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "{}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"fullResponse": true,
|
|
"neverError": true,
|
|
"responseFormat": "json"
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"id": "bf1ac078-9362-445b-99d4-cd884ba5a8e1",
|
|
"name": "Validar administrador en Supabase",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
-2272,
|
|
384
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $input.first()?.json ?? {};\nconst request = $('Validar solicitud').first().json ?? {};\n\nconst statusCode = Number(source.statusCode ?? 200);\n\nlet permission = source.body ?? source;\n\nif (typeof permission === 'string') {\n try {\n permission = JSON.parse(permission);\n } catch {\n permission = {};\n }\n}\n\nif (Array.isArray(permission)) {\n permission = permission[0] ?? {};\n}\n\nif (\n permission &&\n typeof permission === 'object' &&\n permission.authorized === undefined &&\n permission.body !== undefined\n) {\n permission = permission.body;\n\n if (typeof permission === 'string') {\n try {\n permission = JSON.parse(permission);\n } catch {\n permission = {};\n }\n }\n}\n\nconst authorized =\n statusCode >= 200 &&\n statusCode < 300 &&\n (\n permission?.authorized === true ||\n String(permission?.authorized ?? '').toLowerCase() === 'true'\n );\n\nlet error = '';\n\nif (statusCode === 401) {\n error =\n 'La sesión de Supabase expiró. Cierra sesión e inicia nuevamente.';\n} else if (statusCode === 403) {\n error =\n 'Supabase rechazó la validación administrativa.';\n} else if (statusCode >= 400) {\n error =\n String(permission?.message ?? permission?.error ?? '') ||\n `Supabase respondió con el código ${statusCode}.`;\n} else if (!authorized) {\n error =\n 'Tu sesión no tiene permisos de administrador para generar íconos.';\n}\n\nreturn [\n {\n json: {\n authorized,\n error,\n name: String(request.name ?? ''),\n description: String(request.description ?? ''),\n email: String(permission?.email ?? ''),\n userId: String(permission?.userId ?? ''),\n supabaseStatus: statusCode\n }\n }\n];"
|
|
},
|
|
"id": "0f6af309-436b-49df-8a1b-2120a0290474",
|
|
"name": "Confirmar autorización",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-2048,
|
|
384
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "c9275da4-0bca-4ce9-bd75-cc7d06e4cf56",
|
|
"leftValue": "={{ $json.authorized }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "c115098c-9289-46ad-902b-d79aa8168bc0",
|
|
"name": "¿Es administrador?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
-1840,
|
|
384
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: false, error: $json.error } }}",
|
|
"options": {
|
|
"responseCode": 403,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "c44dee89-da47-43d0-9134-a723f80f7e39",
|
|
"name": "Responder 403",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
-1600,
|
|
560
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const { name, description } = $input.first().json;\nconst prompt = `Crea un ícono cuadrado 1:1, profesional, minimalista y fácil de reconocer para una aplicación interna de GomezLee Marketing.\n\nNombre de la aplicación: ${name}\nDescripción funcional: ${description}\n\nRequisitos visuales obligatorios:\n- Representa la función principal con un único símbolo simple, claro y reconocible.\n- Estética corporativa moderna, limpia y tecnológica.\n- Paleta inspirada en GLM: azul grisáceo, verde claro y verde azulado.\n- Fondo blanco o transparente.\n- Composición centrada, con buen espacio de respiración.\n- Sin texto, sin letras, sin siglas, sin números, sin marcas comerciales y sin logotipos existentes.\n- Sin mockups, sin fotografías, sin personas, sin sombras exageradas y sin fondos complejos.\n- Debe seguir siendo legible al reducirse a 32 x 32 píxeles.\n- Entrega solamente el ícono final.`;\nreturn [{ json: { prompt } }];"
|
|
},
|
|
"id": "4a092f12-4966-4d3e-ba45-f82626f8aff8",
|
|
"name": "Construir prompt del ícono",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-864,
|
|
352
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "image",
|
|
"modelId": {
|
|
"__rl": true,
|
|
"value": "models/gemini-3-pro-image",
|
|
"mode": "list",
|
|
"cachedResultName": "models/gemini-3-pro-image (Nano Banana Pro)"
|
|
},
|
|
"prompt": "={{ $json.prompt }}",
|
|
"options": {
|
|
"binaryPropertyOutput": "data"
|
|
}
|
|
},
|
|
"id": "0daf0a06-a280-4500-95f6-f76b8945ed63",
|
|
"name": "Generate an image",
|
|
"type": "@n8n/n8n-nodes-langchain.googleGemini",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
-96,
|
|
384
|
|
],
|
|
"credentials": {
|
|
"googlePalmApi": {
|
|
"id": "jvsXYwL6IOoY2DBU",
|
|
"name": "Isaac - Gemini Api Pago"
|
|
}
|
|
},
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "binary",
|
|
"responseDataSource": "set",
|
|
"options": {
|
|
"responseCode": 200,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
},
|
|
{
|
|
"name": "Content-Disposition",
|
|
"value": "inline; filename=\"glm-hub-icon.png\""
|
|
},
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "image/png"
|
|
},
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "8594ec32-5d88-4435-aa8f-7d95d819eada",
|
|
"name": "Responder con ícono",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
192,
|
|
304
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const input = $input.first().json ?? {};\nconst rawError = input.error ?? input.message ?? input.description ?? input;\nlet message = 'Gemini no pudo generar el ícono. Revisa la credencial, el modelo y la cuota disponible.';\n\nif (typeof rawError === 'string' && rawError.trim()) {\n message = rawError.trim();\n} else if (rawError && typeof rawError === 'object') {\n message = String(rawError.message ?? rawError.description ?? rawError.status ?? message);\n}\n\nreturn [{ json: { ok: false, error: message } }];"
|
|
},
|
|
"id": "ef98ab6b-3341-441b-83bc-41eabfe1d839",
|
|
"name": "Preparar error de Gemini",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
768,
|
|
464
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: false, error: $json.error } }}",
|
|
"options": {
|
|
"responseCode": 502,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "273ca42a-ef45-426d-a5c1-e4a3f9ea704d",
|
|
"name": "Responder error de Gemini",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
1008,
|
|
464
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 1. RECEPCIÓN Y VALIDACIÓN\n\nRecibe desde GLM Hub la solicitud para generar el icono y valida que la información obligatoria esté completa.\n\n- Comprueba los datos de la aplicación y la descripción del icono.\n- Normaliza la información recibida antes de continuar.\n- Detiene el proceso cuando faltan datos o la solicitud es incorrecta.\n- Devuelve una respuesta HTTP 400 para solicitudes inválidas.",
|
|
"height": 512,
|
|
"width": 1104,
|
|
"color": 2
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-3824,
|
|
240
|
|
],
|
|
"id": "0df68d74-d3c2-4257-be79-7a91077c3a07",
|
|
"name": "Sticky Note"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 2. AUTORIZACIÓN DEL USUARIO\n\nConsulta Supabase para comprobar que la persona que solicita la generación del icono tenga permisos de administrador.\n\n- Busca el perfil y los permisos del usuario autenticado.\n- Normaliza el resultado devuelto por Supabase.\n- Permite continuar únicamente a administradores autorizados.\n- Devuelve una respuesta HTTP 403 cuando el usuario no tiene permisos.",
|
|
"height": 544,
|
|
"width": 976,
|
|
"color": "#891F3A"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-2368,
|
|
176
|
|
],
|
|
"id": "6133d6fd-9c82-40d2-8343-ceb670ec9410",
|
|
"name": "Sticky Note1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 3. PREPARACIÓN DEL PROMPT\n\nConstruye las instrucciones que se enviarán a Gemini para generar un icono adecuado para la aplicación.\n\n- Utiliza el nombre, la descripción y el propósito de la aplicación.\n- Define el estilo visual requerido para los iconos de GLM Hub.\n- Evita textos, marcas de agua y elementos innecesarios.\n- Prepara un prompt claro y consistente para obtener mejores resultados.",
|
|
"height": 384,
|
|
"width": 832,
|
|
"color": 7
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1216,
|
|
160
|
|
],
|
|
"id": "cf146dba-5bed-4158-83bc-b55c268b1837",
|
|
"name": "Sticky Note2"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 4. GENERACIÓN DEL ICONO\n\nEnvía el prompt a Gemini, genera la imagen y devuelve el icono al GLM Hub cuando el proceso finaliza correctamente.\n\n- Solicita a Gemini la creación del icono.\n- Recibe la imagen generada por el modelo.\n- Devuelve el resultado mediante la respuesta del webhook.\n- Permite que el GLM Hub utilice el icono generado en la aplicación.",
|
|
"height": 528,
|
|
"width": 592,
|
|
"color": 3
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-192,
|
|
80
|
|
],
|
|
"id": "2fc45b39-21af-4151-90c8-0299f6a5a527",
|
|
"name": "Sticky Note3"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 5. CONTROL DE ERRORES DE GEMINI\n\nGestiona los fallos que puedan producirse durante la generación de la imagen y devuelve una respuesta controlada al GLM Hub.\n\n- Captura errores técnicos o respuestas fallidas de Gemini.\n- Prepara un mensaje comprensible para el usuario.\n- Evita que el workflow termine sin una respuesta.\n- Informa que el icono no pudo generarse para que pueda intentarse nuevamente.",
|
|
"height": 384,
|
|
"width": 704
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
544,
|
|
256
|
|
],
|
|
"id": "2caf2891-ccdf-434b-b773-5a4dca43e4a4",
|
|
"name": "Sticky Note4"
|
|
}
|
|
],
|
|
"connections": {
|
|
"Webhook - Generar ícono": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Validar solicitud",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Validar solicitud": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Solicitud válida?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Solicitud válida?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Validar administrador en Supabase",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder 400",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Validar administrador en Supabase": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Confirmar autorización",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Confirmar autorización": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Es administrador?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Es administrador?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Construir prompt del ícono",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder 403",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Construir prompt del ícono": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Generate an image",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Generate an image": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Responder con ícono",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Preparar error de Gemini",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar error de Gemini": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Responder error de Gemini",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"binaryMode": "separate",
|
|
"availableInMCP": true
|
|
},
|
|
"staticData": null,
|
|
"meta": {
|
|
"templateCredsSetupCompleted": true
|
|
},
|
|
"versionId": "b743c332-a154-44c3-a9c5-6c433e352520",
|
|
"activeVersionId": "b743c332-a154-44c3-a9c5-6c433e352520",
|
|
"versionCounter": 115,
|
|
"triggerCount": 1,
|
|
"shared": [
|
|
{
|
|
"updatedAt": "2026-07-28T16:18:07.831Z",
|
|
"createdAt": "2026-07-28T16:18:07.831Z",
|
|
"role": "workflow:owner",
|
|
"workflowId": "F2rMlVS9mBypNNn7",
|
|
"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-03T13:45:11.000Z",
|
|
"createdAt": "2026-08-03T13:45:01.320Z",
|
|
"versionId": "b743c332-a154-44c3-a9c5-6c433e352520",
|
|
"workflowId": "F2rMlVS9mBypNNn7",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "glm-hub-generar-icono",
|
|
"responseMode": "responseNode",
|
|
"options": {
|
|
"allowedOrigins": "*"
|
|
}
|
|
},
|
|
"id": "e9002643-523d-436d-8c57-3d37f80f7ec3",
|
|
"name": "Webhook - Generar ícono",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
-3712,
|
|
464
|
|
],
|
|
"webhookId": "bc20a398-5ecf-42a2-9d17-9396aa278708"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const request = $input.first().json ?? {};\nconst rawBody = request.body ?? request;\nlet body = rawBody;\n\nif (typeof rawBody === 'string') {\n try {\n body = JSON.parse(rawBody);\n } catch {\n body = {};\n }\n}\n\nconst headers = request.headers ?? {};\nconst headerAuthorization = String(headers.authorization ?? headers.Authorization ?? '').trim();\nconst accessToken = String(body.accessToken ?? '').trim();\nconst authorization = headerAuthorization.toLowerCase().startsWith('bearer ')\n ? headerAuthorization\n : accessToken\n ? `Bearer ${accessToken}`\n : '';\nconst name = String(body.name ?? '').trim();\nconst description = String(body.description ?? '').trim();\n\nlet error = '';\nif (!authorization.toLowerCase().startsWith('bearer ')) {\n error = 'La sesión de Supabase no fue enviada o no es válida.';\n} else if (name.length < 2 || name.length > 60) {\n error = 'El nombre debe contener entre 2 y 60 caracteres.';\n} else if (!description || description.length > 180) {\n error = 'La descripción debe contener entre 1 y 180 caracteres.';\n}\n\nreturn [{ json: { valid: !error, error, authorization, name, description } }];"
|
|
},
|
|
"id": "f7b0a22e-2651-4479-86c8-25262faa38c4",
|
|
"name": "Validar solicitud",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-3456,
|
|
464
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "308c498d-7c3f-4414-a21f-571276aee28b",
|
|
"leftValue": "={{ $json.valid }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "0313d3ca-0439-4c2d-bd34-4da5ef6c3950",
|
|
"name": "¿Solicitud válida?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
-3232,
|
|
464
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: false, error: $json.error } }}",
|
|
"options": {
|
|
"responseCode": 400,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "bcfcc3ea-bbb3-4346-a5b4-1681d226a40c",
|
|
"name": "Responder 400",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
-2928,
|
|
576
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://dbit.digitalcompass.agency/rest/v1/rpc/glm_hub_icon_generation_access",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "apikey",
|
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "={{ $('Validar solicitud').first().json.authorization }}"
|
|
},
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "{}",
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"fullResponse": true,
|
|
"neverError": true,
|
|
"responseFormat": "json"
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"id": "bf1ac078-9362-445b-99d4-cd884ba5a8e1",
|
|
"name": "Validar administrador en Supabase",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
-2272,
|
|
384
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const source = $input.first()?.json ?? {};\nconst request = $('Validar solicitud').first().json ?? {};\n\nconst statusCode = Number(source.statusCode ?? 200);\n\nlet permission = source.body ?? source;\n\nif (typeof permission === 'string') {\n try {\n permission = JSON.parse(permission);\n } catch {\n permission = {};\n }\n}\n\nif (Array.isArray(permission)) {\n permission = permission[0] ?? {};\n}\n\nif (\n permission &&\n typeof permission === 'object' &&\n permission.authorized === undefined &&\n permission.body !== undefined\n) {\n permission = permission.body;\n\n if (typeof permission === 'string') {\n try {\n permission = JSON.parse(permission);\n } catch {\n permission = {};\n }\n }\n}\n\nconst authorized =\n statusCode >= 200 &&\n statusCode < 300 &&\n (\n permission?.authorized === true ||\n String(permission?.authorized ?? '').toLowerCase() === 'true'\n );\n\nlet error = '';\n\nif (statusCode === 401) {\n error =\n 'La sesión de Supabase expiró. Cierra sesión e inicia nuevamente.';\n} else if (statusCode === 403) {\n error =\n 'Supabase rechazó la validación administrativa.';\n} else if (statusCode >= 400) {\n error =\n String(permission?.message ?? permission?.error ?? '') ||\n `Supabase respondió con el código ${statusCode}.`;\n} else if (!authorized) {\n error =\n 'Tu sesión no tiene permisos de administrador para generar íconos.';\n}\n\nreturn [\n {\n json: {\n authorized,\n error,\n name: String(request.name ?? ''),\n description: String(request.description ?? ''),\n email: String(permission?.email ?? ''),\n userId: String(permission?.userId ?? ''),\n supabaseStatus: statusCode\n }\n }\n];"
|
|
},
|
|
"id": "0f6af309-436b-49df-8a1b-2120a0290474",
|
|
"name": "Confirmar autorización",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-2048,
|
|
384
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "c9275da4-0bca-4ce9-bd75-cc7d06e4cf56",
|
|
"leftValue": "={{ $json.authorized }}",
|
|
"rightValue": true,
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "c115098c-9289-46ad-902b-d79aa8168bc0",
|
|
"name": "¿Es administrador?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
-1840,
|
|
384
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: false, error: $json.error } }}",
|
|
"options": {
|
|
"responseCode": 403,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "c44dee89-da47-43d0-9134-a723f80f7e39",
|
|
"name": "Responder 403",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
-1600,
|
|
560
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const { name, description } = $input.first().json;\nconst prompt = `Crea un ícono cuadrado 1:1, profesional, minimalista y fácil de reconocer para una aplicación interna de GomezLee Marketing.\n\nNombre de la aplicación: ${name}\nDescripción funcional: ${description}\n\nRequisitos visuales obligatorios:\n- Representa la función principal con un único símbolo simple, claro y reconocible.\n- Estética corporativa moderna, limpia y tecnológica.\n- Paleta inspirada en GLM: azul grisáceo, verde claro y verde azulado.\n- Fondo blanco o transparente.\n- Composición centrada, con buen espacio de respiración.\n- Sin texto, sin letras, sin siglas, sin números, sin marcas comerciales y sin logotipos existentes.\n- Sin mockups, sin fotografías, sin personas, sin sombras exageradas y sin fondos complejos.\n- Debe seguir siendo legible al reducirse a 32 x 32 píxeles.\n- Entrega solamente el ícono final.`;\nreturn [{ json: { prompt } }];"
|
|
},
|
|
"id": "4a092f12-4966-4d3e-ba45-f82626f8aff8",
|
|
"name": "Construir prompt del ícono",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-864,
|
|
352
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"resource": "image",
|
|
"modelId": {
|
|
"__rl": true,
|
|
"value": "models/gemini-3-pro-image",
|
|
"mode": "list",
|
|
"cachedResultName": "models/gemini-3-pro-image (Nano Banana Pro)"
|
|
},
|
|
"prompt": "={{ $json.prompt }}",
|
|
"options": {
|
|
"binaryPropertyOutput": "data"
|
|
}
|
|
},
|
|
"id": "0daf0a06-a280-4500-95f6-f76b8945ed63",
|
|
"name": "Generate an image",
|
|
"type": "@n8n/n8n-nodes-langchain.googleGemini",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
-96,
|
|
384
|
|
],
|
|
"credentials": {
|
|
"googlePalmApi": {
|
|
"id": "jvsXYwL6IOoY2DBU",
|
|
"name": "Isaac - Gemini Api Pago"
|
|
}
|
|
},
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "binary",
|
|
"responseDataSource": "set",
|
|
"options": {
|
|
"responseCode": 200,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
},
|
|
{
|
|
"name": "Content-Disposition",
|
|
"value": "inline; filename=\"glm-hub-icon.png\""
|
|
},
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "image/png"
|
|
},
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "8594ec32-5d88-4435-aa8f-7d95d819eada",
|
|
"name": "Responder con ícono",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
192,
|
|
304
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const input = $input.first().json ?? {};\nconst rawError = input.error ?? input.message ?? input.description ?? input;\nlet message = 'Gemini no pudo generar el ícono. Revisa la credencial, el modelo y la cuota disponible.';\n\nif (typeof rawError === 'string' && rawError.trim()) {\n message = rawError.trim();\n} else if (rawError && typeof rawError === 'object') {\n message = String(rawError.message ?? rawError.description ?? rawError.status ?? message);\n}\n\nreturn [{ json: { ok: false, error: message } }];"
|
|
},
|
|
"id": "ef98ab6b-3341-441b-83bc-41eabfe1d839",
|
|
"name": "Preparar error de Gemini",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
768,
|
|
464
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ { ok: false, error: $json.error } }}",
|
|
"options": {
|
|
"responseCode": 502,
|
|
"responseHeaders": {
|
|
"entries": [
|
|
{
|
|
"name": "Cache-Control",
|
|
"value": "no-store"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"id": "273ca42a-ef45-426d-a5c1-e4a3f9ea704d",
|
|
"name": "Responder error de Gemini",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1.4,
|
|
"position": [
|
|
1008,
|
|
464
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 1. RECEPCIÓN Y VALIDACIÓN\n\nRecibe desde GLM Hub la solicitud para generar el icono y valida que la información obligatoria esté completa.\n\n- Comprueba los datos de la aplicación y la descripción del icono.\n- Normaliza la información recibida antes de continuar.\n- Detiene el proceso cuando faltan datos o la solicitud es incorrecta.\n- Devuelve una respuesta HTTP 400 para solicitudes inválidas.",
|
|
"height": 512,
|
|
"width": 1104,
|
|
"color": 2
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-3824,
|
|
240
|
|
],
|
|
"id": "0df68d74-d3c2-4257-be79-7a91077c3a07",
|
|
"name": "Sticky Note"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 2. AUTORIZACIÓN DEL USUARIO\n\nConsulta Supabase para comprobar que la persona que solicita la generación del icono tenga permisos de administrador.\n\n- Busca el perfil y los permisos del usuario autenticado.\n- Normaliza el resultado devuelto por Supabase.\n- Permite continuar únicamente a administradores autorizados.\n- Devuelve una respuesta HTTP 403 cuando el usuario no tiene permisos.",
|
|
"height": 544,
|
|
"width": 976,
|
|
"color": "#891F3A"
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-2368,
|
|
176
|
|
],
|
|
"id": "6133d6fd-9c82-40d2-8343-ceb670ec9410",
|
|
"name": "Sticky Note1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 3. PREPARACIÓN DEL PROMPT\n\nConstruye las instrucciones que se enviarán a Gemini para generar un icono adecuado para la aplicación.\n\n- Utiliza el nombre, la descripción y el propósito de la aplicación.\n- Define el estilo visual requerido para los iconos de GLM Hub.\n- Evita textos, marcas de agua y elementos innecesarios.\n- Prepara un prompt claro y consistente para obtener mejores resultados.",
|
|
"height": 384,
|
|
"width": 832,
|
|
"color": 7
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1216,
|
|
160
|
|
],
|
|
"id": "cf146dba-5bed-4158-83bc-b55c268b1837",
|
|
"name": "Sticky Note2"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 4. GENERACIÓN DEL ICONO\n\nEnvía el prompt a Gemini, genera la imagen y devuelve el icono al GLM Hub cuando el proceso finaliza correctamente.\n\n- Solicita a Gemini la creación del icono.\n- Recibe la imagen generada por el modelo.\n- Devuelve el resultado mediante la respuesta del webhook.\n- Permite que el GLM Hub utilice el icono generado en la aplicación.",
|
|
"height": 528,
|
|
"width": 592,
|
|
"color": 3
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-192,
|
|
80
|
|
],
|
|
"id": "2fc45b39-21af-4151-90c8-0299f6a5a527",
|
|
"name": "Sticky Note3"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 5. CONTROL DE ERRORES DE GEMINI\n\nGestiona los fallos que puedan producirse durante la generación de la imagen y devuelve una respuesta controlada al GLM Hub.\n\n- Captura errores técnicos o respuestas fallidas de Gemini.\n- Prepara un mensaje comprensible para el usuario.\n- Evita que el workflow termine sin una respuesta.\n- Informa que el icono no pudo generarse para que pueda intentarse nuevamente.",
|
|
"height": 384,
|
|
"width": 704
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
544,
|
|
256
|
|
],
|
|
"id": "2caf2891-ccdf-434b-b773-5a4dca43e4a4",
|
|
"name": "Sticky Note4"
|
|
}
|
|
],
|
|
"connections": {
|
|
"Webhook - Generar ícono": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Validar solicitud",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Validar solicitud": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Solicitud válida?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Solicitud válida?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Validar administrador en Supabase",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder 400",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Validar administrador en Supabase": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Confirmar autorización",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Confirmar autorización": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "¿Es administrador?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"¿Es administrador?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Construir prompt del ícono",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Responder 403",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Construir prompt del ícono": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Generate an image",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Generate an image": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Responder con ícono",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Preparar error de Gemini",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar error de Gemini": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Responder error de Gemini",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"authors": "Isaac Aracena",
|
|
"name": "Version b743c332",
|
|
"description": "",
|
|
"autosaved": true,
|
|
"workflowPublishHistory": [
|
|
{
|
|
"createdAt": "2026-08-03T13:45:11.216Z",
|
|
"id": 3848,
|
|
"workflowId": "F2rMlVS9mBypNNn7",
|
|
"versionId": "b743c332-a154-44c3-a9c5-6c433e352520",
|
|
"event": "activated",
|
|
"userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
|
|
}
|
|
]
|
|
}
|
|
} |