From 2bc7a93bcf2f145adc6bfc11961e2982caae5488 Mon Sep 17 00:00:00 2001 From: Isaac_Aracena Date: Fri, 5 Jun 2026 14:51:18 +0000 Subject: [PATCH] Create backup: Gitea n8n Auto Backup --- gitea-n8n-auto-backup.json | 1212 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1212 insertions(+) create mode 100644 gitea-n8n-auto-backup.json diff --git a/gitea-n8n-auto-backup.json b/gitea-n8n-auto-backup.json new file mode 100644 index 0000000..42988c6 --- /dev/null +++ b/gitea-n8n-auto-backup.json @@ -0,0 +1,1212 @@ +{ + "updatedAt": "2026-06-05T14:50:21.914Z", + "createdAt": "2026-06-05T14:36:49.343Z", + "id": "jtJGuwijffvHznH2", + "name": "Gitea n8n Auto Backup", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 0, + 336 + ], + "id": "8b02b163-b1b9-4715-a426-3c07f15afd96", + "name": "Manual - Ejecutar respaldo ahora" + }, + { + "parameters": { + "rule": { + "interval": [ + { + "field": "minutes", + "minutesInterval": 1 + } + ] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [ + 0, + 560 + ], + "id": "b5c75588-f5a2-4e04-a567-7c98c1e20d87", + "name": "Schedule - Revisar publicados cada 1 min" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "463e6c6e-40ee-46ff-b30f-3a612f21af4a", + "name": "n8n_base_url", + "value": "https://agenteit.digitalcompass.agency", + "type": "string" + }, + { + "id": "73de7608-d7ca-4893-babc-5c59552bbd25", + "name": "gitea_base_url", + "value": "https://git.digitalcompass.agency", + "type": "string" + }, + { + "id": "83967bc3-16a0-4afc-9bd1-2325d759395a", + "name": "gitea_owner", + "value": "Isaac_Aracena", + "type": "string" + }, + { + "id": "b62d61e7-9688-4ebe-bf0e-c40d38c84ffc", + "name": "owner_type", + "value": "user", + "type": "string" + }, + { + "id": "f4443432-6bda-4b9b-9b86-c62158fb201f", + "name": "default_branch", + "value": "main", + "type": "string" + }, + { + "id": "a6681aaf-eccc-42e6-95b9-d1926cff2fab", + "name": "private_repo", + "value": false, + "type": "boolean" + }, + { + "id": "1501bda6-ee9b-4633-979a-46176c853ce4", + "name": "force_full_backup", + "value": false, + "type": "boolean" + }, + { + "id": "859f8c57-9a61-4d81-8e1d-74e2767d60d3", + "name": "only_published_workflows", + "value": true, + "type": "boolean" + }, + { + "id": "ce4d20a9-5f5c-45c4-80dc-63e628ccdcbc", + "name": "ignore_workflow_ids_csv", + "value": "", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 272, + 432 + ], + "id": "fb75c274-8db3-4ad3-8903-74f2dbcc99ec", + "name": "Config" + }, + { + "parameters": { + "url": "={{ $json.n8n_base_url.replace(/\\/$/, '') + '/api/v1/workflows?limit=250' }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 528, + 432 + ], + "id": "13b905b8-ffa8-4020-a02f-b5db5d78d99b", + "name": "n8n - Listar workflows", + "credentials": { + "httpHeaderAuth": { + "id": "UPDlAh0YeGhLTTiE", + "name": "Header Auth account 2" + } + } + }, + { + "parameters": { + "jsCode": "const cfg = $('Config').first().json;\nconst response = $input.first().json;\n\n// La API pública de n8n normalmente devuelve { data: [...], nextCursor: ... }.\n// Dejamos fallback por si la instancia devuelve un array directo.\nconst workflows = Array.isArray(response.data)\n ? response.data\n : (Array.isArray(response) ? response : []);\n\nif (!workflows.length) {\n throw new Error('No recibí workflows desde la API de n8n. Revisa n8n_base_url, credencial X-N8N-API-KEY y endpoint /api/v1/workflows.');\n}\n\nconst staticData = $getWorkflowStaticData('global');\nstaticData.backedUpWorkflows = staticData.backedUpWorkflows || {};\n\nconst forceAll = cfg.force_full_backup === true || String(cfg.force_full_backup).toLowerCase() === 'true';\nconst onlyPublished = cfg.only_published_workflows === true || String(cfg.only_published_workflows).toLowerCase() === 'true';\nconst ignored = String(cfg.ignore_workflow_ids_csv || '')\n .split(',')\n .map(v => v.trim())\n .filter(Boolean);\n\nconst out = [];\n\nfor (const wf of workflows) {\n const id = String(wf.id || wf.workflowId || '');\n if (!id) continue;\n if (ignored.includes(id)) continue;\n\n // En n8n la lista suele traer active=true para workflows publicados/activos.\n // Si tu instancia usa otro campo, dejamos varios fallbacks comunes.\n const isPublished = wf.active === true || wf.isActive === true || wf.published === true || String(wf.active).toLowerCase() === 'true';\n if (onlyPublished && !isPublished) continue;\n\n const updatedAt = String(wf.updatedAt || wf.updated_at || wf.versionId || wf.version_id || '');\n const previousUpdatedAt = staticData.backedUpWorkflows[id]?.updatedAt;\n\n // Primera ejecución: respalda todos los publicados detectados.\n // Luego: solo workflows publicados nuevos o modificados.\n if (forceAll || !previousUpdatedAt || previousUpdatedAt !== updatedAt) {\n out.push({\n json: {\n ...cfg,\n workflow_id: id,\n workflow_name_hint: wf.name || `workflow-${id}`,\n workflow_updated_at: updatedAt,\n workflow_published: isPublished,\n detected_at: new Date().toISOString(),\n }\n });\n }\n}\n\nreturn out;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 784, + 432 + ], + "id": "bb3bd9fe-8c31-43bf-8ed2-988e9df69c8a", + "name": "Filtrar workflows nuevos o modificados" + }, + { + "parameters": { + "url": "={{ $json.n8n_base_url.replace(/\\/$/, '') + '/api/v1/workflows/' + $json.workflow_id }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1040, + 432 + ], + "id": "2425a188-773c-4776-9e05-8b7d1af59c2b", + "name": "n8n - Obtener workflow completo", + "credentials": { + "httpHeaderAuth": { + "id": "UPDlAh0YeGhLTTiE", + "name": "Header Auth account 2" + } + } + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const cfg = $('Filtrar workflows nuevos o modificados').item.json;\nconst workflow = $input.item.json;\n\nfunction slugifyForRepo(value) {\n return String(value || '')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/ñ/g, 'n')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .replace(/-{2,}/g, '-')\n .slice(0, 90) || 'workflow-sin-nombre';\n}\n\nconst workflowName = workflow.name || cfg.workflow_name_hint || `workflow-${cfg.workflow_id}`;\nconst workflowId = String(workflow.id || cfg.workflow_id);\nconst repoName = slugifyForRepo(workflowName);\nconst filePath = `${repoName}.json`;\nconst branch = cfg.default_branch || 'main';\n\nconst exportWorkflow = {\n ...workflow,\n};\n\n// Evita guardar datos de prueba pesados si vienen en la respuesta.\ndelete exportWorkflow.pinData;\n\nconst content = Buffer.from(\n JSON.stringify(exportWorkflow, null, 2),\n 'utf8'\n).toString('base64');\n\nreturn {\n json: {\n ...cfg,\n workflow_id: workflowId,\n workflow_name: workflowName,\n repo_name: repoName,\n file_path: filePath,\n branch,\n repo_description: `Backup público automático del workflow n8n: ${workflowName}`,\n content,\n commit_create_message: `Create backup: ${workflowName}`,\n commit_update_message: `Update backup: ${workflowName}`,\n }\n};" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1328, + 432 + ], + "id": "75ce5ba0-c1ea-4a22-8e15-648d82e6e827", + "name": "Preparar repo y archivo" + }, + { + "parameters": { + "url": "={{ $json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $json.gitea_owner + '/' + $json.repo_name }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1952, + 432 + ], + "id": "a221abfd-0ce0-4c7c-86eb-f9e9314f940d", + "name": "Gitea - Verificar repo", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + }, + "onError": "continueErrorOutput" + }, + { + "parameters": { + "method": "POST", + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + ($('Loop Over Items').item.json.owner_type === 'org' ? '/api/v1/orgs/' + $('Loop Over Items').item.json.gitea_owner + '/repos' : '/api/v1/user/repos') }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { name: $('Loop Over Items').item.json.repo_name, description: $('Loop Over Items').item.json.repo_description, private: false, auto_init: true, default_branch: $('Loop Over Items').item.json.branch } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2208, + 624 + ], + "id": "26e851db-95d6-4756-852c-ffa29bf9ed0a", + "name": "Gitea - Crear repo público si no existe", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + } + }, + { + "parameters": { + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Loop Over Items').item.json.gitea_owner + '/' + $('Loop Over Items').item.json.repo_name + '/contents/' + $('Loop Over Items').item.json.file_path }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2464, + 432 + ], + "id": "bed90785-6c35-4d38-8942-f644fa8b655b", + "name": "Gitea - Verificar archivo", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + }, + "onError": "continueErrorOutput" + }, + { + "parameters": { + "method": "PUT", + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Loop Over Items').item.json.gitea_owner + '/' + $('Loop Over Items').item.json.repo_name + '/contents/' + $('Loop Over Items').item.json.file_path }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { message: $('Loop Over Items').item.json.commit_update_message, content: $('Loop Over Items').item.json.content, sha: $json.sha, branch: $('Loop Over Items').item.json.branch } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2752, + 320 + ], + "id": "fac285a6-2e37-4043-8127-d534c78aa607", + "name": "Gitea - Actualizar workflow.json", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Loop Over Items').item.json.gitea_owner + '/' + $('Loop Over Items').item.json.repo_name + '/contents/' + $('Loop Over Items').item.json.file_path }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { message: $('Loop Over Items').item.json.commit_create_message, content: $('Loop Over Items').item.json.content, branch: $('Loop Over Items').item.json.branch } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2752, + 624 + ], + "id": "a76819e9-ba84-40d9-8799-ffe86cf0f53f", + "name": "Gitea - Crear workflow.json", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + } + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const data = $('Loop Over Items').item.json;\n\nconst staticData = $getWorkflowStaticData('global');\nstaticData.backedUpWorkflows = staticData.backedUpWorkflows || {};\n\nstaticData.backedUpWorkflows[data.workflow_id] = {\n name: data.workflow_name,\n repo_name: data.repo_name,\n file_path: data.file_path,\n updatedAt: data.workflow_updated_at || '',\n backedUpAt: new Date().toISOString(),\n};\n\nreturn {\n json: {\n ok: true,\n workflow_id: data.workflow_id,\n workflow_name: data.workflow_name,\n repo_name: data.repo_name,\n file_path: data.file_path,\n repo_public: true,\n backedUpAt: new Date().toISOString(),\n }\n};" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3024, + 464 + ], + "id": "6b48a9df-89ef-4a8a-a6ff-2c7cfa789b51", + "name": "Marcar respaldado" + }, + { + "parameters": { + "options": {} + }, + "type": "n8n-nodes-base.splitInBatches", + "typeVersion": 3, + "position": [ + 1680, + 432 + ], + "id": "1e618c0d-2b6d-4f69-9b63-300c222004e8", + "name": "Loop Over Items" + }, + { + "parameters": { + "method": "PATCH", + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Loop Over Items').item.json.gitea_owner + '/' + $('Loop Over Items').item.json.repo_name }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { private: false } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2208, + 336 + ], + "id": "bcaa9c9b-bd82-4dc9-aa6f-e8b4391d26a8", + "name": "Gitea - Asegurar repo público", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + } + } + ], + "connections": { + "Manual - Ejecutar respaldo ahora": { + "main": [ + [ + { + "node": "Config", + "type": "main", + "index": 0 + } + ] + ] + }, + "Schedule - Revisar publicados cada 1 min": { + "main": [ + [ + { + "node": "Config", + "type": "main", + "index": 0 + } + ] + ] + }, + "Config": { + "main": [ + [ + { + "node": "n8n - Listar workflows", + "type": "main", + "index": 0 + } + ] + ] + }, + "n8n - Listar workflows": { + "main": [ + [ + { + "node": "Filtrar workflows nuevos o modificados", + "type": "main", + "index": 0 + } + ] + ] + }, + "Filtrar workflows nuevos o modificados": { + "main": [ + [ + { + "node": "n8n - Obtener workflow completo", + "type": "main", + "index": 0 + } + ] + ] + }, + "n8n - Obtener workflow completo": { + "main": [ + [ + { + "node": "Preparar repo y archivo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar repo y archivo": { + "main": [ + [ + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + }, + "Loop Over Items": { + "main": [ + [], + [ + { + "node": "Gitea - Verificar repo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Verificar repo": { + "main": [ + [ + { + "node": "Gitea - Asegurar repo público", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Gitea - Crear repo público si no existe", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Asegurar repo público": { + "main": [ + [ + { + "node": "Gitea - Verificar archivo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Crear repo público si no existe": { + "main": [ + [ + { + "node": "Gitea - Verificar archivo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Verificar archivo": { + "main": [ + [ + { + "node": "Gitea - Actualizar workflow.json", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Gitea - Crear workflow.json", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Actualizar workflow.json": { + "main": [ + [ + { + "node": "Marcar respaldado", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Crear workflow.json": { + "main": [ + [ + { + "node": "Marcar respaldado", + "type": "main", + "index": 0 + } + ] + ] + }, + "Marcar respaldado": { + "main": [ + [ + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate" + }, + "staticData": { + "node:Schedule - Revisar publicados cada 1 min": { + "recurrenceRules": [] + }, + "global": { + "backedUpWorkflows": { + "4eCPQcE3FDIll8bW": { + "name": "TEST - Telegram - Propuestas Ejecutadas", + "repo_name": "test-telegram-propuestas-ejecutadas", + "file_path": "test-telegram-propuestas-ejecutadas.json", + "updatedAt": "2026-06-04T01:50:49.282Z", + "backedUpAt": "2026-06-05T14:47:19.027Z" + }, + "8jCi4gPc5kfHZvk2": { + "name": "Cruce de Cuentas - GLM", + "repo_name": "cruce-de-cuentas-glm", + "file_path": "cruce-de-cuentas-glm.json", + "updatedAt": "2026-05-12T19:01:56.419Z", + "backedUpAt": "2026-06-05T14:47:20.070Z" + }, + "J4EmhOad6NtLHzkA": { + "name": "Gitea n8n Auto Backup READY - Publicados a Gitea", + "repo_name": "gitea-n8n-auto-backup-ready-publicados-a-gitea", + "file_path": "gitea-n8n-auto-backup-ready-publicados-a-gitea.json", + "updatedAt": "2026-06-05T14:35:59.420Z", + "backedUpAt": "2026-06-05T14:47:21.527Z" + }, + "MU5GdxLtFzcRcfyM": { + "name": "Tablero CDC - Sync Listas", + "repo_name": "tablero-cdc-sync-listas", + "file_path": "tablero-cdc-sync-listas.json", + "updatedAt": "2026-06-04T00:42:19.112Z", + "backedUpAt": "2026-06-05T14:47:22.589Z" + }, + "NDHPXi6jzVifyZU1": { + "name": "Fulgencio Procesar Propuestas en Bruto - GLM", + "repo_name": "fulgencio-procesar-propuestas-en-bruto-glm", + "file_path": "fulgencio-procesar-propuestas-en-bruto-glm.json", + "updatedAt": "2026-05-15T21:17:35.955Z", + "backedUpAt": "2026-06-05T14:47:24.157Z" + }, + "UzzVI2AKIXuZGgTL": { + "name": "Fulgencio Alertas Sheets - GLM", + "repo_name": "fulgencio-alertas-sheets-glm", + "file_path": "fulgencio-alertas-sheets-glm.json", + "updatedAt": "2026-06-05T14:46:59.558Z", + "backedUpAt": "2026-06-05T14:47:25.465Z" + }, + "Z8W67i7RfrhXRDFQ": { + "name": "CDC Brief - GLM V2", + "repo_name": "cdc-brief-glm-v2", + "file_path": "cdc-brief-glm-v2.json", + "updatedAt": "2026-06-04T00:42:57.459Z", + "backedUpAt": "2026-06-05T14:47:26.510Z" + }, + "d39DtIUTQzduqX0v": { + "name": "Tablero CDC - Sync Proyecto a Sheet WEBHOOK", + "repo_name": "tablero-cdc-sync-proyecto-a-sheet-webhook", + "file_path": "tablero-cdc-sync-proyecto-a-sheet-webhook.json", + "updatedAt": "2026-06-04T00:43:22.749Z", + "backedUpAt": "2026-06-05T14:47:27.450Z" + }, + "jtJGuwijffvHznH2": { + "name": "Gitea n8n Auto Backup READY - Publicados a Gitea", + "repo_name": "gitea-n8n-auto-backup-ready-publicados-a-gitea", + "file_path": "gitea-n8n-auto-backup-ready-publicados-a-gitea.json", + "updatedAt": "2026-06-05T14:50:11.847Z", + "backedUpAt": "2026-06-05T14:50:17.993Z" + }, + "puF4LUczoSz3hcek": { + "name": "Reportar Error - Isaac", + "repo_name": "reportar-error-isaac", + "file_path": "reportar-error-isaac.json", + "updatedAt": "2026-05-18T14:04:42.487Z", + "backedUpAt": "2026-06-05T14:47:29.512Z" + }, + "t6lFUulNtDIR3tAm": { + "name": "Chat de WhatsApp de propuestas ejecutadas", + "repo_name": "chat-de-whatsapp-de-propuestas-ejecutadas", + "file_path": "chat-de-whatsapp-de-propuestas-ejecutadas.json", + "updatedAt": "2026-06-04T00:42:33.818Z", + "backedUpAt": "2026-06-05T14:47:30.527Z" + } + } + } + }, + "meta": null, + "versionId": "6fea1c4a-860c-43b6-ab05-52a901c0523b", + "activeVersionId": "6fea1c4a-860c-43b6-ab05-52a901c0523b", + "versionCounter": 33, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-06-05T14:36:49.347Z", + "createdAt": "2026-06-05T14:36:49.347Z", + "role": "workflow:owner", + "workflowId": "jtJGuwijffvHznH2", + "projectId": "PJpTANzTXIFibWsW", + "project": { + "updatedAt": "2026-04-22T14:25:09.686Z", + "createdAt": "2026-04-22T14:22:54.790Z", + "id": "PJpTANzTXIFibWsW", + "name": "Isaac Aracena ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" + } + } + ], + "tags": [], + "activeVersion": { + "updatedAt": "2026-06-05T14:50:23.000Z", + "createdAt": "2026-06-05T14:50:11.849Z", + "versionId": "6fea1c4a-860c-43b6-ab05-52a901c0523b", + "workflowId": "jtJGuwijffvHznH2", + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 0, + 336 + ], + "id": "8b02b163-b1b9-4715-a426-3c07f15afd96", + "name": "Manual - Ejecutar respaldo ahora" + }, + { + "parameters": { + "rule": { + "interval": [ + { + "field": "minutes", + "minutesInterval": 1 + } + ] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [ + 0, + 560 + ], + "id": "b5c75588-f5a2-4e04-a567-7c98c1e20d87", + "name": "Schedule - Revisar publicados cada 1 min" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "463e6c6e-40ee-46ff-b30f-3a612f21af4a", + "name": "n8n_base_url", + "value": "https://agenteit.digitalcompass.agency", + "type": "string" + }, + { + "id": "73de7608-d7ca-4893-babc-5c59552bbd25", + "name": "gitea_base_url", + "value": "https://git.digitalcompass.agency", + "type": "string" + }, + { + "id": "83967bc3-16a0-4afc-9bd1-2325d759395a", + "name": "gitea_owner", + "value": "Isaac_Aracena", + "type": "string" + }, + { + "id": "b62d61e7-9688-4ebe-bf0e-c40d38c84ffc", + "name": "owner_type", + "value": "user", + "type": "string" + }, + { + "id": "f4443432-6bda-4b9b-9b86-c62158fb201f", + "name": "default_branch", + "value": "main", + "type": "string" + }, + { + "id": "a6681aaf-eccc-42e6-95b9-d1926cff2fab", + "name": "private_repo", + "value": false, + "type": "boolean" + }, + { + "id": "1501bda6-ee9b-4633-979a-46176c853ce4", + "name": "force_full_backup", + "value": false, + "type": "boolean" + }, + { + "id": "859f8c57-9a61-4d81-8e1d-74e2767d60d3", + "name": "only_published_workflows", + "value": true, + "type": "boolean" + }, + { + "id": "ce4d20a9-5f5c-45c4-80dc-63e628ccdcbc", + "name": "ignore_workflow_ids_csv", + "value": "", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 272, + 432 + ], + "id": "fb75c274-8db3-4ad3-8903-74f2dbcc99ec", + "name": "Config" + }, + { + "parameters": { + "url": "={{ $json.n8n_base_url.replace(/\\/$/, '') + '/api/v1/workflows?limit=250' }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 528, + 432 + ], + "id": "13b905b8-ffa8-4020-a02f-b5db5d78d99b", + "name": "n8n - Listar workflows", + "credentials": { + "httpHeaderAuth": { + "id": "UPDlAh0YeGhLTTiE", + "name": "Header Auth account 2" + } + } + }, + { + "parameters": { + "jsCode": "const cfg = $('Config').first().json;\nconst response = $input.first().json;\n\n// La API pública de n8n normalmente devuelve { data: [...], nextCursor: ... }.\n// Dejamos fallback por si la instancia devuelve un array directo.\nconst workflows = Array.isArray(response.data)\n ? response.data\n : (Array.isArray(response) ? response : []);\n\nif (!workflows.length) {\n throw new Error('No recibí workflows desde la API de n8n. Revisa n8n_base_url, credencial X-N8N-API-KEY y endpoint /api/v1/workflows.');\n}\n\nconst staticData = $getWorkflowStaticData('global');\nstaticData.backedUpWorkflows = staticData.backedUpWorkflows || {};\n\nconst forceAll = cfg.force_full_backup === true || String(cfg.force_full_backup).toLowerCase() === 'true';\nconst onlyPublished = cfg.only_published_workflows === true || String(cfg.only_published_workflows).toLowerCase() === 'true';\nconst ignored = String(cfg.ignore_workflow_ids_csv || '')\n .split(',')\n .map(v => v.trim())\n .filter(Boolean);\n\nconst out = [];\n\nfor (const wf of workflows) {\n const id = String(wf.id || wf.workflowId || '');\n if (!id) continue;\n if (ignored.includes(id)) continue;\n\n // En n8n la lista suele traer active=true para workflows publicados/activos.\n // Si tu instancia usa otro campo, dejamos varios fallbacks comunes.\n const isPublished = wf.active === true || wf.isActive === true || wf.published === true || String(wf.active).toLowerCase() === 'true';\n if (onlyPublished && !isPublished) continue;\n\n const updatedAt = String(wf.updatedAt || wf.updated_at || wf.versionId || wf.version_id || '');\n const previousUpdatedAt = staticData.backedUpWorkflows[id]?.updatedAt;\n\n // Primera ejecución: respalda todos los publicados detectados.\n // Luego: solo workflows publicados nuevos o modificados.\n if (forceAll || !previousUpdatedAt || previousUpdatedAt !== updatedAt) {\n out.push({\n json: {\n ...cfg,\n workflow_id: id,\n workflow_name_hint: wf.name || `workflow-${id}`,\n workflow_updated_at: updatedAt,\n workflow_published: isPublished,\n detected_at: new Date().toISOString(),\n }\n });\n }\n}\n\nreturn out;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 784, + 432 + ], + "id": "bb3bd9fe-8c31-43bf-8ed2-988e9df69c8a", + "name": "Filtrar workflows nuevos o modificados" + }, + { + "parameters": { + "url": "={{ $json.n8n_base_url.replace(/\\/$/, '') + '/api/v1/workflows/' + $json.workflow_id }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1040, + 432 + ], + "id": "2425a188-773c-4776-9e05-8b7d1af59c2b", + "name": "n8n - Obtener workflow completo", + "credentials": { + "httpHeaderAuth": { + "id": "UPDlAh0YeGhLTTiE", + "name": "Header Auth account 2" + } + } + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const cfg = $('Filtrar workflows nuevos o modificados').item.json;\nconst workflow = $input.item.json;\n\nfunction slugifyForRepo(value) {\n return String(value || '')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .replace(/ñ/g, 'n')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .replace(/-{2,}/g, '-')\n .slice(0, 90) || 'workflow-sin-nombre';\n}\n\nconst workflowName = workflow.name || cfg.workflow_name_hint || `workflow-${cfg.workflow_id}`;\nconst workflowId = String(workflow.id || cfg.workflow_id);\nconst repoName = slugifyForRepo(workflowName);\nconst filePath = `${repoName}.json`;\nconst branch = cfg.default_branch || 'main';\n\nconst exportWorkflow = {\n ...workflow,\n};\n\n// Evita guardar datos de prueba pesados si vienen en la respuesta.\ndelete exportWorkflow.pinData;\n\nconst content = Buffer.from(\n JSON.stringify(exportWorkflow, null, 2),\n 'utf8'\n).toString('base64');\n\nreturn {\n json: {\n ...cfg,\n workflow_id: workflowId,\n workflow_name: workflowName,\n repo_name: repoName,\n file_path: filePath,\n branch,\n repo_description: `Backup público automático del workflow n8n: ${workflowName}`,\n content,\n commit_create_message: `Create backup: ${workflowName}`,\n commit_update_message: `Update backup: ${workflowName}`,\n }\n};" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1328, + 432 + ], + "id": "75ce5ba0-c1ea-4a22-8e15-648d82e6e827", + "name": "Preparar repo y archivo" + }, + { + "parameters": { + "url": "={{ $json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $json.gitea_owner + '/' + $json.repo_name }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1952, + 432 + ], + "id": "a221abfd-0ce0-4c7c-86eb-f9e9314f940d", + "name": "Gitea - Verificar repo", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + }, + "onError": "continueErrorOutput" + }, + { + "parameters": { + "method": "POST", + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + ($('Loop Over Items').item.json.owner_type === 'org' ? '/api/v1/orgs/' + $('Loop Over Items').item.json.gitea_owner + '/repos' : '/api/v1/user/repos') }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { name: $('Loop Over Items').item.json.repo_name, description: $('Loop Over Items').item.json.repo_description, private: false, auto_init: true, default_branch: $('Loop Over Items').item.json.branch } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2208, + 624 + ], + "id": "26e851db-95d6-4756-852c-ffa29bf9ed0a", + "name": "Gitea - Crear repo público si no existe", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + } + }, + { + "parameters": { + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Loop Over Items').item.json.gitea_owner + '/' + $('Loop Over Items').item.json.repo_name + '/contents/' + $('Loop Over Items').item.json.file_path }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2464, + 432 + ], + "id": "bed90785-6c35-4d38-8942-f644fa8b655b", + "name": "Gitea - Verificar archivo", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + }, + "onError": "continueErrorOutput" + }, + { + "parameters": { + "method": "PUT", + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Loop Over Items').item.json.gitea_owner + '/' + $('Loop Over Items').item.json.repo_name + '/contents/' + $('Loop Over Items').item.json.file_path }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { message: $('Loop Over Items').item.json.commit_update_message, content: $('Loop Over Items').item.json.content, sha: $json.sha, branch: $('Loop Over Items').item.json.branch } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2752, + 320 + ], + "id": "fac285a6-2e37-4043-8127-d534c78aa607", + "name": "Gitea - Actualizar workflow.json", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Loop Over Items').item.json.gitea_owner + '/' + $('Loop Over Items').item.json.repo_name + '/contents/' + $('Loop Over Items').item.json.file_path }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { message: $('Loop Over Items').item.json.commit_create_message, content: $('Loop Over Items').item.json.content, branch: $('Loop Over Items').item.json.branch } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2752, + 624 + ], + "id": "a76819e9-ba84-40d9-8799-ffe86cf0f53f", + "name": "Gitea - Crear workflow.json", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + } + }, + { + "parameters": { + "mode": "runOnceForEachItem", + "jsCode": "const data = $('Loop Over Items').item.json;\n\nconst staticData = $getWorkflowStaticData('global');\nstaticData.backedUpWorkflows = staticData.backedUpWorkflows || {};\n\nstaticData.backedUpWorkflows[data.workflow_id] = {\n name: data.workflow_name,\n repo_name: data.repo_name,\n file_path: data.file_path,\n updatedAt: data.workflow_updated_at || '',\n backedUpAt: new Date().toISOString(),\n};\n\nreturn {\n json: {\n ok: true,\n workflow_id: data.workflow_id,\n workflow_name: data.workflow_name,\n repo_name: data.repo_name,\n file_path: data.file_path,\n repo_public: true,\n backedUpAt: new Date().toISOString(),\n }\n};" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3024, + 464 + ], + "id": "6b48a9df-89ef-4a8a-a6ff-2c7cfa789b51", + "name": "Marcar respaldado" + }, + { + "parameters": { + "options": {} + }, + "type": "n8n-nodes-base.splitInBatches", + "typeVersion": 3, + "position": [ + 1680, + 432 + ], + "id": "1e618c0d-2b6d-4f69-9b63-300c222004e8", + "name": "Loop Over Items" + }, + { + "parameters": { + "method": "PATCH", + "url": "={{ $('Loop Over Items').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Loop Over Items').item.json.gitea_owner + '/' + $('Loop Over Items').item.json.repo_name }}", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ { private: false } }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 2208, + 336 + ], + "id": "bcaa9c9b-bd82-4dc9-aa6f-e8b4391d26a8", + "name": "Gitea - Asegurar repo público", + "credentials": { + "httpHeaderAuth": { + "id": "goGJ7p07L5ygli0D", + "name": "Header Auth account" + } + } + } + ], + "connections": { + "Manual - Ejecutar respaldo ahora": { + "main": [ + [ + { + "node": "Config", + "type": "main", + "index": 0 + } + ] + ] + }, + "Schedule - Revisar publicados cada 1 min": { + "main": [ + [ + { + "node": "Config", + "type": "main", + "index": 0 + } + ] + ] + }, + "Config": { + "main": [ + [ + { + "node": "n8n - Listar workflows", + "type": "main", + "index": 0 + } + ] + ] + }, + "n8n - Listar workflows": { + "main": [ + [ + { + "node": "Filtrar workflows nuevos o modificados", + "type": "main", + "index": 0 + } + ] + ] + }, + "Filtrar workflows nuevos o modificados": { + "main": [ + [ + { + "node": "n8n - Obtener workflow completo", + "type": "main", + "index": 0 + } + ] + ] + }, + "n8n - Obtener workflow completo": { + "main": [ + [ + { + "node": "Preparar repo y archivo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar repo y archivo": { + "main": [ + [ + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + }, + "Loop Over Items": { + "main": [ + [], + [ + { + "node": "Gitea - Verificar repo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Verificar repo": { + "main": [ + [ + { + "node": "Gitea - Asegurar repo público", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Gitea - Crear repo público si no existe", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Asegurar repo público": { + "main": [ + [ + { + "node": "Gitea - Verificar archivo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Crear repo público si no existe": { + "main": [ + [ + { + "node": "Gitea - Verificar archivo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Verificar archivo": { + "main": [ + [ + { + "node": "Gitea - Actualizar workflow.json", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Gitea - Crear workflow.json", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Actualizar workflow.json": { + "main": [ + [ + { + "node": "Marcar respaldado", + "type": "main", + "index": 0 + } + ] + ] + }, + "Gitea - Crear workflow.json": { + "main": [ + [ + { + "node": "Marcar respaldado", + "type": "main", + "index": 0 + } + ] + ] + }, + "Marcar respaldado": { + "main": [ + [ + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Isaac Aracena", + "name": "Version 6fea1c4a", + "description": "", + "autosaved": true, + "workflowPublishHistory": [ + { + "createdAt": "2026-06-05T14:50:23.264Z", + "id": 1149, + "workflowId": "jtJGuwijffvHznH2", + "versionId": "6fea1c4a-860c-43b6-ab05-52a901c0523b", + "event": "activated", + "userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029" + } + ] + } +} \ No newline at end of file