{
  "name": "Rodopi Dent - SMS Reminder (\u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24,
              "triggerAtHour": 12
            }
          ]
        }
      },
      "id": "schedule-trigger-001",
      "name": "Daily at 12:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -1200,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Calculate tomorrow's date in Sofia timezone\nconst now = new Date();\nconst sofia = new Intl.DateTimeFormat('en-CA', {\n  timeZone: 'Europe/Sofia',\n  year: 'numeric',\n  month: '2-digit',\n  day: '2-digit'\n});\n\n// Get tomorrow\nconst tomorrow = new Date(now);\ntomorrow.setDate(tomorrow.getDate() + 1);\n\nconst tomorrowStr = sofia.format(tomorrow); // YYYY-MM-DD format\n\n// Also format for SMS display (DD.MM.YYYY)\nconst displayDate = new Intl.DateTimeFormat('bg-BG', {\n  timeZone: 'Europe/Sofia',\n  day: '2-digit',\n  month: '2-digit',\n  year: 'numeric'\n}).format(tomorrow);\n\nreturn [{\n  json: {\n    tomorrowISO: tomorrowStr,\n    tomorrowDisplay: displayDate,\n    timeMin: tomorrowStr + 'T00:00:00+02:00',\n    timeMax: tomorrowStr + 'T23:59:59+02:00'\n  }\n}];"
      },
      "id": "calc-tomorrow-001",
      "name": "Calculate Tomorrow",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -980,
        300
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendarId": "rodopi.dent@gmail.com",
        "returnAll": true,
        "options": {
          "timeMin": "={{ $json.timeMin }}",
          "timeMax": "={{ $json.timeMax }}",
          "singleEvents": true,
          "orderBy": "startTime"
        }
      },
      "id": "get-calendar-001",
      "name": "Get Tomorrow Events",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.2,
      "position": [
        -760,
        300
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Get events and filter by reminder flag (\ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435: \u0414\u0410)\nconst items = $input.all();\nconst tomorrowDisplay = $('Calculate Tomorrow').first().json.tomorrowDisplay;\n\nconst eventsToRemind = [];\nconst debug = {\n  totalEvents: items.length,\n  skippedCancelled: 0,\n  skippedNoReminderTag: 0,\n  skippedReminderNo: 0,\n  skippedStatusDone: 0,\n  skippedNoPhone: 0,\n  skippedSamples: []\n};\n\nfunction logSkip(reason, event) {\n  if (debug.skippedSamples.length < 3) {\n    debug.skippedSamples.push({\n      reason,\n      summary: event.summary || '',\n      descriptionPreview: (event.description || '').slice(0, 300)\n    });\n  }\n}\n\nfor (const item of items) {\n  const event = item.json;\n  const desc = event.description || '';\n\n  if (event.status === 'cancelled') {\n    debug.skippedCancelled++;\n    continue;\n  }\n\n  // Look for \ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 tag (use LAST match \u2014 admin edits append to description)\n  const allReminderMatches = [...desc.matchAll(/\ud83d\udd14\\s*\u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435:\\s*(\u0414\u0410|\u041d\u0415|YES|NO)/gi)];\n  if (allReminderMatches.length === 0) {\n    debug.skippedNoReminderTag++;\n    logSkip('no-reminder-tag', event);\n    continue;\n  }\n  const flagVal = allReminderMatches[allReminderMatches.length - 1][1].toUpperCase();\n  if (flagVal !== '\u0414\u0410' && flagVal !== 'YES') {\n    debug.skippedReminderNo++;\n    logSkip('reminder-NO', event);\n    continue;\n  }\n\n  if (/\u0421\u0442\u0430\u0442\u0443\u0441:\\s*\u0417\u0430\u0432\u044a\u0440\u0448\u0435\u043d/i.test(desc)) {\n    debug.skippedStatusDone++;\n    continue;\n  }\n\n  // Try labelled phone first, then any Bulgarian-format phone anywhere in description\n  let phone = '';\n  const labelled = desc.match(/(?:\ud83d\udcde\\s*\u0422\u0435\u043b:|\ud83d\udcf1|Phone:|\u0422\u0435\u043b\u0435\u0444\u043e\u043d:)\\s*([+]?[0-9\\s\\-()]{8,20})/i);\n  if (labelled) {\n    phone = labelled[1].replace(/[\\s\\-()]/g, '');\n  } else {\n    const generic = desc.match(/(\\+359\\d{9}|\\+?0?\\d{9,10})/);\n    if (generic) phone = generic[1].replace(/[\\s\\-()]/g, '');\n  }\n  if (!phone) {\n    debug.skippedNoPhone++;\n    logSkip('no-phone', event);\n    continue;\n  }\n\n  // Optional email\n  let email = '';\n  const emailMatch = desc.match(/(?:\u2709\ufe0f\\s*\u0418\u043c\u0435\u0439\u043b:|Email:|\u0418\u043c\u0435\u0439\u043b:)\\s*([^\\s\\n]+@[^\\s\\n]+)/i);\n  if (emailMatch) email = emailMatch[1].trim();\n\n  // Notification channels (default: sms)\n  let channels = ['sms'];\n  const channelsMatch = desc.match(/\ud83d\udcec\\s*\u0418\u0437\u0432\u0435\u0441\u0442\u0438\u044f:\\s*([^\\n]+)/i);\n  if (channelsMatch) {\n    const raw = channelsMatch[1].toLowerCase();\n    channels = [];\n    if (raw.includes('sms')) channels.push('sms');\n    if (raw.includes('\u0438\u043c\u0435\u0439\u043b') || raw.includes('email')) channels.push('email');\n    if (channels.length === 0) channels = ['sms'];\n  }\n\n  // Time in Sofia TZ\n  let time = '';\n  const startISO = event.start?.dateTime || event.start?.date;\n  if (startISO) {\n    const eventDate = new Date(startISO);\n    time = new Intl.DateTimeFormat('en-GB', {\n      timeZone: 'Europe/Sofia',\n      hour: '2-digit',\n      minute: '2-digit',\n      hour12: false\n    }).format(eventDate);\n  }\n\n  const patientName = (event.summary || '').replace(/[\u2705\ud83d\udfe2\u23f3\ud83d\udfe1\ud83d\udd14]/g, '').trim() || '\u041f\u0430\u0446\u0438\u0435\u043d\u0442';\n\n  eventsToRemind.push({\n    eventId: event.id,\n    patientName,\n    phone,\n    email,\n    channels,\n    date: tomorrowDisplay,\n    time,\n    summary: event.summary\n  });\n}\n\nif (eventsToRemind.length === 0) {\n  return [{ json: { noEvents: true, message: '\u041d\u044f\u043c\u0430 \u0447\u0430\u0441\u043e\u0432\u0435 \u0437\u0430 \u043d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 \u0443\u0442\u0440\u0435', debug } }];\n}\n\nreturn eventsToRemind.map(e => ({ json: e }));"
      },
      "id": "filter-confirmed-001",
      "name": "Filter Events Needing Reminder",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -540,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.noEvents }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "check-events-001",
      "name": "Has Events?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.1,
      "position": [
        -320,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Prepare SMS for each patient\nconst event = $input.first().json;\n\n// Normalize Bulgarian phone number\nlet phone = event.phone.replace(/\\s+/g, '').replace(/[^0-9+]/g, '');\nif (phone.startsWith('0')) {\n  phone = '+359' + phone.substring(1);\n} else if (!phone.startsWith('+')) {\n  phone = '+359' + phone;\n}\n\n// Create reminder message\nconst message = `\u0420\u043e\u0434\u043e\u043f\u0438 \u0414\u0435\u043d\u0442: \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 - \u0438\u043c\u0430\u0442\u0435 \u0447\u0430\u0441 \u0443\u0442\u0440\u0435 (${event.date}) \u0432 ${event.time}. \u041e\u0447\u0430\u043a\u0432\u0430\u043c\u0435 \u0432\u0438!`;\n\nreturn [{\n  json: {\n    phone: phone,\n    message: message,\n    patientName: event.patientName,\n    eventId: event.eventId\n  }\n}];"
      },
      "id": "prepare-sms-001",
      "name": "Prepare Reminder SMS",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -100,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://fcm.googleapis.com/v1/projects/sms-sender-27028/messages:send",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"message\": {\n    \"token\": \"cdLIn7RwRYuNi7hvpHeUxU:APA91bEiNvE_SCHwrHE-O1nArEOf6Phpltza4HH3vxoRKr8bHlfX9nK_q_4r0xlC9YYvfGxZHNPL5X6uQ44H6dSqbTcwFYQ68ZLYh2G8JtOfpHLoWy7E4aI\",\n    \"data\": {\n      \"phone\": \"{{ $json.phone }}\",\n      \"message\": \"{{ $json.message }}\"\n    }\n  }\n}",
        "options": {
          "allowUnauthorizedCerts": false
        }
      },
      "id": "send-fcm-001",
      "name": "Send FCM to Android",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        120,
        200
      ],
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "## \ud83d\udcf1 SMS \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 \u0437\u0430 \u0447\u0430\u0441\u043e\u0432\u0435\n\n**\u0418\u0437\u043f\u044a\u043b\u043d\u044f\u0432\u0430 \u0441\u0435:** \u0412\u0441\u0435\u043a\u0438 \u0434\u0435\u043d \u0432 12:00 (Europe/Sofia)\n\n**\u041b\u043e\u0433\u0438\u043a\u0430 (OPT-IN):**\n1. \u0412\u0437\u0438\u043c\u0430 \u0432\u0441\u0438\u0447\u043a\u0438 \u0441\u044a\u0431\u0438\u0442\u0438\u044f \u043e\u0442 Google Calendar \u0437\u0430 \u0423\u0422\u0420\u0415\u0428\u041d\u0418\u042f \u0434\u0435\u043d\n2. \u26a0\ufe0f \u0418\u0437\u043f\u0440\u0430\u0449\u0430 \u0421\u0410\u041c\u041e \u0430\u043a\u043e \u0432 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u0442\u043e \u0438\u043c\u0430 \u0442\u0430\u0433 `\ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435: \u0414\u0410`\n   - \u0411\u0435\u0437 \u0442\u0430\u0433 \u2192 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430 (\u0441\u0442\u0430\u0440\u0438/\u0430\u0434\u043c\u0438\u043d \u0447\u0430\u0441\u043e\u0432\u0435 \u0431\u0435\u0437 opt-in)\n   - `\ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435: \u041d\u0415` \u2192 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\n3. \u0418\u0437\u0432\u043b\u0438\u0447\u0430 \u0442\u0435\u043b\u0435\u0444\u043e\u043d (`\ud83d\udcde \u0422\u0435\u043b:`) \u043e\u0442 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u0442\u043e\n4. \u0418\u0437\u043f\u0440\u0430\u0449\u0430 SMS \u0447\u0440\u0435\u0437 Android \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 (FCM)\n\n**\u041a\u043e\u0439 \u043f\u043e\u043b\u0443\u0447\u0430\u0432\u0430 \u0442\u0430\u0433 \u0414\u0410:**\n- \u041d\u041e\u0412 \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043f\u0440\u0435\u0437 \u0441\u0430\u0439\u0442\u0430 \u2192 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u0414\u0410\n- \u0420\u0435\u0434\u043e\u0432\u0435\u043d \u043f\u0430\u0446\u0438\u0435\u043d\u0442 \u043f\u0440\u0435\u0437 \u0441\u0430\u0439\u0442\u0430 \u2192 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u041d\u0415\n- \u0427\u0430\u0441 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u043d \u043e\u0442 \u0430\u0434\u043c\u0438\u043d \u043f\u0430\u043d\u0435\u043b\u0430 \u2192 \u043a\u0430\u043a\u0432\u043e\u0442\u043e \u0435 \u0446\u044a\u043a\u043d\u0430\u043b\u0430 \u0434\u043e\u043a\u0442\u043e\u0440\u043a\u0430\u0442\u0430\n- Edit \u043d\u0430 \u0447\u0430\u0441 \u2192 \u043a\u0430\u043a\u0432\u043e\u0442\u043e \u0435 \u0446\u044a\u043a\u043d\u0430\u0442\u043e \u043f\u0440\u0438 \u0440\u0435\u0434\u0430\u043a\u0446\u0438\u044f\n\n**SMS \u0442\u0435\u043a\u0441\u0442:**\n```\n\u0420\u043e\u0434\u043e\u043f\u0438 \u0414\u0435\u043d\u0442: \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435 - \u0438\u043c\u0430\u0442\u0435 \u0447\u0430\u0441 \u0443\u0442\u0440\u0435 (DD.MM.YYYY) \u0432 HH:MM. \u041e\u0447\u0430\u043a\u0432\u0430\u043c\u0435 \u0432\u0438!\n```\n\n**\u0418\u0437\u0438\u0441\u043a\u0432\u0430\u043d\u0438\u044f:**\n- Google Calendar credentials\n- FCM/Google API credentials (RODOPI_DENT_SMS)\n- Android \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044a\u0442 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0438 \u0441 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442",
        "height": 380,
        "width": 360
      },
      "id": "sticky-note-001",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1200,
        -80
      ]
    },
    {
      "parameters": {},
      "id": "no-op-001",
      "name": "No Events - Skip",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        -100,
        400
      ]
    }
  ],
  "connections": {
    "Daily at 12:00": {
      "main": [
        [
          {
            "node": "Calculate Tomorrow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Tomorrow": {
      "main": [
        [
          {
            "node": "Get Tomorrow Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Tomorrow Events": {
      "main": [
        [
          {
            "node": "Filter Events Needing Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Events?": {
      "main": [
        [
          {
            "node": "Prepare Reminder SMS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Events - Skip",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Reminder SMS": {
      "main": [
        [
          {
            "node": "Send FCM to Android",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Events Needing Reminder": {
      "main": [
        [
          {
            "node": "Has Events?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Sofia"
  },
  "versionId": "reminder-v1-001",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "id": "sms-reminder-workflow",
  "tags": [
    {
      "id": "U4AUJ7Gip9wo0aTE",
      "name": "Rodopi Dent"
    }
  ]
}