# Forno — powiadomienia SMS o rezerwacji (forno-notify.js) Moduł `forno-notify.js` wysyła klientowi SMS z przypomnieniem o opłacie rezerwacji **6 h przed upływem 24-godzinnego terminu**. SMS wychodzi **z telefonu właściciela** (własna karta SIM, brak opłat za bramkę) przez aplikację [SMS Gateway for Android](https://sms-gate.app) — ten sam mechanizm, którego używa SliceHub (`core/Notifications/Channels/PersonalPhoneChannel.php`, provider `smsgateway_android`). > **Stan repo:** aplikacja rezerwacji (`forno-server.js`, `views/forno-*.ejs`, > `reservations.json`, `forno-settings.json`) **jeszcze nie istnieje**. Ten moduł jest > gotowy do wpięcia, gdy powstanie. Gra biletowa (`server.js`) nie jest dotknięta. --- ## 1. Jak to działa (architektura) ``` Render (forno-server.js) chmura sms-gate.app telefon właściciela POST /tasks/run (cron co 15 min) (darmowa, publiczna) (Android + SIM, online) └─ selectDueReminders() ──HTTPS──▶ POST /3rdparty/v1/messages ──FCM push──▶ apka SMSGate ──▶ SMS do klienta └─ sendReminder(rez, settings) Basic Auth login/hasło (fallback: polling co 15 min) ``` * Render **nie musi** widzieć telefonu — łączy się tylko z publicznym API `https://api.sms-gate.app`. Telefon łączy się z tą samą chmurą z dowolnej sieci (Wi‑Fi w lokalu / LTE), bez publicznego IP, tunelu ani VPS. * Tryb **Local Server** aplikacji (adres `http://192.168.x.x:8080`, którego SliceHub używa w LAN pizzerii) **nie zadziała z Render** — Render nie ma dostępu do sieci lokalnej. Zawsze używaj trybu **Cloud Server**. * Usypianie instancji free na Render nie przeszkadza: zewnętrzny cron budzi serwer wywołaniem `POST /tasks/run`, a wysyłka to jedno krótkie żądanie HTTPS. * Brak konfiguracji lub błąd → `sendReminder()` zwraca `{ ok:false, ... }`, loguje ostrzeżenie i **nigdy nie rzuca**. `/tasks/run` działa dalej, a rezerwacje trafiają na ręczną listę przypomnień w panelu managera (`selectDueReminders()`). ## 2. API modułu ```js const notify = require('./forno-notify'); notify.selectDueReminders(reservations, now?) // rezerwacje w oknie ≤6h przed payment_deadline, // bez reminder_sent_at, status ∉ {paid, cancelled, expired} await notify.sendReminder(reservation, settings) // → { ok:true, messageId, phone, text } // { ok:false, skipped:true, reason:'not_configured'|'invalid_phone'|'unknown_provider' } // { ok:false, error:'...' } notify.buildReminderText(reservation, settings) // podgląd treści (panel managera) notify.isEnabled() // czy kanał SMS jest skonfigurowany notify.getStatus() // diagnostyka: lastError, lastSuccess, sentCount… ``` Pola rezerwacji używane przez moduł: `id` (lub `number`), `phone`, `people`, `payment_deadline` (ISO), `status`, `reminder_sent_at`. Pola ustawień: `price_per_person` (domyślnie 25), `venue_name` (domyślnie „Forno”). Przykładowa treść (bez polskich znaków → mieści się w 1 segmencie SMS): ``` Forno: przypomnienie o rezerwacji nr 7 (4 os.). Do zaplaty 100 zl na miejscu w pizzerii Forno do 10.09 17:00. Po tym terminie rezerwacja wygasa. ``` Szkic użycia w `forno-server.js`: ```js app.post('/tasks/run', requireTasksToken, async (req, res) => { const due = notify.selectDueReminders(reservations); for (const r of due) { const result = await notify.sendReminder(r, settings); if (result.ok) { r.reminder_sent_at = new Date().toISOString(); } } saveReservations(); // + github-sync res.json({ due: due.length, sms: notify.getStatus() }); }); ``` Zmiana kanału = dodanie funkcji do obiektu `channels` w `forno-notify.js` i ustawienie `FORNO_SMS_PROVIDER`. --- ## 3. CO MUSISZ SKONFIGUROWAĆ OSOBIŚCIE ### 3.1 Telefon / bramka SMS (trzeba utrzymać online) 1. Na telefonie z Androidem i kartą SIM, z której mają wychodzić SMS-y (najlepiej **dedykowany, stary telefon** podłączony do ładowarki w lokalu), zainstaluj aplikację **SMS Gateway for Android** — APK z (na Android 15+ Play Protect może blokować instalację — patrz FAQ na docs.sms-gate.app). 2. Nadaj uprawnienia **SMS** (na Android 15+ trzeba ręcznie w Ustawienia → Aplikacje → SMSGate → Uprawnienia; jeśli wyszarzone: „Zezwól na ustawienia z ograniczeniami”). 3. W aplikacji włącz przełącznik **Cloud Server** i kliknij **Offline → Online**. Aplikacja **sama** wygeneruje `login` i `hasło` (brak rejestracji, e-maila, numeru). Zapisz je — to wartości `FORNO_SMS_USERNAME` / `FORNO_SMS_PASSWORD`. 4. Wyłącz optymalizację baterii dla aplikacji (Ustawienia → System w aplikacji) i sprawdź dla producenta telefonu. 5. (Opcjonalnie) Ustawienia → Messages → Limits: np. 30/h, 100/dzień — chroni SIM przed blokadą antyspamową operatora. 6. Panel www do podglądu kolejki/statusów: (logowanie tym samym loginem/hasłem). ### 3.2 Zmienne środowiskowe na Render (serwis `forno-rezerwacje`) | Zmienna | Wartość / skąd | |---|---| | `FORNO_SMS_PROVIDER` | `smsgateway_android` | | `FORNO_SMS_USERNAME` | login z aplikacji (Settings → Cloud Server → Credentials) | | `FORNO_SMS_PASSWORD` | hasło z aplikacji (j.w.) | | `FORNO_SMS_BASE_URL` | `https://api.sms-gate.app/3rdparty/v1` (domyślne; zmień tylko przy własnym Private Server) | | `FORNO_SMS_SIM_NUMBER` | opcjonalnie `1`/`2` gdy telefon ma dwie karty SIM | | `FORNO_SMS_DRY_RUN` | `1` na czas testów (loguje treść, nie wysyła), potem `0` | | `TASKS_TOKEN` | długi losowy ciąg (`openssl rand -hex 32`) — autoryzacja `POST /tasks/run` | Szablon serwisu (zakomentowany, do odkomentowania gdy powstanie `forno-server.js`) jest w `render.yaml`. Definicja serwisu `trzciamajka` (gra biletowa) pozostaje bez zmian. Alternatywa `generic_http` (Tasker/MacroDroid/własny webhook): `FORNO_SMS_PROVIDER=generic_http`, `FORNO_SMS_URL=https://…/send`, `FORNO_SMS_BEARER_TOKEN=…`. Wysyła `POST {"to":"+48…","body":"…"}`. Wymaga jednak **publicznego** URL-a osiągalnego z Render — dlatego domyślną drogą jest chmura sms-gate.app. ### 3.3 Połączenie Render ↔ bramka Nic do konfiguracji sieciowo: Render → `api.sms-gate.app` (HTTPS, Basic Auth), telefon → `api.sms-gate.app` (FCM push / SSE / polling co 15 min). Jedyny warunek: telefon ma Internet i aplikacja jest „Online”. ### 3.4 Zewnętrzny cron (np. cron-job.org) 1. Załóż darmowe konto na (lub podobnym: UptimeRobot z POST, GitHub Actions `schedule`). 2. Nowy cronjob: * URL: `https://forno-rezerwacje.onrender.com/tasks/run` * Metoda: **POST** * Nagłówek: `Authorization: Bearer ` * Harmonogram: **co 15 minut** (`*/15 * * * *`) * Timeout: ≥ 60 s (zimny start instancji free na Render trwa 30–60 s). 3. Na cron-job.org włącz powiadomienia o błędach, żeby dowiedzieć się, gdy endpoint zwraca ≠ 2xx. Okno 6 h + cron co 15 min ⇒ klient dostaje SMS między 6:00 a 5:45 h przed terminem. ### 3.5 Kiedy SMS-y PRZESTANĄ działać * Telefon offline / rozładowany / aplikacja ubita przez system (patrz „Don't kill my app”), aplikacja przełączona na „Offline” lub tryb Local Server. * Brak środków/limitu SMS na karcie SIM (błąd `RESULT_ERROR_GENERIC_FAILURE`) lub antyspam operatora (`RESULT_ERROR_LIMIT_EXCEEDED`; operatorzy zwykle 30–200 SMS/h). * Zmiana hasła w aplikacji bez aktualizacji `FORNO_SMS_PASSWORD` na Render (HTTP 401). * Wiadomość czeka w kolejce > `FORNO_SMS_TTL_SECONDS` (domyślnie 6 h) lub > 24 h (twardy limit chmury sms-gate.app) → zostaje porzucona, przypomnienie się nie doręczy. * Cron przestał wołać `/tasks/run` (wygasłe konto cron-job.org, zmieniony token). * Numer klienta nie daje się znormalizować do `+48XXXXXXXXX` → `skipped: invalid_phone`. * Chmura sms-gate.app jest darmowa „bez limitów, chyba że wpływasz na innych użytkowników”; autorzy zastrzegają, że **nowe** funkcje/nowi użytkownicy mogą być kiedyś płatni () — dla kilkudziesięciu SMS-ów na koncert to bez znaczenia, ale warto wiedzieć. We wszystkich tych przypadkach `/tasks/run` nadal działa; rezerwacje z okna 6 h pozostają na liście `selectDueReminders()` w panelu managera i można zadzwonić/napisać ręcznie. ### 3.6 Test end-to-end 1. **Lokalnie, bez wysyłki:** `npm run test:forno` (mock HTTP, 10 testów). 2. **Chmura → telefon, bez aplikacji rezerwacji:** z komputera ```bash curl -X POST -u "LOGIN:HASLO" \ --json '{"textMessage":{"text":"Test Forno"},"phoneNumbers":["+48TWOJNUMER"]}' \ https://api.sms-gate.app/3rdparty/v1/messages ``` Oczekiwane: HTTP 201 z `{"id":"…","state":"Pending"}` i SMS na Twoim telefonie w kilka sekund. Status: `curl -u "LOGIN:HASLO" https://api.sms-gate.app/3rdparty/v1/messages/` → `Sent`/`Delivered`. 3. **Z modułu, jednorazowo (Node 20):** ```bash FORNO_SMS_USERNAME=LOGIN FORNO_SMS_PASSWORD=HASLO node -e " require('./forno-notify').sendReminder( { id: 'TEST', phone: '+48TWOJNUMER', people: 2, payment_deadline: new Date(Date.now()+6*3600e3).toISOString() }, {} ).then(r => console.log(r))" ``` 4. **Na Render:** najpierw `FORNO_SMS_DRY_RUN=1`, zrób rezerwację testową z deadline za < 6 h, ręcznie wywołaj `POST /tasks/run` z tokenem, sprawdź w logach Render wpis `[forno-notify] DRY RUN → +48…`. Następnie `FORNO_SMS_DRY_RUN=0`, powtórz — SMS ma dojść, a `getStatus().lastSuccess` w odpowiedzi `/tasks/run` ma być ustawione. 5. Sprawdź `https://dashboard.sms-gate.app` → Messages: status `Sent` (albo `Failed` + powód).