{
  "name": "DEMO — Solleciti di pagamento delle fatture (PauSolutions)",
  "nodes": [
    {
      "parameters": {
        "content": "## Solleciti di pagamento — DEMO\nStudio grafico e fatture fittizi, nessuna credenziale.\n\n- 3 livelli: cortese (3+ giorni dalla scadenza), più deciso (10+), ultimo (20+, segnalato al titolare).\n- Le fatture pagate vengono saltate: i solleciti si fermano da soli.\n- Collega l'elenco fatture e la tua email, poi attiva il nodo email.\n\nVersione interattiva: pausolutions.com/it/demos/payment-reminders/\n\n_Tutte le date di questa demo sono esempi (\"oggi\" di esempio: 21 ott 2026). In produzione il flusso usa la data reale._",
        "height": 330,
        "width": 460
      },
      "id": "dfcd9a78-2af2-4800-a1fa-e3c1fa823fc8",
      "name": "Info su questa demo",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -80,
        -340
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 9
            }
          ]
        }
      },
      "id": "46a8d114-e8d7-4200-a89f-a8ad5f4f434c",
      "name": "Ogni giorno feriale alle 9:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {},
      "id": "5e7c93f1-bcd6-4200-a04b-ea48301286c7",
      "name": "Test manuale",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "// DATI DEMO — fatture fittizie. In produzione leggi l'elenco delle fatture\n// (Google Sheets, Excel o un export dal tuo gestionale di fatturazione).\nconst invoices = [\n  {\n    \"id\": \"i1\",\n    \"number\": \"2026/101\",\n    \"client\": \"Rossi Arredamenti Srl (demo)\",\n    \"email\": \"amministrazione@rossiarredamenti.example\",\n    \"amount\": 480,\n    \"due\": \"2026-10-18\",\n    \"paid\": false,\n    \"stageSent\": 0\n  },\n  {\n    \"id\": \"i2\",\n    \"number\": \"2026/097\",\n    \"client\": \"Caffè Foglia Verde (demo)\",\n    \"email\": \"titolare@fogliaverde.example\",\n    \"amount\": 250,\n    \"due\": \"2026-10-08\",\n    \"paid\": false,\n    \"stageSent\": 1\n  },\n  {\n    \"id\": \"i3\",\n    \"number\": \"2026/090\",\n    \"client\": \"Officina Verdi Snc (demo)\",\n    \"email\": \"ufficio@officinaverdi.example\",\n    \"amount\": 1200,\n    \"due\": \"2026-09-25\",\n    \"paid\": false,\n    \"stageSent\": 2\n  },\n  {\n    \"id\": \"i4\",\n    \"number\": \"2026/099\",\n    \"client\": \"Bici Nord (demo)\",\n    \"email\": \"negozio@bicinord.example\",\n    \"amount\": 320,\n    \"due\": \"2026-10-10\",\n    \"paid\": true,\n    \"stageSent\": 1\n  },\n  {\n    \"id\": \"i5\",\n    \"number\": \"2026/104\",\n    \"client\": \"Bianchi Design (demo)\",\n    \"email\": \"info@bianchidesign.example\",\n    \"amount\": 150,\n    \"due\": \"2026-11-05\",\n    \"paid\": false,\n    \"stageSent\": 0\n  }\n];\nreturn invoices.map((i) => ({ json: i }));"
      },
      "id": "0ffcc22d-0334-4600-a1cb-bb946f257dab",
      "name": "Fatture (dati demo)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        100
      ]
    },
    {
      "parameters": {
        "jsCode": "// Stessa logica della demo interattiva su pausolutions.com/it/demos/ (regole + modelli, senza IA).\nconst TEXT = {\n  \"locale\": \"it-IT\",\n  \"firm\": \"Studio Grafico Lume (demo)\",\n  \"stageNames\": [\n    \"Cortese\",\n    \"Più deciso\",\n    \"Ultimo\"\n  ],\n  \"subjects\": [\n    \"Promemoria: fattura {number} ({amount})\",\n    \"Secondo promemoria: fattura {number} scaduta da {days} giorni\",\n    \"Ultimo promemoria: fattura {number} ({amount})\"\n  ],\n  \"bodies\": [\n    \"Gentile {client},\\n\\nle ricordiamo cortesemente che la fattura {number} di {amount}, con scadenza {due}, risulta ancora aperta. Se ha già provveduto al pagamento, ignori questo messaggio. Grazie!\\n\\n{firm}\",\n    \"Gentile {client},\\n\\nla fattura {number} di {amount} era in scadenza il {due} e risulta scaduta da {days} giorni. Può indicarci quando prevede di saldarla? Se c'è un problema con la fattura, ci risponda e lo risolviamo.\\n\\n{firm}\",\n    \"Gentile {client},\\n\\nnonostante i precedenti promemoria, la fattura {number} di {amount} (scadenza {due}) risulta ancora non pagata. La preghiamo di provvedere entro il {deadline}. In mancanza di riscontro la contatteremo telefonicamente per concordare come procedere.\\n\\n{firm}\"\n  ],\n  \"summarySubject\": \"Fatture scadute per il titolare\"\n};\nconst STAGES = [3,10,20];\nconst today = \"2026-10-21\"; // Demo: data di esempio fissa, coerente con i dati demo. In produzione usa: new Date().toISOString().slice(0, 10)\n\nfunction fill(tpl, vars) {\n  return tpl.replace(/\\{(\\w+)\\}/g, (m, k) => (k in vars ? String(vars[k]) : m));\n}\n\nfunction daysBetween(fromIso, toIso) {\n  return Math.round((Date.parse(toIso + \"T00:00:00Z\") - Date.parse(fromIso + \"T00:00:00Z\")) / 86400000);\n}\n\nfunction addDays(iso, days) {\n  return new Date(Date.parse(iso + \"T00:00:00Z\") + days * 86400000).toISOString().slice(0, 10);\n}\n\nfunction longDate(iso, locale) {\n  return new Intl.DateTimeFormat(locale, { day: \"numeric\", month: \"long\", year: \"numeric\", timeZone: \"UTC\" }).format(new Date(iso + \"T00:00:00Z\"));\n}\n\nfunction money(amount, locale) {\n  return new Intl.NumberFormat(locale, { style: \"currency\", currency: \"EUR\" }).format(amount);\n}\n\nfunction cleanText(value, max, multiline) {\n  const s = String(value ?? \"\").replace(multiline ? /[\\u0000-\\u0009\\u000B-\\u001F\\u007F]/g : /[\\u0000-\\u001F\\u007F]/g, \" \");\n  const collapsed = multiline ? s.replace(/[ \\t]+/g, \" \").replace(/\\n{3,}/g, \"\\n\\n\") : s.replace(/\\s+/g, \" \");\n  return collapsed.trim().slice(0, max);\n}\n\nfunction hasTerm(text, term, whole) {\n  if (!/^[\\p{L}\\p{N}]/u.test(term)) return text.includes(term);\n  const escaped = term.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n  return new RegExp(\"(^|[^\\\\p{L}\\\\p{N}])\" + escaped + (whole ? \"(?![\\\\p{L}\\\\p{N}])\" : \"\"), \"u\").test(text);\n}\n\nfunction planPayments(invoices, today, text, stages) {\n  return invoices.map((inv) => {\n    const daysOverdue = daysBetween(inv.due, today);\n    const stage = stages.filter((d) => daysOverdue >= d).length;\n    const base = { id: inv.id, invoice: inv.number, client: inv.client, email: inv.email, amount: inv.amount, daysOverdue, stage, stageName: null, flagOwner: false, subject: null, body: null };\n    if (inv.paid) return { ...base, status: \"paid\" };\n    if (daysOverdue <= 0) return { ...base, status: \"not-due\" };\n    if (stage === 0 || stage <= inv.stageSent) return { ...base, status: \"overdue\", flagOwner: inv.stageSent >= 3 };\n    const vars = { client: inv.client, number: inv.number, amount: money(inv.amount, text.locale), due: longDate(inv.due, text.locale), days: daysOverdue, firm: text.firm, deadline: longDate(addDays(today, 7), text.locale) };\n    return { ...base, status: \"email\", stageName: text.stageNames[stage - 1], flagOwner: stage === 3, subject: fill(text.subjects[stage - 1], vars), body: fill(text.bodies[stage - 1], vars) };\n  });\n}\n\nreturn planPayments($input.all().map((i) => i.json), today, TEXT, STAGES).map((o) => ({ json: o }));"
      },
      "id": "29841658-3fcb-4b00-a7b3-6fe1712b1ce8",
      "name": "Scegli il livello di sollecito",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        100
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "8d762cfe-188b-4800-a341-554754286e92",
              "leftValue": "={{ $json.status }}",
              "rightValue": "email",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "15145405-51d7-4480-ab23-2dbc13392313",
      "name": "Solo le fatture da sollecitare",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        740,
        0
      ],
      "notes": "Fatture pagate, non ancora scadute o già sollecitate vengono saltate.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "fromEmail": "amministrazione@studiolume.example",
        "toEmail": "={{ $json.email }}",
        "subject": "={{ $json.subject }}",
        "emailFormat": "text",
        "text": "={{ $json.body }}",
        "options": {}
      },
      "id": "07c80718-42cf-4f80-a9ff-7ea1205a5628",
      "name": "Invia sollecito di pagamento",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        980,
        0
      ],
      "disabled": true,
      "notes": "Disattivato nella demo. Collega il tuo account SMTP/Gmail/Outlook, poi attivalo.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "// Stessa logica della demo interattiva su pausolutions.com/it/demos/ (regole + modelli, senza IA).\nfunction paymentOverview(outcomes) {\n  const sum = (list) => ({ count: list.length, total: list.reduce((t, o) => t + o.amount, 0) });\n  const overdue = outcomes.filter((o) => o.status === \"overdue\" || o.status === \"email\");\n  return {\n    paid: sum(outcomes.filter((o) => o.status === \"paid\")),\n    notDue: sum(outcomes.filter((o) => o.status === \"not-due\")),\n    overdue: sum(overdue),\n    emails: outcomes.filter((o) => o.status === \"email\").length,\n    flaggedForOwner: outcomes.filter((o) => o.flagOwner).map((o) => o.invoice),\n  };\n}\n\nreturn [{ json: { subject: \"Fatture scadute per il titolare\", ...paymentOverview($input.all().map((i) => i.json)) } }];"
      },
      "id": "f9c2ef64-f969-4800-a7f5-96ddf8a1267c",
      "name": "Riepilogo per il titolare",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        240
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Ogni giorno feriale alle 9:00": {
      "main": [
        [
          {
            "node": "Fatture (dati demo)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Test manuale": {
      "main": [
        [
          {
            "node": "Fatture (dati demo)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fatture (dati demo)": {
      "main": [
        [
          {
            "node": "Scegli il livello di sollecito",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scegli il livello di sollecito": {
      "main": [
        [
          {
            "node": "Solo le fatture da sollecitare",
            "type": "main",
            "index": 0
          },
          {
            "node": "Riepilogo per il titolare",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Solo le fatture da sollecitare": {
      "main": [
        [
          {
            "node": "Invia sollecito di pagamento",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "48c4f0cc-09ce-4a00-a6f3-89752eeda3d4",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": [],
  "_demo": {
    "by": "PauSolutions",
    "note": "DEMO con dati fittizi. Nessuna credenziale inclusa. I nodi email sono disattivati finché non colleghi il tuo account.",
    "tags": [
      "demo",
      "fatture"
    ]
  }
}
