{
  "name": "Informe de la central → plan de la semana de la tienda",
  "nodes": [
    {
      "parameters": {
        "content": "> Plantilla de partida de fernandorivera.es. Úsala y adáptala libremente; sin garantía; revisa los datos que tratas.\n\n## Del informe de la central al plan de la semana\nRecoge el Excel o CSV que manda la central, **lo limpia y calcula las desviaciones con código** (venta frente a objetivo, cumplimiento, LFL, ticket medio, venta por hora, roturas) y te manda un correo con la tabla y el prompt listo para pegar en la IA que tu empresa haya aprobado.\n\n- No toca el ERP ni el BI: solo lee el archivo que ya recibes.\n- Pensado para n8n **autoalojado** en la infraestructura de la empresa.\n- **Es una plantilla de partida:** revísala con tu equipo de sistemas antes de usarla con datos reales.\n\nGuía completa en fernandorivera.es.",
        "height": 400,
        "width": 460,
        "color": 4
      },
      "id": "43981633-1bc1-52c8-8bd8-c90425ac771d",
      "name": "Nota: qué hace",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -520,
        -560
      ]
    },
    {
      "parameters": {
        "content": "## Qué datos pasan y cuáles no\nEl nodo de código se queda **solo** con estas columnas: tienda, familia, venta, objetivo, venta año anterior, tickets, unidades, horas, roturas y cobertura. Todo lo demás se descarta y el correo te dice qué columnas ha quitado.\n\nNunca metas en el archivo: nombres de clientes, tarjetas de fidelización, nombres o DNI de empleados, ni ventas por vendedor.\n\nLa rama de IA automática está **desactivada**. Actívala solo si sistemas te da un servicio de IA aprobado y su dirección.",
        "height": 400,
        "width": 440,
        "color": 3
      },
      "id": "bce9fbb9-a44b-5810-8f5e-89b9ee37d66b",
      "name": "Nota: datos",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -600
      ]
    },
    {
      "parameters": {
        "content": "## Configura\n1. **Email Trigger (IMAP):** credencial de tu buzón y el asunto exacto del informe en *Custom Email Rules*. Si tu empresa no permite IMAP, cambia este nodo por el disparador de Microsoft Outlook o usa la entrada por carpeta (abajo, desactivada).\n2. **Limpiar y calcular:** en `CONFIG` pon tus tiendas, el umbral y los nombres de columna de tu archivo.\n3. **Prompt y destinatario:** tu correo y el remitente.\n4. **Send Email:** credencial SMTP de la empresa.\n\nPrueba primero con un archivo de semanas pasadas.",
        "height": 400,
        "width": 440,
        "color": 6
      },
      "id": "dcb8f6b8-0389-5c6c-bb76-71c3ecff9248",
      "name": "Nota: configura",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        480,
        -600
      ]
    },
    {
      "parameters": {
        "content": "### Fallos típicos\n- **«No encuentro las columnas»:** añade el nombre exacto de tu cabecera en `CONFIG.columnas`.\n- **Tildes rotas en el CSV:** cambia la codificación del nodo CSV (Latin1 o UTF-8).\n- **Todo en una columna:** el separador del CSV es `;` o `,`. Cámbialo en el nodo.\n- **Cifras multiplicadas:** el archivo trae subtotales sin la palabra «Total». Fíltralos en el código.\n- **PDF:** pide a la central la versión en Excel o CSV. Leer tablas de un PDF falla mucho.",
        "height": 340,
        "width": 400,
        "color": 7
      },
      "id": "06c90446-2d6a-5db6-976d-9149806308b4",
      "name": "Nota: fallos típicos",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        960,
        -600
      ]
    },
    {
      "parameters": {
        "mailbox": "INBOX",
        "postProcessAction": "read",
        "format": "simple",
        "downloadAttachments": true,
        "dataPropertyAttachmentsPrefixName": "attachment_",
        "options": {
          "customEmailConfig": "[\"UNSEEN\", [\"SUBJECT\", \"ASUNTO_DEL_INFORME_SEMANAL\"]]"
        }
      },
      "id": "2c07a0a4-37fd-58b1-92a9-de859e16bc27",
      "name": "Llega el informe de la central",
      "type": "n8n-nodes-base.emailReadImap",
      "typeVersion": 2,
      "position": [
        -480,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "b94dbc08-3340-5327-a1d9-59eb10e133ee",
              "leftValue": "={{ ($binary.attachment_0?.fileExtension || $binary.attachment_0?.fileName || '').toLowerCase() }}",
              "rightValue": "csv",
              "operator": {
                "type": "string",
                "operation": "endsWith"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "0c84a02e-b522-5e7a-af29-33c7f51a8582",
      "name": "¿CSV o Excel?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -260,
        0
      ]
    },
    {
      "parameters": {
        "operation": "csv",
        "binaryPropertyName": "attachment_0",
        "options": {
          "delimiter": ";",
          "encoding": "latin1",
          "headerRow": true
        }
      },
      "id": "74077cd6-c98c-51b1-a9f0-8355cddf1e93",
      "name": "Leer CSV",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        -40,
        -100
      ]
    },
    {
      "parameters": {
        "operation": "xlsx",
        "binaryPropertyName": "attachment_0",
        "options": {
          "headerRow": true
        }
      },
      "id": "55da57f5-749f-5084-a9dc-1aa24845a081",
      "name": "Leer Excel",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        -40,
        100
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "weeksInterval": 1,
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 7,
              "triggerAtMinute": 0
            }
          ]
        }
      },
      "id": "821eac0f-b650-5b0b-81d4-2a8457a6567a",
      "name": "Cada lunes 7:00 (carpeta)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -480,
        320
      ],
      "disabled": true
    },
    {
      "parameters": {
        "fileSelector": "/RUTA/PERMITIDA/informe-semanal.xlsx",
        "options": {}
      },
      "id": "d06f59a6-2aca-5c7f-804a-b9319a80a8e9",
      "name": "Leer archivo de la carpeta",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        -260,
        320
      ],
      "disabled": true
    },
    {
      "parameters": {
        "operation": "xlsx",
        "binaryPropertyName": "data",
        "options": {
          "headerRow": true
        }
      },
      "id": "431e36ae-ea66-505d-a57e-404d6f34ae02",
      "name": "Leer Excel (carpeta)",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        -40,
        320
      ],
      "disabled": true
    },
    {
      "parameters": {
        "jsCode": "// Limpia el informe de la central y calcula las desviaciones. La IA no calcula nada.\n// Solo se quedan las columnas de la lista: cualquier otra (vendedor, empleado, cliente…) se descarta aquí.\nconst CONFIG = {\n  tiendas: [],            // vacío = todas las del archivo. Ejemplo: ['T012', 'T034']\n  umbral_prioridad: 95,   // % de cumplimiento del objetivo por debajo del cual la línea es prioridad alta\n  peores: 5,              // cuántas familias con peor desviación se enseñan por tienda\n  mejores: 3,\n  // Nombres posibles de cada columna en tu archivo. Añade los de tu empresa (da igual mayúsculas y tildes).\n  columnas: {\n    tienda:    ['tienda', 'centro', 'store', 'cod tienda', 'codigo tienda'],\n    familia:   ['familia', 'seccion', 'departamento', 'categoria'],\n    venta:     ['venta', 'venta neta', 'ventas', 'venta real'],\n    objetivo:  ['objetivo', 'presupuesto', 'budget', 'venta objetivo'],\n    venta_aa:  ['venta aa', 'venta ano anterior', 'venta ly', 'ly'],\n    tickets:   ['tickets', 'transacciones', 'n tickets'],\n    unidades:  ['unidades', 'uds'],\n    horas:     ['horas', 'horas trabajadas'],\n    roturas:   ['roturas', 'referencias sin stock', 'sin stock'],\n    cobertura: ['cobertura', 'cobertura dias', 'dias de cobertura'],\n  },\n};\n\nconst norm = s => String(s ?? '').normalize('NFD').replace(/[̀-ͯ]/g, '')\n  .toLowerCase().replace(/[^a-z0-9 ]/g, '').replace(/\\s+/g, ' ').trim();\nfunction num(v) {\n  if (v === null || v === undefined || v === '') return null;\n  if (typeof v === 'number') return v;\n  let s = String(v).trim();\n  const neg = /^\\(.*\\)$/.test(s);\n  s = s.replace(/[()€%\\s]/g, '');\n  if (s.includes(',')) s = s.replace(/\\./g, '').replace(',', '.');\n  else if (/^-?\\d{1,3}(\\.\\d{3})+$/.test(s)) s = s.replace(/\\./g, ''); // 11.000 = once mil\n  const n = parseFloat(s);\n  return isNaN(n) ? null : (neg ? -n : n);\n}\nfunction mapear(pares) {\n  const m = {};\n  for (const [key, label] of pares) {\n    const n = norm(label);\n    for (const [campo, alias] of Object.entries(CONFIG.columnas)) {\n      if (!m[campo] && alias.map(norm).includes(n)) m[campo] = key;\n    }\n  }\n  return m;\n}\n\nconst filas = $input.all().map(i => i.json).filter(r => r && Object.keys(r).length);\nlet cabecera = [...new Set(filas.slice(0, 50).flatMap(r => Object.keys(r)))].map(k => [k, k]);\nlet mapa = mapear(cabecera);\nlet inicio = 0;\n// Si el archivo trae filas de título antes de la cabecera, la busca en las 20 primeras filas.\nif (!mapa.venta || !(mapa.tienda || mapa.familia)) {\n  for (let i = 0; i < Math.min(20, filas.length); i++) {\n    const pares = Object.entries(filas[i]).map(([k, v]) => [k, String(v)]);\n    const m = mapear(pares);\n    if (m.venta && (m.tienda || m.familia)) { mapa = m; inicio = i + 1; cabecera = pares; break; }\n  }\n}\n\nconst hoy = $now.setZone('Europe/Madrid');\nconst semana = hoy.startOf('day').minus({ days: hoy.weekday - 1 }).toFormat('dd/MM/yyyy');\n\nif (!mapa.venta || !(mapa.tienda || mapa.familia)) {\n  const vistas = cabecera.map(p => p[1]).slice(0, 30).join(' · ');\n  return [{ json: { ok: false, semana, tabla: '', datos_ia: '',\n    resumen: `No encuentro las columnas de venta y tienda o familia. Cabeceras leídas: ${vistas || '(ninguna)'}. Añade sus nombres en CONFIG.columnas del nodo de código.` } }];\n}\n\nconst usadas = new Set(Object.values(mapa));\nconst ignoradas = cabecera.filter(([k]) => !usadas.has(k)).map(p => p[1]).filter(x => x && !/^__EMPTY/.test(x));\n\nconst datos = [];\nfor (const r of filas.slice(inicio)) {\n  const get = c => mapa[c] !== undefined ? r[mapa[c]] : undefined;\n  const tienda = String(get('tienda') ?? '').trim() || '(sin tienda)';\n  const familia = String(get('familia') ?? '').trim() || '(total)';\n  if (/total/.test(norm(tienda)) || /total/.test(norm(familia))) continue; // quita subtotales\n  if (CONFIG.tiendas.length && !CONFIG.tiendas.map(norm).includes(norm(tienda))) continue;\n  const venta = num(get('venta')), objetivo = num(get('objetivo'));\n  if (venta === null && objetivo === null) continue; // filas vacías o de texto\n  datos.push({ tienda, familia, venta: venta || 0, objetivo, venta_aa: num(get('venta_aa')),\n    tickets: num(get('tickets')), unidades: num(get('unidades')), horas: num(get('horas')),\n    roturas: num(get('roturas')), cobertura: num(get('cobertura')) });\n}\n\nconst miles = n => String(Math.round(n)).replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\nconst eur = n => n === null || n === undefined ? 's/d' : miles(n) + ' €';\nconst p1 = n => n === null ? 's/d' : n.toFixed(1).replace('.', ',') + ' %';\nconst signo = n => n === null ? 's/d' : (n >= 0 ? '+' : '') + n.toFixed(1).replace('.', ',') + ' %';\nconst cumpl = (v, o) => o ? v / o * 100 : null;\nconst lfl = (v, aa) => aa ? (v / aa - 1) * 100 : null;\nconst prioridad = c => c === null ? 'sin objetivo' : c < CONFIG.umbral_prioridad ? 'ALTA' : c < 100 ? 'media' : 'ok';\nconst suma = (arr, k) => arr.some(x => x[k] !== null) ? arr.reduce((s, x) => s + (x[k] || 0), 0) : null;\n\nconst porTienda = {};\ndatos.forEach(d => { (porTienda[d.tienda] = porTienda[d.tienda] || []).push(d); });\n\nconst bloques = [];\nfor (const [tienda, ls] of Object.entries(porTienda)) {\n  const v = suma(ls, 'venta'), o = suma(ls, 'objetivo'), aa = suma(ls, 'venta_aa');\n  const t = suma(ls, 'tickets'), u = suma(ls, 'unidades'), h = suma(ls, 'horas');\n  const c = cumpl(v, o);\n  const lineas = [\n    `TIENDA ${tienda}: venta ${eur(v)} | objetivo ${eur(o)} | desviación ${o !== null ? eur(v - o) : 's/d'} | cumplimiento ${p1(c)} (${prioridad(c)}) | LFL ${signo(lfl(v, aa))}`,\n  ];\n  if (t) lineas.push(`  Tickets ${miles(t)} | ticket medio ${eur(v / t)}${u ? ' | UPT ' + (u / t).toFixed(2).replace('.', ',') : ''}`);\n  const vConHoras = ls.filter(x => x.horas).reduce((s, x) => s + x.venta, 0); // solo la venta de las filas que traen horas\n  if (h) lineas.push(`  Horas ${miles(h)} | venta por hora ${eur(vConHoras / h)}`);\n  const fam = ls.filter(x => x.objetivo !== null).map(x => ({ ...x, desv: x.venta - x.objetivo, c: cumpl(x.venta, x.objetivo) }));\n  const peores = [...fam].sort((a, b) => a.desv - b.desv).slice(0, CONFIG.peores).filter(x => x.desv < 0);\n  const mejores = [...fam].sort((a, b) => b.desv - a.desv).slice(0, CONFIG.mejores).filter(x => x.desv > 0);\n  const f = x => `    - ${x.familia}: ${eur(x.desv)} (cumplimiento ${p1(x.c)}, ${prioridad(x.c)}, LFL ${signo(lfl(x.venta, x.venta_aa))})`;\n  if (peores.length) lineas.push('  Familias por debajo del objetivo (peor primero):', ...peores.map(f));\n  if (mejores.length) lineas.push('  Familias por encima del objetivo:', ...mejores.map(f));\n  const stock = ls.filter(x => (x.roturas || 0) > 0 || x.cobertura !== null)\n    .sort((a, b) => (b.roturas || 0) - (a.roturas || 0) || (a.cobertura ?? 999) - (b.cobertura ?? 999)).slice(0, 5);\n  if (stock.length) lineas.push('  Stock (según el informe):', ...stock.map(x => `    - ${x.familia}: roturas ${x.roturas ?? 's/d'} | cobertura ${x.cobertura ?? 's/d'} días`));\n  bloques.push({ desv: o !== null ? v - o : 0, texto: lineas.join('\\n') });\n}\nbloques.sort((a, b) => a.desv - b.desv);\n\nconst avisos = [];\nif (!mapa.objetivo) avisos.push('El archivo no trae columna de objetivo: no hay desviaciones, solo venta y LFL.');\nif (!mapa.venta_aa) avisos.push('El archivo no trae venta del año anterior: el LFL sale como s/d.');\nif (ignoradas.length) avisos.push(`Columnas descartadas antes de llegar a la IA: ${ignoradas.slice(0, 20).join(', ')}.`);\nif (!datos.length) avisos.push('No ha quedado ninguna fila con datos después de limpiar. Revisa el filtro de tiendas.');\n\nconst tabla = bloques.map(b => b.texto).join('\\n\\n');\nconst datos_ia = `Semana del ${semana}. Umbral de prioridad: cumplimiento por debajo del ${CONFIG.umbral_prioridad} %.\\n\\n${tabla}\\n\\nAvisos de datos:\\n${avisos.length ? avisos.map(a => '- ' + a).join('\\n') : '- Ninguno'}`;\n\nreturn [{ json: { ok: datos.length > 0, semana, filas: datos.length, tabla, datos_ia,\n  resumen: `${datos.length} filas válidas de ${Object.keys(porTienda).length} tienda(s).` } }];\n"
      },
      "id": "f549782b-88a9-57d7-9e2f-21151910cf5d",
      "name": "Limpiar y calcular desviaciones",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        200,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "d3a9bd69-1d0f-5970-8340-a59fdc08c250",
              "name": "prompt",
              "value": "Eres el ayudante de un encargado de tienda (o de un jefe de zona) de una cadena de retail. Te paso los resultados de la semana YA CALCULADOS a partir del informe de la central: venta, objetivo, desviación, cumplimiento, LFL y, si los hay, tickets, horas y stock por tienda y familia.\n\nCon esos datos, escribe el PLAN DE LA SEMANA en este formato:\n1. PRIORIDADES (máximo 3): qué tienda o familia, por qué (cita el dato exacto) y qué se espera conseguir.\n2. CUADRANTE: qué franjas o días reforzar o aligerar SOLO si los datos de horas o venta por hora lo justifican. Si no hay datos de horas, escribe «sin datos de horas: revisar con el cuadrante actual». No nombres a personas.\n3. REPOSICIÓN: qué familias revisar primero por roturas o cobertura baja, si el informe lo trae.\n4. FOCO COMERCIAL: una acción de venta por prioridad (exposición, venta sugerida, argumentario).\n5. PREGUNTAS PARA LA REUNIÓN DEL LUNES: 3 preguntas para el equipo o los encargados.\n\nReglas: no calcules ni cambies cifras, usa solo las de los datos. No inventes causas: si los datos no lo explican, escribe «sin causa clara: revisar en tienda». Si hay avisos de datos, empieza por ellos. Máximo 25 líneas, español de España, sin saludos.",
              "type": "string"
            },
            {
              "id": "1ce07949-7112-50af-803e-32491f41798a",
              "name": "email_destino",
              "value": "TU_CORREO_DEL_TRABAJO",
              "type": "string"
            },
            {
              "id": "b8a74476-b92c-5996-878f-c190ae41eac2",
              "name": "email_remitente",
              "value": "REMITENTE_AUTORIZADO",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "b89dae1a-5303-5fb8-af7d-bfff7c35ab71",
      "name": "Prompt y destinatario",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        420,
        0
      ]
    },
    {
      "parameters": {
        "fromEmail": "={{ $json.email_remitente }}",
        "toEmail": "={{ $json.email_destino }}",
        "subject": "=Plan de la semana del {{ $json.semana }}: datos listos{{ $json.ok ? '' : ' (REVISAR ARCHIVO)' }}",
        "emailFormat": "text",
        "text": "=Plan de la semana del {{ $json.semana }}: datos listos\n\n{{ $json.resumen }}\n\nDESVIACIONES (calculadas por el flujo, no por la IA)\n{{ $json.tabla || '(sin datos)' }}\n\n────────────────────────────\nCOPIA DESDE AQUÍ Y PÉGALO EN LA IA APROBADA POR TU EMPRESA (por ejemplo, Copilot de Microsoft 365 con tu cuenta del trabajo):\n\n{{ $json.prompt }}\n\nDATOS:\n{{ $json.datos_ia }}\n────────────────────────────\n\nRevisa el plan que te devuelva antes de compartirlo: las cifras buenas son las de arriba.",
        "options": {}
      },
      "id": "b174b118-a348-5a01-a89f-ae9279e54982",
      "name": "Enviar datos y prompt",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        660,
        -100
      ],
      "webhookId": "f0921cbb-d58f-5409-bdb7-f59c0a715c0a"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "URL_DEL_SERVICIO_DE_IA_APROBADO",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ messages: [ { role: 'system', content: $json.prompt }, { role: 'user', content: $json.datos_ia } ], temperature: 0.2 }) }}",
        "options": {}
      },
      "id": "449c5c24-e528-5fbd-b404-eb88c1cc3fad",
      "name": "IA aprobada por la empresa (opcional)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        160
      ],
      "disabled": true
    },
    {
      "parameters": {
        "fromEmail": "={{ $('Prompt y destinatario').first().json.email_remitente }}",
        "toEmail": "={{ $('Prompt y destinatario').first().json.email_destino }}",
        "subject": "=Plan de la semana del {{ $('Prompt y destinatario').first().json.semana }} (borrador IA)",
        "emailFormat": "text",
        "text": "=Plan de la semana del {{ $('Prompt y destinatario').first().json.semana }} (borrador de la IA: revísalo)\n\n{{ $json.choices?.[0]?.message?.content || $json.output || JSON.stringify($json).slice(0, 3000) }}\n\n────────────────────────────\nDESVIACIONES (calculadas por el flujo, no por la IA)\n{{ $('Prompt y destinatario').first().json.tabla }}",
        "options": {}
      },
      "id": "5b893894-c70b-50dc-b43c-80f8b8115e0c",
      "name": "Enviar plan (IA de la empresa)",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        900,
        160
      ],
      "disabled": true,
      "webhookId": "a0e1ae79-452f-5f5b-91c5-e23bb8bea1f9"
    },
    {
      "parameters": {
        "content": "### Rama de IA automática (desactivada)\nSolo si tu empresa te da un servicio de IA aprobado con acceso por API. El cuerpo sigue el formato de mensajes tipo OpenAI: cámbialo por el que te indique sistemas. La credencial va en *Header Auth*, nunca escrita en el nodo.",
        "height": 200,
        "width": 420,
        "color": 5
      },
      "id": "d22c4334-20c6-52ef-bff4-13ca68c86ad2",
      "name": "Nota: rama IA automática",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        620,
        340
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Llega el informe de la central": {
      "main": [
        [
          {
            "node": "¿CSV o Excel?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "¿CSV o Excel?": {
      "main": [
        [
          {
            "node": "Leer CSV",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Leer Excel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer CSV": {
      "main": [
        [
          {
            "node": "Limpiar y calcular desviaciones",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer Excel": {
      "main": [
        [
          {
            "node": "Limpiar y calcular desviaciones",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cada lunes 7:00 (carpeta)": {
      "main": [
        [
          {
            "node": "Leer archivo de la carpeta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer archivo de la carpeta": {
      "main": [
        [
          {
            "node": "Leer Excel (carpeta)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer Excel (carpeta)": {
      "main": [
        [
          {
            "node": "Limpiar y calcular desviaciones",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limpiar y calcular desviaciones": {
      "main": [
        [
          {
            "node": "Prompt y destinatario",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prompt y destinatario": {
      "main": [
        [
          {
            "node": "Enviar datos y prompt",
            "type": "main",
            "index": 0
          },
          {
            "node": "IA aprobada por la empresa (opcional)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IA aprobada por la empresa (opcional)": {
      "main": [
        [
          {
            "node": "Enviar plan (IA de la empresa)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "tags": []
}