{
  "name": "DEMO — Solleciti documenti ai clienti (PauSolutions)",
  "nodes": [
    {
      "parameters": {
        "content": "## Solleciti documenti ai clienti — DEMO\nStudio contabile e clienti fittizi, nessuna credenziale.\n\n1. Sostituisci **Elenco clienti (dati demo)** con il tuo nodo Google Sheets/Excel.\n2. Collega il tuo account email in **Invia richiesta / sollecito** e attivalo.\n3. Regole: prima richiesta, promemoria dopo 5 giorni, secondo promemoria dopo altri 5, poi **telefonare al cliente** (niente più email).\n\nVersione interattiva: pausolutions.com/it/demos/document-chaser/\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 — clienti fittizi. In produzione sostituisci questo nodo con un\n// nodo Google Sheets / Excel che legge l'elenco dei tuoi clienti.\nconst clients = [\n  {\n    \"id\": \"c1\",\n    \"client\": \"Rossi Arredamenti Srl (demo)\",\n    \"email\": \"amministrazione@rossiarredamenti.example\",\n    \"requested\": [\n      \"Estratto conto\",\n      \"Fatture di acquisto\",\n      \"Scontrini carburante\"\n    ],\n    \"received\": [\n      \"Fatture di acquisto\"\n    ],\n    \"reminders\": 1,\n    \"lastReminder\": \"2026-10-14\"\n  },\n  {\n    \"id\": \"c2\",\n    \"client\": \"Bianchi Design (demo)\",\n    \"email\": \"info@bianchidesign.example\",\n    \"requested\": [\n      \"Estratto conto\",\n      \"Fatture di vendita\"\n    ],\n    \"received\": [\n      \"Estratto conto\",\n      \"Fatture di vendita\"\n    ],\n    \"reminders\": 0,\n    \"lastReminder\": \"\"\n  },\n  {\n    \"id\": \"c3\",\n    \"client\": \"Caffè Foglia Verde (demo)\",\n    \"email\": \"titolare@fogliaverde.example\",\n    \"requested\": [\n      \"Estratto conto\",\n      \"Fatture di acquisto\",\n      \"Riepilogo paghe\"\n    ],\n    \"received\": [],\n    \"reminders\": 0,\n    \"lastReminder\": \"\"\n  },\n  {\n    \"id\": \"c4\",\n    \"client\": \"Officina Verdi Snc (demo)\",\n    \"email\": \"ufficio@officinaverdi.example\",\n    \"requested\": [\n      \"Estratto conto\"\n    ],\n    \"received\": [],\n    \"reminders\": 3,\n    \"lastReminder\": \"2026-10-16\"\n  },\n  {\n    \"id\": \"c5\",\n    \"client\": \"Bici Nord (demo)\",\n    \"email\": \"negozio@bicinord.example\",\n    \"requested\": [\n      \"Estratto conto\",\n      \"Corrispettivi\"\n    ],\n    \"received\": [\n      \"Corrispettivi\"\n    ],\n    \"reminders\": 2,\n    \"lastReminder\": \"2026-10-12\"\n  },\n  {\n    \"id\": \"c6\",\n    \"client\": \"Fisioterapia Acero (demo)\",\n    \"email\": \"segreteria@fisioacero.example\",\n    \"requested\": [\n      \"Estratto conto\",\n      \"Report POS\"\n    ],\n    \"received\": [\n      \"Report POS\"\n    ],\n    \"reminders\": 1,\n    \"lastReminder\": \"2026-10-19\"\n  }\n];\nreturn clients.map((c) => ({ json: c }));"
      },
      "id": "2aa34dba-299b-4800-a494-3403f30e7ef6",
      "name": "Elenco clienti (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 Ferri Commercialisti (demo)\",\n  \"period\": \"settembre 2026\",\n  \"subjects\": [\n    \"Documenti necessari per {period}\",\n    \"Promemoria: documenti per {period}\",\n    \"Secondo promemoria: documenti per {period} entro il {deadline}\"\n  ],\n  \"bodies\": [\n    \"Gentile {client},\\n\\nper preparare la contabilità di {period} ci servono ancora:\\n{list}\\n\\nPuò semplicemente rispondere a questa email allegando i file. Grazie!\\n\\n{firm}\",\n    \"Gentile {client},\\n\\nle ricordiamo che attendiamo ancora questi documenti per {period}:\\n{list}\\n\\nRiesce a inviarceli entro questa settimana? Basta rispondere allegando i file.\\n\\n{firm}\",\n    \"Gentile {client},\\n\\nnon abbiamo ancora ricevuto questi documenti per {period}:\\n{list}\\n\\nCi servono entro il {deadline} per completare la contabilità nei tempi. Se qualcosa non è chiaro, risponda a questa email e la aiutiamo.\\n\\n{firm}\"\n  ],\n  \"actions\": {\n    \"first\": \"prima richiesta\",\n    \"reminder\": \"promemoria\",\n    \"last\": \"secondo promemoria\",\n    \"call\": \"telefonata\",\n    \"waiting\": \"in attesa\",\n    \"complete\": \"completo\"\n  },\n  \"callReason\": \"Nessuna risposta dopo 3 email: telefonare al cliente\",\n  \"summarySubject\": \"Stato documenti per lo studio\"\n};\nconst RULES = {\n  \"everyDays\": 5,\n  \"maxReminders\": 3\n};\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 planChaser(clients, today, text, rules) {\n  return clients.map((c) => {\n    const missing = c.requested.filter((d) => !c.received.includes(d));\n    const base = { id: c.id, client: c.client, email: c.email, missing, remindersBefore: c.reminders, remindersAfter: c.reminders, stage: null, nextReminder: null, subject: null, body: null, reason: null };\n    if (missing.length === 0) return { ...base, status: \"complete\", action: text.actions.complete };\n    if (c.reminders >= rules.maxReminders) return { ...base, status: \"call\", action: text.actions.call, reason: text.callReason };\n    const since = c.lastReminder ? daysBetween(c.lastReminder, today) : Infinity;\n    if (since < rules.everyDays) return { ...base, status: \"waiting\", action: text.actions.waiting, nextReminder: addDays(c.lastReminder, rules.everyDays) };\n    const stage = Math.min(c.reminders, 2);\n    const vars = { client: c.client, period: text.period, firm: text.firm, list: missing.map((d) => \"- \" + d).join(\"\\n\"), deadline: longDate(addDays(today, rules.everyDays), text.locale) };\n    const action = stage === 0 ? text.actions.first : stage === 1 ? text.actions.reminder : text.actions.last;\n    return { ...base, status: \"email\", action, stage, remindersAfter: c.reminders + 1, subject: fill(text.subjects[stage], vars), body: fill(text.bodies[stage], vars) };\n  });\n}\n\nreturn planChaser($input.all().map((i) => i.json), today, TEXT, RULES).map((o) => ({ json: o }));"
      },
      "id": "58246a6a-703f-4e00-a613-13d3698048c6",
      "name": "Decidi cosa inviare",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        100
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "30ad3d13-62ba-4500-ae9a-44aa60e9d915",
              "leftValue": "={{ $json.status }}",
              "rightValue": "email",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "3b606d50-7d86-4400-a557-14e9452edc30",
      "name": "Solo i clienti da sollecitare via email",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        740,
        0
      ],
      "notes": "I clienti da telefonare non ricevono email.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "fromEmail": "studio@studioferri.example",
        "toEmail": "={{ $json.email }}",
        "subject": "={{ $json.subject }}",
        "emailFormat": "text",
        "text": "={{ $json.body }}",
        "options": {}
      },
      "id": "1167410a-fb47-4200-af50-38b377532526",
      "name": "Invia richiesta / sollecito",
      "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).\nconst TEXT = {\n  \"locale\": \"it-IT\",\n  \"firm\": \"Studio Ferri Commercialisti (demo)\",\n  \"period\": \"settembre 2026\",\n  \"subjects\": [\n    \"Documenti necessari per {period}\",\n    \"Promemoria: documenti per {period}\",\n    \"Secondo promemoria: documenti per {period} entro il {deadline}\"\n  ],\n  \"bodies\": [\n    \"Gentile {client},\\n\\nper preparare la contabilità di {period} ci servono ancora:\\n{list}\\n\\nPuò semplicemente rispondere a questa email allegando i file. Grazie!\\n\\n{firm}\",\n    \"Gentile {client},\\n\\nle ricordiamo che attendiamo ancora questi documenti per {period}:\\n{list}\\n\\nRiesce a inviarceli entro questa settimana? Basta rispondere allegando i file.\\n\\n{firm}\",\n    \"Gentile {client},\\n\\nnon abbiamo ancora ricevuto questi documenti per {period}:\\n{list}\\n\\nCi servono entro il {deadline} per completare la contabilità nei tempi. Se qualcosa non è chiaro, risponda a questa email e la aiutiamo.\\n\\n{firm}\"\n  ],\n  \"actions\": {\n    \"first\": \"prima richiesta\",\n    \"reminder\": \"promemoria\",\n    \"last\": \"secondo promemoria\",\n    \"call\": \"telefonata\",\n    \"waiting\": \"in attesa\",\n    \"complete\": \"completo\"\n  },\n  \"callReason\": \"Nessuna risposta dopo 3 email: telefonare al cliente\",\n  \"summarySubject\": \"Stato documenti per lo studio\"\n};\n\nfunction chaserSummary(outcomes, text) {\n  const open = outcomes.filter((o) => o.status !== \"complete\");\n  return {\n    subject: text.summarySubject,\n    emailsSent: outcomes.filter((o) => o.status === \"email\").length,\n    toCall: outcomes.filter((o) => o.status === \"call\").map((o) => o.client),\n    waiting: outcomes.filter((o) => o.status === \"waiting\").length,\n    complete: outcomes.length - open.length,\n    lines: open.map((o) => o.client + \": \" + o.missing.join(\", \") + \" → \" + o.action).join(\"\\n\"),\n  };\n}\n\nreturn [{ json: chaserSummary($input.all().map((i) => i.json), TEXT) }];"
      },
      "id": "ae12c1c2-953a-4800-a025-b87bf99d5cae",
      "name": "Riepilogo per lo studio",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        240
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Ogni giorno feriale alle 9:00": {
      "main": [
        [
          {
            "node": "Elenco clienti (dati demo)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Test manuale": {
      "main": [
        [
          {
            "node": "Elenco clienti (dati demo)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Elenco clienti (dati demo)": {
      "main": [
        [
          {
            "node": "Decidi cosa inviare",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decidi cosa inviare": {
      "main": [
        [
          {
            "node": "Solo i clienti da sollecitare via email",
            "type": "main",
            "index": 0
          },
          {
            "node": "Riepilogo per lo studio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Solo i clienti da sollecitare via email": {
      "main": [
        [
          {
            "node": "Invia richiesta / sollecito",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "c0eea3a8-0971-4000-aed9-da11bc46d918",
  "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",
      "contabilità"
    ]
  }
}
