{
  "name": "Informe del lunes: ventas del TPV con KPIs y resumen IA",
  "nodes": [
    {
      "parameters": {
        "content": "> Plantilla de partida de fernandorivera.es. Úsala y adáptala libremente; sin garantía; revisa los datos que tratas.\n\n## Informe del lunes, automático\nCada lunes a las 8:00 lee las ventas de la semana pasada de una hoja de Google, **calcula los KPIs con código** (venta, LFL, tickets, ticket medio, UPT, venta por hora, top y bottom productos) y te manda un correo.\n\nLa IA solo redacta el resumen y 3 acciones a partir de los números ya calculados. Los números del correo salen del nodo de código, no de la IA.\n\n**Es una plantilla de partida:** revísala, adáptala y pruébala con tus datos antes de fiarte de ella.\n\nGuía completa: «Informe semanal de ventas automático» en fernandorivera.es.",
        "height": 380,
        "width": 440,
        "color": 4
      },
      "id": "4e8935f6-bb54-54a0-9530-92b79b3f9fdb",
      "name": "Nota: qué hace",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        -520
      ]
    },
    {
      "parameters": {
        "content": "## 1. Prepara la hoja\nTres pestañas, con los nombres de columna exactos en la fila 1:\n\n**Lineas** (una fila por línea de ticket):\n`Fecha` · `Ticket` · `Producto` · `Unidades` · `Importe`\n\n**Horas** (una fila por día):\n`Fecha` · `Horas`\n\n**Historial** (la rellena el flujo):\n`Semana` · `Venta` · `Venta año anterior` · `LFL %` · `Tickets` · `Ticket medio` · `UPT` · `Venta por hora` · `Resumen IA`\n\n- Fecha: AAAA-MM-DD o DD/MM/AAAA.\n- Importe: siempre con IVA o siempre sin IVA, pero igual en todas las filas.\n- Para el LFL necesitas también las ventas del año pasado en **Lineas**.\n- Solo totales de venta: nada de nombres, emails ni teléfonos de clientes.",
        "height": 460,
        "width": 480,
        "color": 5
      },
      "id": "bb4eab2e-7c88-5ff5-8b65-7e6d989bcff4",
      "name": "Nota: la hoja",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -560
      ]
    },
    {
      "parameters": {
        "content": "## 2. Configura\n- Cambia `TU_ID_DE_HOJA` en los nodos de Google Sheets (está en la URL de la hoja, entre `/d/` y `/edit`).\n- Crea las credenciales de Google Sheets, OpenAI y Gmail en n8n. Esta plantilla no trae ninguna clave.\n- En **Configuración**: tu correo, el nombre de la tienda y cuántos productos quieres en el top y el bottom.\n- Revisa la zona horaria en *Settings* del workflow (viene Europe/Madrid).\n- Pruébalo con *Execute workflow* antes de activarlo.",
        "height": 340,
        "width": 420,
        "color": 6
      },
      "id": "e05ff0b2-1b0e-53b0-814e-f0985ba861f8",
      "name": "Nota: configura",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        520,
        -520
      ]
    },
    {
      "parameters": {
        "content": "### Si algo no cuadra\n- **No llega nada:** la pestaña Lineas está vacía o con otro nombre. Mira la salida de «Leer líneas de venta».\n- **Venta a 0:** las fechas no se reconocen. El correo de aviso dice cuántas líneas se han descartado.\n- **Tickets disparados:** la columna Ticket viene vacía o con el número de línea, no de ticket.\n- **LFL s/d:** faltan las ventas del año pasado en Lineas.\n- **La IA cambia cifras:** baja la temperatura y recuerda que los números buenos son los de arriba del correo.",
        "height": 340,
        "width": 420,
        "color": 3
      },
      "id": "1982d9dc-de47-501a-90b0-d33dfbf035e3",
      "name": "Nota: fallos típicos",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1000,
        -520
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "weeksInterval": 1,
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8,
              "triggerAtMinute": 0
            }
          ]
        }
      },
      "id": "61ca943f-05b1-5a3d-a683-09a86db42783",
      "name": "Cada lunes 8:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -440,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "2b139723-e892-5390-9bf1-e7238a892feb",
              "name": "email_informe",
              "value": "TU_CORREO",
              "type": "string"
            },
            {
              "id": "8e56e748-ae0b-5639-bc25-ce2198f3ef9f",
              "name": "nombre_tienda",
              "value": "Mi tienda",
              "type": "string"
            },
            {
              "id": "9e9117a8-0561-53ea-a952-e8a4658c8669",
              "name": "top_productos",
              "value": 5,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "2dc9f46a-d3a2-5a4f-8d74-a6ac2fa2722b",
      "name": "Configuración",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -220,
        0
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Lineas",
          "mode": "name"
        },
        "options": {}
      },
      "id": "02c838b8-31ea-5246-8ce1-bcd29673f369",
      "name": "Leer líneas de venta",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        0,
        0
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Horas",
          "mode": "name"
        },
        "options": {}
      },
      "id": "490bbb70-aae8-5995-a643-967c10a396e2",
      "name": "Leer horas",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        220,
        0
      ],
      "alwaysOutputData": true,
      "executeOnce": true
    },
    {
      "parameters": {
        "jsCode": "// Calcula los KPIs de la semana pasada (lunes a domingo) y de la misma semana del año anterior\n// (364 días antes, para que coincidan los días de la semana). La IA NO calcula nada: solo recibe este resultado.\nconst cfg = $('Configuración').first().json;\nconst ZONA = 'Europe/Madrid';\nconst TOP = Number(cfg.top_productos) || 5;\n\nconst limpias = arr => arr.map(i => i.json).filter(r => r && Object.keys(r).length);\nconst lineas = limpias($('Leer líneas de venta').all());\nconst horasRows = limpias($('Leer horas').all());\n\n// Números en formato español o inglés: \"1.234,50\", \"1234.5\", \"12 €\"\nfunction num(v) {\n  if (v === null || v === undefined || v === '') return 0;\n  if (typeof v === 'number') return v;\n  let s = String(v).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) ? 0 : n;\n}\n// Fechas: AAAA-MM-DD, DD/MM/AAAA o número de serie de hoja de cálculo\nfunction fecha(v) {\n  if (v === null || v === undefined || v === '') return null;\n  const s = String(v).trim();\n  if (/^\\d{5}(\\.\\d+)?$/.test(s)) return DateTime.fromISO('1899-12-30').plus({ days: Math.floor(Number(s)) }).toISODate();\n  let m = s.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/);\n  if (m) return `${m[1]}-${m[2].padStart(2, '0')}-${m[3].padStart(2, '0')}`;\n  m = s.match(/^(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})/);\n  if (m) return `${m[3]}-${m[2].padStart(2, '0')}-${m[1].padStart(2, '0')}`;\n  return null;\n}\n\nconst hoy = $now.setZone(ZONA).startOf('day');\nconst ini = hoy.minus({ days: hoy.weekday - 1 + 7 }); // lunes de la semana pasada\nconst fin = ini.plus({ days: 6 });\nconst iniAA = ini.minus({ days: 364 });\nconst finAA = fin.minus({ days: 364 });\n\nlet sinFecha = 0;\nlineas.forEach(r => { if (!fecha(r['Fecha'])) sinFecha++; });\n\nfunction kpis(desde, hasta) {\n  const d = desde.toISODate(), h = hasta.toISODate();\n  const dentro = r => { const f = fecha(r['Fecha']); return f && f >= d && f <= h; };\n  const ls = lineas.filter(dentro);\n  const venta = ls.reduce((s, r) => s + num(r['Importe']), 0);\n  const unidades = ls.reduce((s, r) => s + num(r['Unidades']), 0);\n  const tickets = new Set(ls.map(r => String(r['Ticket'] ?? '').trim()).filter(t => t)).size;\n  const horas = horasRows.filter(dentro).reduce((s, r) => s + num(r['Horas']), 0);\n  const productos = {};\n  for (const r of ls) {\n    const p = String(r['Producto'] || '(sin nombre)').trim();\n    if (!productos[p]) productos[p] = { producto: p, venta: 0, unidades: 0 };\n    productos[p].venta += num(r['Importe']);\n    productos[p].unidades += num(r['Unidades']);\n  }\n  return {\n    lineas: ls.length, venta, unidades, tickets, horas,\n    ticketMedio: tickets ? venta / tickets : null,\n    upt: tickets ? unidades / tickets : null,\n    ventaHora: horas ? venta / horas : null,\n    productos,\n  };\n}\n\nconst s = kpis(ini, fin);\nconst a = kpis(iniAA, finAA);\n\nconst pct = (x, y) => (x === null || y === null || !y) ? null : (x / y - 1) * 100;\nconst miles = n => String(Math.round(n)).replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\nconst eur = n => n === null ? 's/d' : miles(n) + ' €';\nconst eur2 = n => { if (n === null) return 's/d'; const [e, d] = Math.abs(n).toFixed(2).split('.'); return (n < 0 ? '-' : '') + miles(Number(e)) + ',' + d + ' €'; };\nconst dec = n => n === null ? 's/d' : n.toFixed(2).replace('.', ',');\nconst var_ = n => n === null ? 's/d' : (n >= 0 ? '+' : '') + n.toFixed(1).replace('.', ',') + ' %';\n\nconst lfl = pct(s.venta, a.venta);\nconst lista = Object.values(s.productos).sort((x, y) => y.venta - x.venta);\nconst top = lista.slice(0, TOP);\nconst bottom = lista.filter(p => p.venta > 0).sort((x, y) => x.venta - y.venta).slice(0, TOP);\nconst caidas = Object.values(a.productos)\n  .map(p => ({ producto: p.producto, antes: p.venta, ahora: (s.productos[p.producto] || { venta: 0 }).venta }))\n  .map(p => ({ ...p, dif: p.ahora - p.antes }))\n  .filter(p => p.dif < 0)\n  .sort((x, y) => x.dif - y.dif)\n  .slice(0, TOP);\n\nconst avisos = [];\nif (sinFecha) avisos.push(`${sinFecha} líneas con la fecha en un formato que no se entiende (no se han contado).`);\nif (!a.venta) avisos.push('No hay ventas del mismo periodo del año anterior: el LFL no se puede calcular.');\nif (!s.horas) avisos.push('No hay horas trabajadas cargadas esta semana: la venta por hora no se puede calcular.');\n\nconst fila = (n, x, y, f, v) => `${n}: ${f(x)} | año anterior ${f(y)} | variación ${v}`;\nconst tabla = [\n  fila('Venta', s.venta, a.venta, eur, var_(lfl)),\n  fila('Tickets', s.tickets, a.tickets, miles, var_(pct(s.tickets, a.tickets))),\n  fila('Ticket medio', s.ticketMedio, a.ticketMedio, eur2, var_(pct(s.ticketMedio, a.ticketMedio))),\n  fila('UPT (unidades por ticket)', s.upt, a.upt, dec, var_(pct(s.upt, a.upt))),\n  fila('Venta por hora trabajada', s.ventaHora, a.ventaHora, eur2, var_(pct(s.ventaHora, a.ventaHora))),\n].join('\\n');\n\nconst prods = arr => arr.length ? arr.map(p => `- ${p.producto}: ${eur(p.venta)} (${miles(p.unidades)} uds)`).join('\\n') : '- (sin datos)';\nconst textoCaidas = caidas.length ? caidas.map(p => `- ${p.producto}: ${eur(p.ahora)} frente a ${eur(p.antes)} (${eur(p.dif)})`).join('\\n') : '- (ninguna o sin datos del año anterior)';\n\nconst semana = `${ini.toFormat('dd/MM/yyyy')} a ${fin.toFormat('dd/MM/yyyy')}`;\nconst datos_ia = [\n  `Tienda: ${cfg.nombre_tienda}`,\n  `Semana: ${semana}. Comparada con ${iniAA.toFormat('dd/MM/yyyy')} a ${finAA.toFormat('dd/MM/yyyy')} (mismos días de la semana).`,\n  '', 'KPIs:', tabla,\n  '', `Top ${TOP} productos por venta:`, prods(top),\n  '', `Bottom ${TOP} productos con venta:`, prods(bottom),\n  '', `Mayores caídas en euros frente al año anterior:`, textoCaidas,\n  '', 'Avisos de datos:', avisos.length ? avisos.map(x => '- ' + x).join('\\n') : '- Ninguno',\n].join('\\n');\n\nreturn [{ json: {\n  semana, semana_inicio: ini.toISODate(),\n  venta: Math.round(s.venta * 100) / 100,\n  venta_aa: Math.round(a.venta * 100) / 100,\n  lfl_pct: lfl === null ? '' : Math.round(lfl * 10) / 10,\n  tickets: s.tickets,\n  ticket_medio: s.ticketMedio === null ? '' : Math.round(s.ticketMedio * 100) / 100,\n  upt: s.upt === null ? '' : Math.round(s.upt * 100) / 100,\n  venta_hora: s.ventaHora === null ? '' : Math.round(s.ventaHora * 100) / 100,\n  lineas_semana: s.lineas,\n  lfl_texto: var_(lfl),\n  venta_texto: eur(s.venta),\n  datos_ia,\n} }];\n"
      },
      "id": "3c436dab-1ce3-5407-ab2e-ed17fa740efc",
      "name": "Calcular KPIs",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "56f14933-f18d-5d00-9095-c63bc6be1dfd",
              "leftValue": "={{ $json.lineas_semana }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "3b9f1436-11f6-58a9-b1f8-ae93c47defea",
      "name": "¿Hay ventas de la semana?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "GPT-4O-MINI"
        },
        "messages": {
          "values": [
            {
              "content": "Eres el ayudante del responsable de una tienda física. Recibes los KPIs de la semana pasada YA CALCULADOS y comparados con la misma semana del año anterior.\n\nReglas:\n1. No calcules, redondees ni cambies ninguna cifra. Usa solo las que aparecen en los datos, tal cual.\n2. Si hay avisos de datos, empieza diciendo qué falta y qué no se puede concluir.\n3. No inventes causas (tiempo, competencia, obras…). Si los datos no explican algo, escribe «sin causa clara en los datos: revisar en tienda».\n4. Español de España, frases cortas, sin saludos ni despedidas.\n\nFormato:\nRESUMEN (máximo 6 líneas): cómo ha ido la venta frente al año anterior y si el cambio viene de tickets (más o menos clientes) o de ticket medio y UPT (gasto por cliente). Menciona la venta por hora si está.\n3 ACCIONES PARA ESTA SEMANA: tres líneas que empiecen por un verbo, cada una ligada a un dato concreto del informe (dilo entre paréntesis). Acciones de tienda: exposición, reposición, venta sugerida, cuadrante, formación rápida al equipo.",
              "role": "system"
            },
            {
              "content": "={{ $json.datos_ia }}"
            }
          ]
        },
        "options": {
          "temperature": 0.2
        }
      },
      "id": "2e2cfd03-8e10-56a8-bc4e-5f80109bfbd2",
      "name": "Redactar resumen y 3 acciones",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        880,
        -100
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Historial",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Semana": "={{ $('Calcular KPIs').item.json.semana_inicio }}",
            "Venta": "={{ $('Calcular KPIs').item.json.venta }}",
            "Venta año anterior": "={{ $('Calcular KPIs').item.json.venta_aa }}",
            "LFL %": "={{ $('Calcular KPIs').item.json.lfl_pct }}",
            "Tickets": "={{ $('Calcular KPIs').item.json.tickets }}",
            "Ticket medio": "={{ $('Calcular KPIs').item.json.ticket_medio }}",
            "UPT": "={{ $('Calcular KPIs').item.json.upt }}",
            "Venta por hora": "={{ $('Calcular KPIs').item.json.venta_hora }}",
            "Resumen IA": "={{ $json.message.content }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Semana",
              "displayName": "Semana",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Venta",
              "displayName": "Venta",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Venta año anterior",
              "displayName": "Venta año anterior",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "LFL %",
              "displayName": "LFL %",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Tickets",
              "displayName": "Tickets",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Ticket medio",
              "displayName": "Ticket medio",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "UPT",
              "displayName": "UPT",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Venta por hora",
              "displayName": "Venta por hora",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Resumen IA",
              "displayName": "Resumen IA",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "7169937f-066e-5a3d-8791-92527ee45eb5",
      "name": "Guardar en Historial",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1100,
        -100
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Configuración').first().json.email_informe }}",
        "subject": "=Semana {{ $('Calcular KPIs').first().json.semana }}: {{ $('Calcular KPIs').first().json.venta_texto }} (LFL {{ $('Calcular KPIs').first().json.lfl_texto }})",
        "emailType": "text",
        "message": "=Informe de la semana {{ $('Calcular KPIs').first().json.semana }}\n{{ $('Configuración').first().json.nombre_tienda }}\n\nLOS NÚMEROS (calculados por el flujo, no por la IA)\n{{ $('Calcular KPIs').first().json.datos_ia }}\n\nRESUMEN Y ACCIONES (borrador de la IA: revísalo antes de usarlo)\n{{ $('Redactar resumen y 3 acciones').first().json.message.content }}\n\n--\nInforme automático. Si algún número no cuadra con el TPV, revisa la pestaña Lineas antes de tomar decisiones.",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "2bde7fa3-3e01-5a9d-af9e-acf6307c33fb",
      "name": "Enviar informe",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1320,
        -100
      ],
      "webhookId": "e083e0c2-16b5-5962-aa78-e154c0d7ee5b"
    },
    {
      "parameters": {
        "sendTo": "={{ $('Configuración').first().json.email_informe }}",
        "subject": "=Informe del lunes: no hay ventas de la semana {{ $json.semana }}",
        "emailType": "text",
        "message": "=El flujo no ha encontrado líneas de venta de la semana {{ $json.semana }} en la pestaña Lineas.\n\nRevisa que la exportación o la carga desde el TPV se ha hecho y que la columna Fecha tiene un formato que se entienda (AAAA-MM-DD o DD/MM/AAAA).\n\n{{ $json.datos_ia }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "bda738a2-bd7a-5b25-ab66-0b682168954f",
      "name": "Avisar: faltan datos",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        880,
        140
      ],
      "webhookId": "1a9c29fb-5b59-56e2-831c-d401a5801266"
    },
    {
      "parameters": {
        "content": "## Opcional: cargar la semana desde Shopify\nEsta rama está **desactivada**. Si vendes con Shopify POS, actívala: cada lunes a las 7:00 pide los pedidos de la semana pasada y los añade a la pestaña Lineas, una hora antes del informe.\n\n- Credencial de Shopify con permiso de lectura de pedidos.\n- Sin el permiso `read_all_orders`, la API solo da los pedidos de los últimos 60 días: el año pasado cárgalo una vez desde una exportación.\n- `Status = Any`: el valor por defecto del nodo solo trae pedidos abiertos.\n- En el nodo de código, comprueba qué `source_name` traen tus ventas de TPV.\n\nCon Square, Lightspeed u Holded, cambia el nodo de Shopify por un HTTP Request a su API (ver la guía).",
        "height": 360,
        "width": 520,
        "color": 7
      },
      "id": "b0a5ab6a-30db-5100-a390-2802fa641ef0",
      "name": "Nota: carga opcional desde Shopify",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -480,
        300
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "weeksInterval": 1,
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 7,
              "triggerAtMinute": 0
            }
          ]
        }
      },
      "id": "6f4dae9e-661e-5041-a8a9-fe58f8a9579f",
      "name": "Cada lunes 7:00 (carga)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        100,
        420
      ],
      "disabled": true
    },
    {
      "parameters": {
        "authentication": "accessToken",
        "resource": "order",
        "operation": "getAll",
        "returnAll": true,
        "options": {
          "status": "any",
          "createdAtMin": "={{ $now.setZone('Europe/Madrid').startOf('day').minus({ days: $now.setZone('Europe/Madrid').weekday - 1 + 7 }).toISO() }}",
          "createdAtMax": "={{ $now.setZone('Europe/Madrid').startOf('day').minus({ days: $now.setZone('Europe/Madrid').weekday - 1 }).toISO() }}"
        }
      },
      "id": "4db64e98-6d39-52d7-9738-4337f839118c",
      "name": "Pedidos de la semana (Shopify)",
      "type": "n8n-nodes-base.shopify",
      "typeVersion": 1,
      "position": [
        320,
        420
      ],
      "disabled": true
    },
    {
      "parameters": {
        "jsCode": "// Convierte los pedidos de Shopify en filas para la pestaña Lineas (una fila por línea de pedido).\n// Revisa con un pedido de prueba qué valor trae source_name en tu tienda para la venta de TPV\n// y cámbialo aquí. Déjalo vacío ('') para cargar todos los canales.\nconst SOLO_CANAL = 'pos';\nconst filas = [];\nfor (const item of $input.all()) {\n  const o = item.json;\n  if (SOLO_CANAL && o.source_name !== SOLO_CANAL) continue;\n  if (o.cancelled_at) continue;\n  const fecha = String(o.created_at || '').slice(0, 10);\n  for (const li of (o.line_items || [])) {\n    const bruto = parseFloat(li.price || 0) * Number(li.quantity || 0);\n    const dto = parseFloat(li.total_discount || 0);\n    filas.push({ json: {\n      Fecha: fecha,\n      Ticket: String(o.name || o.id),\n      Producto: li.title + (li.variant_title ? ' · ' + li.variant_title : ''),\n      Unidades: Number(li.quantity || 0),\n      Importe: Math.round((bruto - dto) * 100) / 100,\n    } });\n  }\n}\nreturn filas;\n"
      },
      "id": "f9f2441d-223b-5855-9107-fc080c22c032",
      "name": "Pasar a líneas",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        540,
        420
      ],
      "disabled": true
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Lineas",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": [
            {
              "id": "Fecha",
              "displayName": "Fecha",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Ticket",
              "displayName": "Ticket",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Producto",
              "displayName": "Producto",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Unidades",
              "displayName": "Unidades",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Importe",
              "displayName": "Importe",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "797d63ae-da86-5655-a4e2-48d7f5e53912",
      "name": "Añadir a Lineas",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        760,
        420
      ],
      "disabled": true
    }
  ],
  "pinData": {},
  "connections": {
    "Cada lunes 8:00": {
      "main": [
        [
          {
            "node": "Configuración",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuración": {
      "main": [
        [
          {
            "node": "Leer líneas de venta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer líneas de venta": {
      "main": [
        [
          {
            "node": "Leer horas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer horas": {
      "main": [
        [
          {
            "node": "Calcular KPIs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calcular KPIs": {
      "main": [
        [
          {
            "node": "¿Hay ventas de la semana?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "¿Hay ventas de la semana?": {
      "main": [
        [
          {
            "node": "Redactar resumen y 3 acciones",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Avisar: faltan datos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Redactar resumen y 3 acciones": {
      "main": [
        [
          {
            "node": "Guardar en Historial",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardar en Historial": {
      "main": [
        [
          {
            "node": "Enviar informe",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cada lunes 7:00 (carga)": {
      "main": [
        [
          {
            "node": "Pedidos de la semana (Shopify)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pedidos de la semana (Shopify)": {
      "main": [
        [
          {
            "node": "Pasar a líneas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pasar a líneas": {
      "main": [
        [
          {
            "node": "Añadir a Lineas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "tags": []
}