1622 lines
59 KiB
JSON
1622 lines
59 KiB
JSON
{
|
|
"updatedAt": "2026-06-05T14:35:59.420Z",
|
|
"createdAt": "2026-06-04T14:27:30.095Z",
|
|
"id": "J4EmhOad6NtLHzkA",
|
|
"name": "Gitea n8n Auto Backup READY - Publicados a Gitea",
|
|
"description": null,
|
|
"active": true,
|
|
"isArchived": false,
|
|
"nodes": [
|
|
{
|
|
"parameters": {},
|
|
"type": "n8n-nodes-base.manualTrigger",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1328,
|
|
2720
|
|
],
|
|
"id": "a5821719-1c7a-4e3d-a234-af8c216f3fb8",
|
|
"name": "Manual - Ejecutar respaldo ahora",
|
|
"disabled": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "minutes",
|
|
"minutesInterval": 2
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
1328,
|
|
2944
|
|
],
|
|
"id": "7f4aa3c5-4c2a-4cf3-824c-62e66e029853",
|
|
"name": "Schedule - Revisar publicados cada 2 min",
|
|
"disabled": true
|
|
},
|
|
{
|
|
"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": [
|
|
1584,
|
|
2832
|
|
],
|
|
"id": "a671385c-e861-424c-946a-be66db3c264e",
|
|
"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": [
|
|
1840,
|
|
2832
|
|
],
|
|
"id": "e5228ab2-23ea-4bde-9b89-36ff278818a3",
|
|
"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": [
|
|
2096,
|
|
2832
|
|
],
|
|
"id": "71af0fc6-936c-4547-852e-3e0c55c9d168",
|
|
"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": [
|
|
2368,
|
|
2832
|
|
],
|
|
"id": "e53aeb0c-3864-49ca-83c5-fd6aa9e1d6e5",
|
|
"name": "n8n - Obtener workflow completo",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "UPDlAh0YeGhLTTiE",
|
|
"name": "Header Auth account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"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\n// Export limpio: este archivo se puede volver a importar en n8n.\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(JSON.stringify(exportWorkflow, null, 2), 'utf8').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": [
|
|
2624,
|
|
2832
|
|
],
|
|
"id": "e82464b7-af8c-4ed2-b081-11ce1e69429d",
|
|
"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": [
|
|
3088,
|
|
2832
|
|
],
|
|
"id": "1fe59c63-6c63-4b08-bdd7-7373e7489aef",
|
|
"name": "Gitea - Verificar repo",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
},
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $('Preparar repo y archivo').item.json.gitea_base_url.replace(/\\/$/, '') + ($('Preparar repo y archivo').item.json.owner_type === 'org' ? '/api/v1/orgs/' + $('Preparar repo y archivo').item.json.gitea_owner + '/repos' : '/api/v1/user/repos') }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ { name: $('Preparar repo y archivo').item.json.repo_name, description: $('Preparar repo y archivo').item.json.repo_description, private: false, auto_init: true, default_branch: $('Preparar repo y archivo').item.json.branch } }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
3344,
|
|
2992
|
|
],
|
|
"id": "620ad44d-bc47-4f03-b55b-72eafee8768a",
|
|
"name": "Gitea - Crear repo público si no existe",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ $('Preparar repo y archivo').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Preparar repo y archivo').item.json.gitea_owner + '/' + $('Preparar repo y archivo').item.json.repo_name + '/contents/' + $('Preparar repo y archivo').item.json.file_path }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
3616,
|
|
2832
|
|
],
|
|
"id": "cecdfd01-6f37-4a75-89a3-fe54f3791823",
|
|
"name": "Gitea - Verificar archivo",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
},
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "PUT",
|
|
"url": "={{ $('Preparar repo y archivo').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Preparar repo y archivo').item.json.gitea_owner + '/' + $('Preparar repo y archivo').item.json.repo_name + '/contents/' + $('Preparar repo y archivo').item.json.file_path }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ { message: $('Preparar repo y archivo').item.json.commit_update_message, content: $('Preparar repo y archivo').item.json.content, sha: $json.sha, branch: $('Preparar repo y archivo').item.json.branch } }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
3888,
|
|
2704
|
|
],
|
|
"id": "3b0412ca-55fa-47e8-bce9-8a8d041dfda4",
|
|
"name": "Gitea - Actualizar workflow.json",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $('Preparar repo y archivo').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Preparar repo y archivo').item.json.gitea_owner + '/' + $('Preparar repo y archivo').item.json.repo_name + '/contents/' + $('Preparar repo y archivo').item.json.file_path }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ { message: $('Preparar repo y archivo').item.json.commit_create_message, content: $('Preparar repo y archivo').item.json.content, branch: $('Preparar repo y archivo').item.json.branch } }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
3888,
|
|
2992
|
|
],
|
|
"id": "8c24a3c3-654e-459d-8e80-8364f12e5ada",
|
|
"name": "Gitea - Crear workflow.json",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const data = $('Preparar repo y archivo').item.json;\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 action: $input.item.json?.content?.sha ? 'updated_or_created' : 'completed',\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
4144,
|
|
2848
|
|
],
|
|
"id": "d9f13146-b745-4191-9b21-8f22ac906002",
|
|
"name": "Marcar respaldado"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## ✅ Versión definitiva realista\n\nNo usa **n8n Trigger** para otros workflows.\n\nRevisa la API de n8n cada 2 minutos.\n\nSi un workflow publicado es nuevo o cambió, crea/actualiza su repo público en Gitea.",
|
|
"height": 260,
|
|
"width": 360
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1264,
|
|
2400
|
|
],
|
|
"id": "48ad24a9-4547-4a98-8630-eb54db74be1e",
|
|
"name": "Nota - Por qué este sí respalda otros workflows"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## ⚙️ Configuración\n\nEn **Config** ajusta:\n\n- `n8n_base_url`\n- `gitea_owner`\n- `owner_type`: user u org\n\nCredenciales:\n- n8n: Header `X-N8N-API-KEY`\n- Gitea: Header `Authorization` = `token TU_TOKEN`",
|
|
"height": 280,
|
|
"width": 360
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1536,
|
|
2400
|
|
],
|
|
"id": "488f256e-94c3-4e60-a46a-a75adf36e30b",
|
|
"name": "Nota - Configuración"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 🌍 Repos públicos\n\nLos repos nuevos se crean con:\n\n`private: false`\n\n`only_published_workflows = true` respalda solo workflows publicados/activos.",
|
|
"height": 220,
|
|
"width": 340
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
2976,
|
|
2400
|
|
],
|
|
"id": "b3460de4-6a72-455a-9f4a-4cefcfac1bfa",
|
|
"name": "Nota - Repos públicos"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "minutes",
|
|
"minutesInterval": 1
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
3520,
|
|
3392
|
|
],
|
|
"id": "b6b3e213-0226-42df-8d2c-ba7477a899c2",
|
|
"name": "Schedule - Revisar publicados cada 1 min"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.splitInBatches",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
5200,
|
|
3280
|
|
],
|
|
"id": "17aff29b-4328-423d-9642-399d3a932f94",
|
|
"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": [
|
|
5728,
|
|
3168
|
|
],
|
|
"id": "e289af3e-59ff-4a10-8017-dc50133aa487",
|
|
"name": "Gitea - Asegurar repo público"
|
|
},
|
|
{
|
|
"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": [
|
|
3792,
|
|
3280
|
|
],
|
|
"id": "b4c65300-1a0e-4f27-8051-d9edfcf9ebeb",
|
|
"name": "Config1"
|
|
},
|
|
{
|
|
"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": [
|
|
4048,
|
|
3280
|
|
],
|
|
"id": "a2a41432-27df-4ada-8e57-e59759d47702",
|
|
"name": "n8n - Listar workflows1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const cfg = $('Config1').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": [
|
|
4304,
|
|
3280
|
|
],
|
|
"id": "170f2b13-74e4-4ccf-8ff3-c743156539ed",
|
|
"name": "Filtrar workflows nuevos o modificados1"
|
|
},
|
|
{
|
|
"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": [
|
|
4560,
|
|
3280
|
|
],
|
|
"id": "2d651085-efd9-4fa7-8252-f63e159790e8",
|
|
"name": "n8n - Obtener workflow completo1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForEachItem",
|
|
"jsCode": "const cfg = $('Filtrar workflows nuevos o modificados1').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\n// Export limpio: este archivo se puede volver a importar en n8n.\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(JSON.stringify(exportWorkflow, null, 2), 'utf8').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": [
|
|
4848,
|
|
3280
|
|
],
|
|
"id": "523180ce-ea6f-41f6-989d-bec904e3a6e7",
|
|
"name": "Preparar repo y archivo1"
|
|
},
|
|
{
|
|
"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": [
|
|
5472,
|
|
3280
|
|
],
|
|
"id": "bd3c2388-6be5-46a6-a522-859de7f06182",
|
|
"name": "Gitea - Verificar repo1",
|
|
"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": [
|
|
5728,
|
|
3456
|
|
],
|
|
"id": "906c2170-4b0f-46cb-8e7c-1b2abc0ce050",
|
|
"name": "Gitea - Crear repo público si no existe1"
|
|
},
|
|
{
|
|
"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": [
|
|
5984,
|
|
3280
|
|
],
|
|
"id": "936fbf60-9fb3-4c0f-adc2-1f15db90372c",
|
|
"name": "Gitea - Verificar archivo1",
|
|
"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": [
|
|
6272,
|
|
3152
|
|
],
|
|
"id": "47622c85-e964-4b21-b405-783fedc37fe7",
|
|
"name": "Gitea - Actualizar workflow.json1"
|
|
},
|
|
{
|
|
"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": [
|
|
6272,
|
|
3456
|
|
],
|
|
"id": "e6cbe196-d2df-402d-9948-a762b2ac367a",
|
|
"name": "Gitea - Crear workflow.json1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForEachItem",
|
|
"jsCode": "const data = $('Loop Over Items').item.json;\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": [
|
|
6544,
|
|
3296
|
|
],
|
|
"id": "b6f40154-8206-45af-b84d-d286ca4c12b5",
|
|
"name": "Marcar respaldado1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## ✅ READY realista\n\nRevisa la API de n8n cada 1 minuto.\n\nCuando un workflow publicado sea nuevo o cambie, crea/actualiza su repo en Gitea.\n\nProcesa uno por uno para evitar que todos caigan en el mismo repo.",
|
|
"height": 260,
|
|
"width": 360
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
3520,
|
|
2848
|
|
],
|
|
"id": "5da31cdc-fb11-452a-a1ef-fa5d9a74c702",
|
|
"name": "Nota - Por qué este sí respalda otros workflows1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## ⚙️ Configuración\n\nEn **Config** revisa:\n\n- `n8n_base_url`\n- `gitea_owner`\n- `owner_type`: user u org\n\nCredenciales:\n- n8n: Header `X-N8N-API-KEY`\n- Gitea: Header `Authorization` = `token TU_TOKEN`",
|
|
"height": 280,
|
|
"width": 360
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
3888,
|
|
2848
|
|
],
|
|
"id": "7f963fa1-e3fe-4349-8f42-c57fd367d354",
|
|
"name": "Nota - Configuración1"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 🌍 Repos públicos\n\nRepos nuevos:\n\n`private: false`\n\nRepos existentes:\n\nse intenta forzar `private: false` antes de actualizar el JSON.",
|
|
"height": 220,
|
|
"width": 340
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
5568,
|
|
2848
|
|
],
|
|
"id": "b52aa2c5-5147-448f-80f3-af2a19a6b2e0",
|
|
"name": "Nota - Repos públicos1"
|
|
}
|
|
],
|
|
"connections": {
|
|
"Manual - Ejecutar respaldo ahora": {
|
|
"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": "Gitea - Verificar repo",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gitea - Verificar repo": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Gitea - Verificar archivo",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Gitea - Crear repo público si no existe",
|
|
"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
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Schedule - Revisar publicados cada 2 min": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Config",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Marcar respaldado": {
|
|
"main": [
|
|
[]
|
|
]
|
|
},
|
|
"Schedule - Revisar publicados cada 1 min": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Config1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Loop Over Items": {
|
|
"main": [
|
|
[],
|
|
[
|
|
{
|
|
"node": "Gitea - Verificar repo1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gitea - Asegurar repo público": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Gitea - Verificar archivo1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Config1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "n8n - Listar workflows1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"n8n - Listar workflows1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Filtrar workflows nuevos o modificados1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Filtrar workflows nuevos o modificados1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "n8n - Obtener workflow completo1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"n8n - Obtener workflow completo1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Preparar repo y archivo1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Preparar repo y archivo1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Loop Over Items",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gitea - Verificar repo1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Gitea - Asegurar repo público",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Gitea - Crear repo público si no existe1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gitea - Crear repo público si no existe1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Gitea - Verificar archivo1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gitea - Verificar archivo1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Gitea - Actualizar workflow.json1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Gitea - Crear workflow.json1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gitea - Actualizar workflow.json1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Marcar respaldado1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gitea - Crear workflow.json1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Marcar respaldado1",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Marcar respaldado1": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Loop Over Items",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"binaryMode": "separate"
|
|
},
|
|
"staticData": {
|
|
"node:Schedule - Revisar cambios cada 10 min": {
|
|
"recurrenceRules": []
|
|
},
|
|
"global": {
|
|
"backedUpWorkflows": {
|
|
"2Mxu9x13OYIn1tz6": {
|
|
"name": "Identificador de productos en una factura",
|
|
"repo_name": "identificador-de-productos-en-una-factura",
|
|
"file_path": "identificador-de-productos-en-una-factura.json",
|
|
"updatedAt": "2026-05-14T14:12:02.000Z",
|
|
"backedUpAt": "2026-06-05T14:00:52.682Z"
|
|
},
|
|
"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:46:38.410Z"
|
|
}
|
|
}
|
|
},
|
|
"node:Schedule - Revisar publicados cada 2 min": {
|
|
"recurrenceRules": []
|
|
}
|
|
},
|
|
"meta": {
|
|
"templateCredsSetupCompleted": true
|
|
},
|
|
"versionId": "743c8afd-5f7e-4415-8ce4-b42369310b07",
|
|
"activeVersionId": "2ba15dd4-1951-4980-9132-78ad48427fea",
|
|
"versionCounter": 148,
|
|
"triggerCount": 1,
|
|
"shared": [
|
|
{
|
|
"updatedAt": "2026-06-04T14:27:30.098Z",
|
|
"createdAt": "2026-06-04T14:27:30.098Z",
|
|
"role": "workflow:owner",
|
|
"workflowId": "J4EmhOad6NtLHzkA",
|
|
"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-06-05T14:20:41.000Z",
|
|
"createdAt": "2026-06-05T14:18:28.683Z",
|
|
"versionId": "2ba15dd4-1951-4980-9132-78ad48427fea",
|
|
"workflowId": "J4EmhOad6NtLHzkA",
|
|
"nodes": [
|
|
{
|
|
"parameters": {},
|
|
"type": "n8n-nodes-base.manualTrigger",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1328,
|
|
2720
|
|
],
|
|
"id": "a5821719-1c7a-4e3d-a234-af8c216f3fb8",
|
|
"name": "Manual - Ejecutar respaldo ahora"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "minutes",
|
|
"minutesInterval": 2
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
1328,
|
|
2944
|
|
],
|
|
"id": "7f4aa3c5-4c2a-4cf3-824c-62e66e029853",
|
|
"name": "Schedule - Revisar publicados cada 2 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": true,
|
|
"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": [
|
|
1584,
|
|
2832
|
|
],
|
|
"id": "a671385c-e861-424c-946a-be66db3c264e",
|
|
"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": [
|
|
1840,
|
|
2832
|
|
],
|
|
"id": "e5228ab2-23ea-4bde-9b89-36ff278818a3",
|
|
"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": [
|
|
2096,
|
|
2832
|
|
],
|
|
"id": "71af0fc6-936c-4547-852e-3e0c55c9d168",
|
|
"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": [
|
|
2368,
|
|
2832
|
|
],
|
|
"id": "e53aeb0c-3864-49ca-83c5-fd6aa9e1d6e5",
|
|
"name": "n8n - Obtener workflow completo",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "UPDlAh0YeGhLTTiE",
|
|
"name": "Header Auth account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"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\n// Export limpio: este archivo se puede volver a importar en n8n.\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(JSON.stringify(exportWorkflow, null, 2), 'utf8').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": [
|
|
2624,
|
|
2832
|
|
],
|
|
"id": "e82464b7-af8c-4ed2-b081-11ce1e69429d",
|
|
"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": [
|
|
2880,
|
|
2832
|
|
],
|
|
"id": "1fe59c63-6c63-4b08-bdd7-7373e7489aef",
|
|
"name": "Gitea - Verificar repo",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
},
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $('Preparar repo y archivo').item.json.gitea_base_url.replace(/\\/$/, '') + ($('Preparar repo y archivo').item.json.owner_type === 'org' ? '/api/v1/orgs/' + $('Preparar repo y archivo').item.json.gitea_owner + '/repos' : '/api/v1/user/repos') }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ { name: $('Preparar repo y archivo').item.json.repo_name, description: $('Preparar repo y archivo').item.json.repo_description, private: false, auto_init: true, default_branch: $('Preparar repo y archivo').item.json.branch } }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
3136,
|
|
2992
|
|
],
|
|
"id": "620ad44d-bc47-4f03-b55b-72eafee8768a",
|
|
"name": "Gitea - Crear repo público si no existe",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "={{ $('Preparar repo y archivo').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Preparar repo y archivo').item.json.gitea_owner + '/' + $('Preparar repo y archivo').item.json.repo_name + '/contents/' + $('Preparar repo y archivo').item.json.file_path }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
3408,
|
|
2832
|
|
],
|
|
"id": "cecdfd01-6f37-4a75-89a3-fe54f3791823",
|
|
"name": "Gitea - Verificar archivo",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
},
|
|
"onError": "continueErrorOutput"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "PUT",
|
|
"url": "={{ $('Preparar repo y archivo').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Preparar repo y archivo').item.json.gitea_owner + '/' + $('Preparar repo y archivo').item.json.repo_name + '/contents/' + $('Preparar repo y archivo').item.json.file_path }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ { message: $('Preparar repo y archivo').item.json.commit_update_message, content: $('Preparar repo y archivo').item.json.content, sha: $json.sha, branch: $('Preparar repo y archivo').item.json.branch } }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
3680,
|
|
2704
|
|
],
|
|
"id": "3b0412ca-55fa-47e8-bce9-8a8d041dfda4",
|
|
"name": "Gitea - Actualizar workflow.json",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $('Preparar repo y archivo').item.json.gitea_base_url.replace(/\\/$/, '') + '/api/v1/repos/' + $('Preparar repo y archivo').item.json.gitea_owner + '/' + $('Preparar repo y archivo').item.json.repo_name + '/contents/' + $('Preparar repo y archivo').item.json.file_path }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ { message: $('Preparar repo y archivo').item.json.commit_create_message, content: $('Preparar repo y archivo').item.json.content, branch: $('Preparar repo y archivo').item.json.branch } }}",
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
3680,
|
|
2992
|
|
],
|
|
"id": "8c24a3c3-654e-459d-8e80-8364f12e5ada",
|
|
"name": "Gitea - Crear workflow.json",
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "goGJ7p07L5ygli0D",
|
|
"name": "Header Auth account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const data = $('Preparar repo y archivo').item.json;\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 action: $input.item.json?.content?.sha ? 'updated_or_created' : 'completed',\n }\n}];"
|
|
},
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
3936,
|
|
2848
|
|
],
|
|
"id": "d9f13146-b745-4191-9b21-8f22ac906002",
|
|
"name": "Marcar respaldado"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## ✅ Versión definitiva realista\n\nNo usa **n8n Trigger** para otros workflows.\n\nRevisa la API de n8n cada 2 minutos.\n\nSi un workflow publicado es nuevo o cambió, crea/actualiza su repo público en Gitea.",
|
|
"height": 260,
|
|
"width": 360
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1264,
|
|
2400
|
|
],
|
|
"id": "48ad24a9-4547-4a98-8630-eb54db74be1e",
|
|
"name": "Nota - Por qué este sí respalda otros workflows"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## ⚙️ Configuración\n\nEn **Config** ajusta:\n\n- `n8n_base_url`\n- `gitea_owner`\n- `owner_type`: user u org\n\nCredenciales:\n- n8n: Header `X-N8N-API-KEY`\n- Gitea: Header `Authorization` = `token TU_TOKEN`",
|
|
"height": 280,
|
|
"width": 360
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1536,
|
|
2400
|
|
],
|
|
"id": "488f256e-94c3-4e60-a46a-a75adf36e30b",
|
|
"name": "Nota - Configuración"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"content": "## 🌍 Repos públicos\n\nLos repos nuevos se crean con:\n\n`private: false`\n\n`only_published_workflows = true` respalda solo workflows publicados/activos.",
|
|
"height": 220,
|
|
"width": 340
|
|
},
|
|
"type": "n8n-nodes-base.stickyNote",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
2976,
|
|
2400
|
|
],
|
|
"id": "b3460de4-6a72-455a-9f4a-4cefcfac1bfa",
|
|
"name": "Nota - Repos públicos"
|
|
}
|
|
],
|
|
"connections": {
|
|
"Manual - Ejecutar respaldo ahora": {
|
|
"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": "Gitea - Verificar repo",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gitea - Verificar repo": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Gitea - Verificar archivo",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Gitea - Crear repo público si no existe",
|
|
"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
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Schedule - Revisar publicados cada 2 min": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Config",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"authors": "Isaac Aracena",
|
|
"name": "Version 2ba15dd4",
|
|
"description": "",
|
|
"autosaved": true,
|
|
"workflowPublishHistory": [
|
|
{
|
|
"createdAt": "2026-06-05T14:18:29.772Z",
|
|
"id": 1142,
|
|
"workflowId": "J4EmhOad6NtLHzkA",
|
|
"versionId": "2ba15dd4-1951-4980-9132-78ad48427fea",
|
|
"event": "activated",
|
|
"userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
|
|
},
|
|
{
|
|
"createdAt": "2026-06-05T14:20:41.806Z",
|
|
"id": 1144,
|
|
"workflowId": "J4EmhOad6NtLHzkA",
|
|
"versionId": "2ba15dd4-1951-4980-9132-78ad48427fea",
|
|
"event": "activated",
|
|
"userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
|
|
},
|
|
{
|
|
"createdAt": "2026-06-05T14:18:56.829Z",
|
|
"id": 1143,
|
|
"workflowId": "J4EmhOad6NtLHzkA",
|
|
"versionId": "2ba15dd4-1951-4980-9132-78ad48427fea",
|
|
"event": "deactivated",
|
|
"userId": "0a88c0b1-928e-4412-896e-c5d1c99b2029"
|
|
}
|
|
]
|
|
}
|
|
} |