{
  "name": "Stock mínimo: borrador de pedido a proveedores",
  "nodes": [
    {
      "parameters": {
        "content": "## Borrador de pedido cuando baja el stock\nCada mañana lee el stock de una hoja de Google, se queda con lo que ha llegado al mínimo o está por debajo, calcula la cantidad, agrupa por proveedor y deja **un borrador de Gmail por proveedor**.\n\n**No envía nada.** Tú revisas, ajustas y envías.\n\nLas cantidades salen de tu hoja y de la fórmula del nodo «Agrupar por proveedor». La IA solo redacta el correo.\n\nExplicación completa en «Cómo automatizar los pedidos a proveedores cuando baja el stock» de fernandorivera.es.",
        "height": 380,
        "width": 420,
        "color": 4
      },
      "id": "3e59c747-5109-59da-ad93-e7b8a6bb3c00",
      "name": "Nota: qué hace",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -440,
        -440
      ]
    },
    {
      "parameters": {
        "content": "## 1. Prepara la hoja\nPestaña **Stock**, columnas en la fila 1 (son las de la hoja maestra de stock de la web):\n\n`Código` · `Producto` · `Proveedor` · `Email proveedor` · `Stock actual` · `Stock mínimo` · `Cantidad habitual de pedido`\n\nOpcionales: `Pendiente de recibir` (para no pedir dos veces) y `Formato caja` (si te obliga a pedir de 12 en 12).\n\nSi tu TPV o programa de gestión se conecta con n8n, puedes leer el stock de ahí en lugar de la hoja.",
        "height": 380,
        "width": 460,
        "color": 5
      },
      "id": "bf4f3237-25f8-55b5-a729-91719b4ae3a3",
      "name": "Nota: la hoja",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -440
      ]
    },
    {
      "parameters": {
        "content": "## 2. Configura\n- **Configuración**: nombre de la tienda y firma.\n- **Leer stock**: cambia `TU_ID_DE_HOJA` y crea la credencial de Google.\n- **Redactar pedido**: crea la credencial de OpenAI y elige el modelo (el que viene es un ejemplo).\n- **Guardar borrador**: crea la credencial de Gmail.\n\nEmpieza con un solo proveedor y revisa los borradores varias semanas antes de plantearte nada más.",
        "height": 380,
        "width": 420,
        "color": 6
      },
      "id": "986dc679-0309-594c-8cf4-aa85d6e02adb",
      "name": "Nota: configura",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        480,
        -440
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "id": "166ea3f7-a3fe-55c1-a5a4-1378bd117e7c",
      "name": "Cada mañana 8:00 (L-V)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -400,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "43c2106f-7e5a-5a11-80aa-e4b7b74c6d3e",
              "name": "nombre_tienda",
              "value": "TU_TIENDA",
              "type": "string"
            },
            {
              "id": "45d1aadd-3583-5994-b4e6-e4f8a6d2f1b6",
              "name": "firma",
              "value": "TU_NOMBRE",
              "type": "string"
            },
            {
              "id": "5f9923d4-ca38-597d-bb0d-66aaf1e3516a",
              "name": "entrega_deseada",
              "value": "",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "76039472-8a9f-5db5-b504-8b712da1b45a",
      "name": "Configuración",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -180,
        0
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "TU_ID_DE_HOJA",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Stock",
          "mode": "name"
        },
        "options": {}
      },
      "id": "7fc54a8d-0f06-50d0-be0d-885dedd24e1c",
      "name": "Leer stock",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        40,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "af84b97a-5322-581e-a0d3-a4d8b2888f79",
              "leftValue": "={{ $json['Código'] }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            },
            {
              "id": "f0d112cb-9e38-5496-a0cc-e317529eb14f",
              "leftValue": "={{ parseFloat(String($json['Stock actual'] ?? '').replace(',', '.')) + (parseFloat(String($json['Pendiente de recibir'] ?? '').replace(',', '.')) || 0) }}",
              "rightValue": "={{ parseFloat(String($json['Stock mínimo'] ?? '').replace(',', '.')) }}",
              "operator": {
                "type": "number",
                "operation": "lte"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "93ef7a2f-e989-5075-bdfb-5a0558b0b19a",
      "name": "En el mínimo o por debajo",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [
        260,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Calcula la cantidad de cada referencia y agrupa por proveedor.\n// Cantidad = lo mismo que la hoja maestra: MAX(cantidad habitual, mínimo − stock + cantidad habitual),\n// redondeada hacia arriba al formato de caja si lo tienes.\nconst num = v => parseFloat(String(v ?? '').replace(',', '.')) || 0;\nconst pedidos = {};\n\n$input.all().forEach((item, i) => {\n  const r = item.json;\n  const stock = num(r['Stock actual']) + num(r['Pendiente de recibir']);\n  const minimo = num(r['Stock mínimo']);\n  const habitual = num(r['Cantidad habitual de pedido']) || 1;\n  const caja = num(r['Formato caja']) || 1;\n  let cantidad = Math.max(habitual, minimo - stock + habitual);\n  cantidad = Math.ceil(cantidad / caja) * caja;\n\n  const prov = String(r['Proveedor'] || 'SIN PROVEEDOR').trim();\n  if (!pedidos[prov]) {\n    pedidos[prov] = { proveedor: prov, email_proveedor: r['Email proveedor'] || '', lineas: [], unidades: 0, origen: [] };\n  }\n  const cajas = caja > 1 ? ` (${cantidad / caja} cajas de ${caja})` : '';\n  pedidos[prov].lineas.push(`- ${cantidad} uds${cajas} · ${r['Producto']} (${r['Código']})`);\n  pedidos[prov].unidades += cantidad;\n  pedidos[prov].origen.push(i);\n});\n\nreturn Object.values(pedidos).map(p => ({\n  json: {\n    proveedor: p.proveedor,\n    email_proveedor: p.email_proveedor,\n    lineas: p.lineas.join('\\n'),\n    total_unidades: p.unidades,\n    aviso: p.email_proveedor ? '' : '[FALTA EL CORREO DEL PROVEEDOR] ',\n  },\n  pairedItem: p.origen.map(i => ({ item: i })),\n}));"
      },
      "id": "0e781251-142e-5efd-bad8-92b73c3b7084",
      "name": "Agrupar por proveedor",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "GPT-4O-MINI"
        },
        "messages": {
          "values": [
            {
              "content": "Redactas correos de pedido a proveedores para una tienda. Escribe solo el cuerpo del correo, en español de España, breve y cordial, sin asunto. Copia exactamente las líneas del pedido que te paso: no cambies cantidades, códigos ni productos, y no añadas ni quites líneas. No inventes precios, condiciones, fechas ni nombres. Si la fecha de entrega deseada viene vacía, no menciones ninguna fecha.",
              "role": "system"
            },
            {
              "content": "=Proveedor: {{ $json.proveedor }}\nTienda: {{ $('Configuración').first().json.nombre_tienda }}\nFecha de entrega deseada: {{ $('Configuración').first().json.entrega_deseada }}\nFirma: {{ $('Configuración').first().json.firma }}\n\nLíneas del pedido:\n{{ $json.lineas }}"
            }
          ]
        },
        "options": {
          "temperature": 0.3
        }
      },
      "id": "3553f7f3-2c10-511f-a803-1b8baa900bdf",
      "name": "Redactar pedido (OpenAI)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        700,
        0
      ]
    },
    {
      "parameters": {
        "resource": "draft",
        "subject": "={{ $('Agrupar por proveedor').item.json.aviso }}Pedido {{ $('Configuración').first().json.nombre_tienda }} · {{ $('Agrupar por proveedor').item.json.proveedor }} · {{ $now.toFormat('dd/MM') }}",
        "emailType": "text",
        "message": "={{ $json.message.content }}",
        "options": {
          "sendTo": "={{ $('Agrupar por proveedor').item.json.email_proveedor }}"
        }
      },
      "id": "f6c5e56a-1b5e-5af1-b37c-ecf7bf71e78d",
      "name": "Guardar borrador en Gmail",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        920,
        0
      ],
      "webhookId": "bf67900c-cc5f-566f-aa73-5e3864a2449b"
    },
    {
      "parameters": {
        "content": "### Antes de enviar cada borrador\n- ¿Las cantidades tienen sentido? (promociones, ferias, festivos)\n- ¿Hay algo ya pedido que no estaba en «Pendiente de recibir»?\n- ¿Llega al pedido mínimo del proveedor?\n\nA la IA solo le llegan productos y cantidades, nunca datos de clientes.",
        "height": 240,
        "width": 380,
        "color": 7
      },
      "id": "86744ce0-68d4-551b-99e3-421e4bacf451",
      "name": "Nota: revisa",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        640,
        200
      ]
    }
  ],
  "connections": {
    "Cada mañana 8:00 (L-V)": {
      "main": [
        [
          {
            "node": "Configuración",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuración": {
      "main": [
        [
          {
            "node": "Leer stock",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Leer stock": {
      "main": [
        [
          {
            "node": "En el mínimo o por debajo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "En el mínimo o por debajo": {
      "main": [
        [
          {
            "node": "Agrupar por proveedor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agrupar por proveedor": {
      "main": [
        [
          {
            "node": "Redactar pedido (OpenAI)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Redactar pedido (OpenAI)": {
      "main": [
        [
          {
            "node": "Guardar borrador en Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid"
  },
  "tags": []
}
