Files
glm-hub-generar-icono-con-g…/glm-hub-generar-icono-con-gemini.json
T

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"
}
]
}
}