Żywy dokument projektowy. Kompleksowy flow przekształcenia obecnej aplikacji single-tenant w SaaS dla wielu gospodarstw.
Sekcja zerowa dodana po audycie v1.1. Przeczytaj zanim wjedziesz w detale. Zawiera: jeden diagram pokazujący cały user flow end-to-end, podsumowanie audytu spójności (gdzie są luki do zaadresowania), konwencje czytania dokumentu i słownik terminów branżowych.
Pełna podróż klienta od pierwszego kontaktu (reklama) do retencji po latach. Każdy etap odsyła do dedykowanej sekcji. To jest kanoniczna sekwencja — wszystkie pozostałe diagramy w dokumencie są jej fragmentami.
flowchart TD
subgraph ACQ["🎯 AKWIZYCJA — sekcja 2"]
A1[Reklama / SEO /
polecenie] --> A2[Landing
agro360.pl]
A2 --> A3{CTA
'Wypróbuj'?}
end
subgraph SIGN["✍️ REJESTRACJA — sekcja 3"]
A3 -->|Tak| B1[Formularz
email + hasło]
B1 --> B2[Weryfikacja
email]
B2 --> B3[Auto-login
+ start trialu 30d]
end
subgraph ONB["🚀 ONBOARDING — sekcja 4"]
B3 --> C1{Pierwsza wizyta?}
C1 -->|Nowy user, brak
zaproszenia| C2[Wizard 5 kroków:
farma → pole → uprawa
→ zabieg → zaproszenie]
C1 -->|Zaproszony user| C3[Mini-onboarding:
tutorial roli,
tour gospodarstwa]
C2 --> C4[💡 AHA MOMENT
koszt/ha widoczny]
C3 --> D1
C4 --> D1
end
subgraph CORE["📦 CODZIENNE UŻYCIE — sekcja 5, 6, 11"]
D1[Dashboard +
switcher gospodarstw] --> D2[Loop:
zabiegi · magazyn ·
faktury · koszty · raporty]
D2 --> D3{Współpraca?}
D3 -->|Tak| D4[Zaprasza
pracownika /
agronoma]
D3 -->|Nie| D2
D4 --> D2
end
subgraph CONV["💳 KONWERSJA — sekcja 8"]
D2 -.->|dzień 23 trialu| E1[Banner '7 dni']
E1 --> E2[Wpięcie karty]
E2 -->|dzień 30| E3{Auto-charge
OK?}
E3 -->|Tak| F1[Klient płacący
Pro 49 zł/mies]
E3 -->|Nie| E4[Dunning:
1d/3d/5d/7d retry]
E4 -->|Sukces| F1
E4 -->|Fail 7d| E5[Read-only
mode]
E5 -->|Wpięcie nowej karty| F1
E5 -.->|60d| E6[Konto
zawieszone]
end
subgraph RET["🔁 RETENCJA & EKSPANSJA — sekcja 6, 8"]
F1 --> G1[Codzienne użycie]
G1 --> G2{Sygnał
at-risk?}
G2 -->|Nie| G1
G2 -->|Tak| G3[Health score
alert → success]
G3 --> G1
G1 --> G4[Upsell:
roczny plan /
Enterprise]
end
subgraph EXIT["🚪 OFFBOARDING — sekcja 9, 11"]
G1 -.->|user chce wyjść| H1[Anulowanie:
krótka ankieta,
działa do końca okresu]
H1 --> H2[Dane zachowane
12 mies]
H2 -.->|Reactivate| F1
H2 -.->|Eksport + usuń| H3[RODO export
+ hard delete]
end
A3 -->|Nie| Z1[Newsletter /
retargeting]
Z1 -.-> A2
style A1 fill:#e8f0e0,stroke:#4a7c2c
style C4 fill:#fff3d6,stroke:#c08a2a
style F1 fill:#4a7c2c,stroke:#2d5016,color:#fff
style E5 fill:#fff3d6,stroke:#c08a2a
style E6 fill:#ffe9e9,stroke:#e88a8a
style H3 fill:#ffe9e9,stroke:#e88a8a
Aha moment = pierwszy zabieg wpisany w wizardzie onboardingowym, po którym system pokazuje wyliczony koszt zł/ha i potwierdza odjęcie z magazynu.
Mierzona metryka:time_to_aha= czas od kliknięcia „Załóż konto" do pierwszegoworkHistory.createdz wyliczonym kosztem. Target: < 15 min. Jeśli > 30 min — alert do success, prawdopodobny churn w trialu.
Wynik przeglądu całego dokumentu pod kątem: spójność user flow, logiczne luki, sprzeczności między sekcjami, braki w obsłudze edge case'ów. Każdy punkt ma 📝 Audit notatkę inline w odpowiedniej sekcji. Wszystkie to decyzje do podjęcia przed implementacją.
Każdy punkt rozstrzygnięty inline w odpowiedniej sekcji (oznaczenie „decyzja v2.0, audit #N RESOLVED"). Krótkie podsumowanie decyzji:
is_internal, smoke testy Playwright po deployu.Status dokumentu: v2.0 — kompletny, gotowy do walidacji przez Przemka (rolnika-autora prototypu). Patrz sekcja 13.
| Element | Znaczenie |
|---|---|
| OK | Sekcja kompletna, do walidacji przez user research |
| WIP | Sekcja częściowa, w trakcie pisania |
| TODO | Placeholder, do wypełnienia |
| ✅ Spójność z apką | Istniejąca apka jako referencja domenowa pokrywa ten obszar — wiemy że logika działa |
| 🐛 Bug / niespójność | Konkretny problem do naprawy (najczęściej w istniejącej apce, jeśli będziemy z niej coś brać) |
| ⚠️ Warn / decyzja | Otwarty problem prawno-techniczno-biznesowy, wymaga decyzji przed implementacją |
| 💡 Pomysł | Opcjonalne usprawnienie, nie blokuje MVP. Backlog na fazę 2/3 |
| 📝 Audit | Recenzja spójności dokumentu — luka w logice, sprzeczność, brak flow. Lista zbiorcza w 0.2 |
| Tabela ze zieloną głową | Standardowa porównawcza / decyzyjna |
| Mermaid diagram | Flow / sekwencja / ERD — renderowane przez CDN, wymaga internetu |
kod | Nazwa funkcji / pola / endpointa |
agro360-mvp2.html (l. XXXX) | Referencja do istniejącej apki jako prototypu domenowego. Po decyzji „budujemy od nowa" — to inspiracja, nie code base do migracji. |
agro360-mvp2.html jest prototypem domenowym — pokazuje że logika biznesowa rolnicza (mieszaniny ŚOR, koszty, magazyn, kalkulatory) została przemyślana i działa. Code SaaS piszemy od zera, ale UI/UX i decyzje branżowe z istniejącej apki to nasza biblioteka wiedzy. Każde odwołanie do numeru linii (l. XXXX) to pointer dla developera/projektanta: „idź zobacz jak to było zrobione, zachowaj logikę, napisz po nowemu".
Skróty branżowe (rolnictwo) i techniczne (SaaS) używane w dokumencie:
Brakowało tego w v1.0 — dodaję zbiorczo dla wszystkich modułów:
| Wymaganie | Target | Pomiar |
|---|---|---|
| Time to First Byte (apka) | < 800 ms p95 | Real User Monitoring |
| First Contentful Paint (landing) | < 1.5 s na 4G | Lighthouse / WebPageTest |
| API response time (read) | < 200 ms p95 | APM / Sentry Performance |
| API response time (write) | < 500 ms p95 | APM |
| Uptime SLO | 99.5% (≈ 3.6h downtime/mies) | UptimeRobot / Better Stack |
| Wsparcie przeglądarek | Chrome / Safari / Firefox / Edge — ostatnie 2 wersje | BrowserStack regress |
| Wsparcie mobile | iOS 15+ / Android 10+ (≈ > 95% rynku PL) | Analytics user agent |
| Dostępność (a11y) | WCAG 2.1 AA — kontrast, klawiatura, screen reader | axe-core w CI |
| Bezpieczeństwo | OWASP Top 10 audited, HTTPS only, CSP headers, regularne pentesty Enterprise | OWASP ZAP, ręczne pentesty rocznie |
| RODO | Hosting EU, eksport < 24h, usunięcie < 30d, DPA dla wszystkich processorów | Audit prawny rocznie |
| Backup / RPO | RPO 1h (max strata 1h danych) / RTO 4h (max czas przywracania) | DR drill kwartalnie |
| Język | MVP: tylko PL. Faza 3: EN, opcjonalnie UA (rolnicy ukraińscy w PL) | — |
| Lokalizacja danych | Daty DD.MM.YYYY, waluta PLN, jednostki ha/kg/t/l (zgodnie z apką) | — |
Zanim zaprojektujemy jakikolwiek ekran SaaS, musimy mieć jasność: dla kogo to robimy, jaki problem rozwiązujemy i jak na tym zarabiamy. Ta sekcja jest fundamentem — wszystkie kolejne decyzje (cennik, onboarding, role, ficzery) będą się do niej odwoływać.
Status: to są tezy wstępne, nie kontrakt. Wymagają walidacji — najlepiej przez rozmowę z 5-10 realnymi rolnikami z grupy docelowej zanim zaczniemy budować.
Dziś typowy rolnik z grupy docelowej radzi sobie z dokumentacją gospodarstwa w sposób fragmentaryczny:
| Obszar | Jak jest dziś | Co boli |
|---|---|---|
| Rejestr ŚOR | Papierowy zeszyt, czasem PDF do druku z portalu | Wpadki podczas kontroli ARiMR, kary 1-5 tys. zł |
| Koszty | Faktury w segregatorze, „mniej więcej wiem" | Brak wiedzy ile zł/ha realnie kosztuje pszenica vs buraki |
| Magazyn nawozów/ŚOR | „Liczę co jest, jak wchodzę do magazynu" | Zaskoczenie brakiem w szczycie sezonu, zakupy na ostatnią chwilę po wyższych cenach |
| Historia pól | W głowie + zeszyt sąsiada | Trudno wrócić do „co siałem 3 lata temu na polu X" |
| Mieszaniny ŚOR | Pamięć, telefon do agronoma | Niepoprawna kolejność = zapchany opryskiwacz, strata środków |
AGRO 360 to cyfrowy notatnik gospodarstwa, który prowadzi się sam. Wpisujesz raz pracę polową — system odlicza z magazynu, liczy koszt na hektar, dokleja do rejestru ŚOR i przygotowuje raport pod kontrolę. Bez Excela, bez papieru, bez „muszę to jeszcze ogarnąć wieczorem".
Trzy obietnice, które różnicują nas od konkurencji (eDWIN, SatAgro, FarmFacts):
Trzy modele do rozważenia, z plusami i minusami:
| Model | Przykład | Plus | Minus |
|---|---|---|---|
| Flat per gospodarstwo | 49 zł/mies za gospodarstwo, niezależnie od ha | Prosty, przewidywalny, łatwo zakomunikować | Małe i duże gospodarstwa płacą tyle samo — nie zarabiamy na dużych |
| Per hektar | 2 zł/ha/mies (min 50 zł) | Sprawiedliwy, skaluje się z wartością klienta | Trudniejszy do komunikacji, klient kalkuluje przed użyciem |
| Tier-based (Free / Pro / Enterprise) | Free do 5 pól / Pro 49 zł / Enterprise indywidualnie | Klasyczny SaaS, low-friction onboarding (Free), upsell przez ograniczenia | Więcej do utrzymania (3 wersje), trzeba zdecydować co dać za darmo |
Decyzja v2.0: Tier-based z Free trial 30 dni → Pro 49 zł/mies za gospodarstwo (flat) → Agronom Pro 99 zł/mies (osobny plan dla doradców z N klientami) → Enterprise indywidualnie (gospodarstwa > 300 ha lub multi-farm). Trial = pełen Pro z jednym ogranicznikiem: brak generowania pakietu kontrolerskiego PDF (killer feature = upgrade trigger).
Założenia finansowe na podstawie benchmarków SaaS B2B + specyfika rolnictwa (sezonowość, długie cykle decyzyjne):
| Metryka | Hipoteza | Wyliczenie |
|---|---|---|
| ARPU (Avg Revenue Per User) | 49 zł/mies dla Pro, 99 zł dla Agronom Pro | Cena planu × dominujący plan |
| Średnia retencja | 24 miesiące (Pro), 36 mies (Agronom) | Rolnictwo = mała mobilność, gdy raz wpisuje dane, nie chce migrować |
| LTV (Lifetime Value) | 1 176 zł (Pro), 3 564 zł (Agronom) | ARPU × retencja w miesiącach |
| Max CAC (Customer Acquisition Cost) | 392 zł (Pro), 1 188 zł (Agronom) | LTV / 3 — standard SaaS |
| Payback period | 8 mies (Pro), 12 mies (Agronom) | Akceptowalne dla SaaS |
| Trial → paid conversion | 25-35% | SaaS B2B benchmark z trialem bez karty: 20-40% |
| Monthly churn | < 5% | Niższy niż konsumencki SaaS (8-10%), wyższy niż enterprise (1-2%) |
| Break-even (gospodarstw) | ~150 płatnych Pro | Pokrycie kosztów ~7350 zł MRR (Supabase + Stripe + emaile + domena + ZUS solo founder) |
Co to oznacza: żeby AGRO 360 było opłacalne dla solo foundera (Adam) potrzebujemy 150 płatnych klientów w ~12 mies. Przy CAC 392 zł budżet marketingowy max ~60 000 zł na akwizycję pierwszych 150. To prawdopodobne przez polecenia + content SEO + 1-2 kampanie Facebook z targetowaniem rolniczym.
Marta to klient płacący, NIE darmowy kanał. Osobny plan Agronom Pro (99 zł/mies) z dostępem do N gospodarstw klientów (z ich zgodą per gospodarstwo). Marta nie konsumuje slotów w planach klientów-rolników. Klienci-rolnicy płacą za swoje Pro osobno. To dwa równoległe kanały przychodu.
Mechanika:
flowchart LR
A[Reklama FB / polecenie
sąsiada / Google] --> B[Landing page
agro360.pl]
B --> C{Decyzja:
wypróbować?}
C -->|Tak| D[Rejestracja
email + hasło]
C -->|Nie| Z[Newsletter /
retargeting]
D --> E[Onboarding wizard
dodaj 1. pole]
E --> F[Pierwsza praca polowa
wpisana w 5 min]
F --> G{Aha moment?
koszt/ha widoczny}
G -->|Tak| H[Codzienne użycie
tydzień 1-4]
G -->|Nie| Y[Churn w trialu]
H --> I[Koniec trialu 30 dni]
I --> J{Konwersja na
płatny plan?}
J -->|Tak| K[Klient płacący
49 zł/mies]
J -->|Nie| Y
K --> L[Retencja
codzienne użycie]
L --> M[Upsell:
zaproś agronoma /
dodaj pracownika]
style A fill:#e8f0e0,stroke:#4a7c2c
style K fill:#4a7c2c,stroke:#2d5016,color:#fff
style Y fill:#fff3d6,stroke:#c08a2a
style G fill:#fff3d6,stroke:#c08a2a
Landing page to jedna strona statyczna (osobny deployment, np. agro360.pl) — nie część aplikacji. Cel: w 10 sekund odpowiedzieć „dla kogo to jest" i przekonwertować na rejestrację. Optymalizowane pod mobile (rolnik na traktorze ze smartfonem).
Logo „AGRO 360" + linki: Funkcje · Cennik · Kontakt · Zaloguj się · [Wypróbuj za darmo] (zielony CTA)
3-4 cytaty od pilotażowych rolników (z imieniem, regionem, liczbą ha, zdjęciem). Na starcie: „Pierwsi użytkownicy mówią:" Z czasem: logo izb rolniczych / organizacji branżowych.
Powtórzony hero CTA „Wypróbuj 30 dni za darmo". Footer: RODO, regulamin, kontakt, social media, blog (jeśli będzie).
flowchart TD
Start([Źródło ruchu]) --> Src{Skąd?}
Src -->|Reklama FB/Google| LP[Landing
agro360.pl]
Src -->|Polecenie sąsiada| LP
Src -->|Branżowy portal| LP
LP --> Read{Czyta hero?}
Read -->|Bounce 70%| End1([Wyjście])
Read -->|Scroll dalej| Features[Sekcje
funkcje/jak działa]
Features --> Price[Cennik]
Price --> Decide{Decyzja}
Decide -->|CTA Wypróbuj| Reg[Rejestracja
sekcja 3]
Decide -->|Demo| Demo[Wideo 2min]
Decide -->|FAQ| FAQ[Czyta FAQ]
Demo --> Reg
FAQ --> Decide
Decide -->|Wyjście| Retarget[Pixel FB
retargeting]
style LP fill:#e8f0e0,stroke:#4a7c2c
style Reg fill:#4a7c2c,stroke:#2d5016,color:#fff
style End1 fill:#fff3d6,stroke:#c08a2a
Cel rejestracji: najmniejszy możliwy próg wejścia. Im więcej pól w formularzu, tym większy drop-off. Decyzja o planie odkładamy na koniec trialu — user najpierw musi zobaczyć wartość.
Trzy modele rozważane, wybieramy C:
| Model | Kiedy karta? | Trade-off |
|---|---|---|
| A) Reverse trial | Karta na starcie, brak opłat 30 dni | Wyższa konwersja po trialu, znacznie niższy sign-up rate |
| B) Free forever (limit) | Nigdy obowiązkowo | Najwięcej userów, najtrudniejszy upsell, kanibalizacja |
| C) Trial bez karty | Po 30 dniach, opcjonalna konwersja | Balans: low friction + wymuszony moment decyzji |
Alternatywnie u góry: [Kontynuuj z Google] (OAuth — 1 klik, znacznie wyższy conversion)
flowchart TD
Start([CTA na landingu]) --> Form[Formularz
email + hasło + imię]
Form --> Submit{Submit}
Submit -->|Email zajęty| Err1[Komunikat:
masz już konto?
link do loginu]
Submit -->|Walidacja OK| Send[Wyślij email
weryfikacyjny]
Send --> Wait[Ekran:
sprawdź skrzynkę]
Wait --> Click{User klika link?}
Click -->|Tak, w 24h| Verify[Konto
aktywne]
Click -->|Nie w 24h| Remind[Email przypominający
po 24h]
Remind --> Click
Click -->|Nigdy w 7 dni| Cleanup[Konto usuwane
email się zwalnia]
Verify --> Login[Auto-login
+ start trialu 30 dni]
Login --> Onboard[Onboarding wizard
sekcja 4]
style Form fill:#e8f0e0,stroke:#4a7c2c
style Onboard fill:#4a7c2c,stroke:#2d5016,color:#fff
style Err1 fill:#ffe9e9,stroke:#e88a8a
tomek@gmail.com"| Trial (30 dni) | Pro | Agronom Pro | Enterprise | |
|---|---|---|---|---|
| Cena | 0 zł | 49 zł/mies (490 zł/rok = 2 mies gratis) | 99 zł/mies (990 zł/rok) | Wycena indywidualna (typ. od 0.5 zł/ha/mies) |
| Dla kogo | Każdy nowy rolnik | Rolnik 20-200 ha (Tomek) | Doradca z portfolio klientów (Marta) | Gospodarstwa > 300 ha lub multi-farm |
| Liczba gospodarstw | 1 (własne) | 1 (własne) | 1 własne + dostęp do N klientów | Bez limitu |
| Operatorzy (właściciel + współwł. + pracownicy) | 3 łącznie | 3 łącznie | 3 łącznie (we własnym) | Bez limitu |
| Agronomi-goście | Bez limitu (osobny licznik) | Bez limitu | — | Bez limitu |
| Księgowa (read-only finanse) | 1 gratis | 1 gratis | 1 gratis | Bez limitu |
| Pola, magazyny, kalkulatory, historia | Bez limitu | Bez limitu | Bez limitu | Bez limitu + integracje |
| Rejestr ŚOR (przeglądanie) | ✅ | ✅ | ✅ | ✅ |
| 📋 Pakiet kontrolerski PDF (ARiMR-ready) | ❌ upgrade trigger | ✅ | ✅ | ✅ + XML do e-Wniosku |
| Eksport danych JSON / Excel | ✅ | ✅ | ✅ | ✅ |
| Dostęp do bazy ŚOR 100+ | ✅ | ✅ | ✅ | ✅ |
| Wsparcie | Email + chat | Email + chat + priorytet | Dedykowany opiekun | |
| API | — | — | Read API klientów | Pełne API |
Logika ogranicznika trialu (audit #4 RESOLVED): Trial daje pełnię użycia (smak prawdziwego produktu), ale brak pakietu kontrolerskiego PDF. To jedyny ograniczenie i jednocześnie killer feature — w momencie gdy Tomkowi „za 3 dni kontrola ARiMR", upgrade jest naturalny i emocjonalnie uzasadniony.
Trial bez karty (3.1) wymaga jasnego flow KIEDY karta się pojawia. Decyzja: trzy okazje:
flowchart TD
Trial1[Trial dzień 1-22] --> Day23{Dzień 23}
Day23 --> Banner1[🟡 Banner non-blocking:
'Zostało 7 dni - wpij metodę
płatności żeby przejść płynnie.
Nic nie pobierzemy do dnia 30.']
Banner1 --> Choice1{User klika?}
Choice1 -->|Tak, wpina| Stored1[💳 Karta zapisana,
banner znika,
dzień 30 auto-charge]
Choice1 -->|Ignoruje| Day28[Dzień 28]
Day28 --> Banner2[🟠 Banner mocniejszy:
'2 dni do końca trialu.
Bez karty stracisz możliwość
wpisywania.']
Banner2 --> Day30{Dzień 30}
Day30 -->|Karta wpięta| Charge[Auto-charge 49 zł]
Day30 -->|Karta nie wpięta| ReadOnly[🔴 Read-only mode
+ ekran konwersji 3.6.1]
ReadOnly --> Settings[Opcja 3: link
'Ustawienia → Subskrypcja'
dostępny zawsze]
Settings --> Choice1
Stored1 --> Day30
style Stored1 fill:#e8f0e0,stroke:#4a7c2c
style ReadOnly fill:#fff3d6,stroke:#c08a2a
style Charge fill:#4a7c2c,stroke:#2d5016,color:#fff
Trzy okazje, trzy poziomy presji:
Ustawienia → Subskrypcja → Wpij kartę. Dla userów którzy chcą od pierwszego dnia.Karta pobierana przez Stripe Customer Portal (hosted) — minimum integracji, max bezpieczeństwa. Wspierane metody: BLIK, karta, Apple/Google Pay (przez Stripe + Przelewy24).
Na dzień 30 bez wpiętej karty: read-only mode (user widzi dane, ale nie może dodawać) + ekran „Przedłuż żeby pisać".
splash (l. 1033) → moduleSelect → farmRoleSelect (owner/worker). W SaaS to znika — po loginie user trafia od razu do swojego gospodarstwa, a rola wynika z jego konta (nie z wyboru per session). Trzeba refaktorować boot flow apki: usunąć selectRole() (l. 3520), zastąpić sprawdzeniem currentUser.role z backendu.
To najważniejszy ekran całego SaaS. Tu się decyduje czy user zostanie. Cel: w <10 min user widzi pierwszą wartość („aha moment") — koszt na hektar swojego pola po wpisaniu jednego zabiegu.
sorDatabase, 4 dostawców nawozów). User nie patrzy na pustkę.
flowchart LR
Login([Po loginie]) --> S1[Krok 1
Nazwa gospodarstwa
+ lokalizacja]
S1 --> S2[Krok 2
Dodaj 1. pole]
S2 --> S3[Krok 3
Przypisz uprawę]
S3 --> S4[Krok 4
Wpisz 1. zabieg
opcjonalne]
S4 --> S5[Krok 5
Zaproś osobę
opcjonalne]
S5 --> Done[Dashboard
z banerem 'aha']
S1 -.skip.-> S2
S2 -.skip.-> S3
S3 -.skip.-> Done
S4 -.skip.-> S5
style Done fill:#4a7c2c,stroke:#2d5016,color:#fff
style S2 fill:#e8f0e0,stroke:#4a7c2c
style S4 fill:#fff3d6,stroke:#c08a2a
Pole minimum: nazwa + powierzchnia. Reszta opcjonalna.
Pełna lista upraw oparta na obecnej liście w apce — patrz renderFieldForm().
Uproszczony formularz na bazie istniejącego renderWorkForm('protection') z apki. Tylko podstawy:
Po zapisaniu → AHA MOMENT:
✅ Zabieg zapisany. Koszt tej operacji: 234,50 zł (18,76 zł/ha). Tak liczone będą wszystkie kolejne. Magazyn aktualny.
Szczegóły ról: sekcja 6.
ownerDashboard z apkiPo wizardzie checklisty się utrzymują w widgetcie na dashboardzie (collapsable), znikają gdy user wykona zadanie. Zniknięcie wszystkich = end-of-onboarding event w analytics.
Sekcje 4.2-4.9 opisują onboarding foundera (zakłada własną farmę). Drugi scenariusz: user dostał zaproszenie (s.6.3) — np. Tomek zaprosił Marka jako pracownika lub Marta zaprosiła swojego klienta. Nie pokazujemy wizarda farmy (nie ma własnej), zamiast tego dedykowany mini-onboarding 3 ekrany.
flowchart LR
Email([Klik w zaproszeniu]) --> Reg[Skrócona rejestracja:
imię + hasło
email pre-filled]
Reg --> S1[Krok 1
Powitanie spersonalizowane:
'Tomek zaprosił Cię do
Gospodarstwa Kowalskich
jako Pracownika']
S1 --> S2[Krok 2
Mini-tour roli:
'Jako Pracownik możesz:
✅ Wpisywać zabiegi
✅ Sprawdzać magazyn
❌ Nie zobaczysz Faktur']
S2 --> S3[Krok 3
Quick win:
'Spróbuj wpisać
zabieg testowy.
Tomek zobaczy notyfikację.']
S3 --> Dash[Dashboard
workerDashboard]
style S2 fill:#e8f0e0,stroke:#4a7c2c
style Dash fill:#4a7c2c,stroke:#2d5016,color:#fff
Po Wariancie B: user trafia do swojego workerDashboard (lub odpowiedniego dla roli — agronomDashboard / accountantDashboard). Onboarding checklisty z 4.9 też się pojawiają, ale w skróconej wersji (np. tylko „Zaktualizuj swój profil" + „Wpisz pierwszy realny zabieg").
Edge case: user zaakceptował zaproszenie do gospodarstwa Tomka, ale chce też założyć własne. Z dashboardu → switcher gospodarstw (s.6.4) → [+ Załóż własne gospodarstwo] → przechodzi do Wariantu A (wizard foundera 4.2-4.9).
Wizard kończy „aha momentem". Dalej zaczyna się zaprojektowana sekwencja retencji — 7 zaplanowanych touchpoints w 30 dni trialu, każdy z konkretnym celem: edukacja → użycie → konwersja.
| Dzień | Kanał | Trigger | Treść / Cel |
|---|---|---|---|
| D+0 wieczór | Po wizardzie | „Witaj w AGRO 360. Zacznij od tych 3 rzeczy" — 3 najprostsze quick wins (uzupełnij magazyn ŚOR z bazy / dodaj kolejne pole / wypróbuj kalkulator MTZ) | |
| D+3 | Email + Push | Jeśli 0 nowych zabiegów po wizardzie | „Wpisanie zabiegu zajmuje 30 sekund — pokazujemy jak" + 60-sek wideo (lub GIF) |
| D+7 | Zawsze | „Twój pierwszy tydzień w AGRO 360 — podsumowanie i 3 funkcje których nie odkryłeś". Personalizowane wg aktywności (np. „dodałeś 5 zabiegów, zobacz Analizę kosztów"). | |
| D+14 | Email + In-app banner | Zawsze | „Pełnia wartości = współpraca. Zaproś syna / pracownika / agronoma" — link bezpośrednio do flow zaproszeń (s.6.3) |
| D+21 | Jeśli niski engagement (< 3 zabiegów łącznie) | Wysyłany ręcznie przez success (lub auto): „Cześć Tomek, widzimy że może coś nie działa — zadzwońmy 15 min?" + Calendly link | |
| D+23 | Email + Banner | Zawsze | „Zostało 7 dni trialu — wpij metodę płatności żeby przejść płynnie" (patrz s.3.6 — pierwsze okno karty) |
| D+28 | Email + Banner + Push | Jeśli karta nie wpięta | „2 dni do końca trialu. Bez wpięcia karty stracisz możliwość wpisywania zabiegów" — pierwsze straszenie utratą |
| D+30 | Email + Ekran konwersji | Zawsze | Ekran 3.6.1 — „Twój trial się skończył" + podsumowanie wartości („wpisałeś 47 zabiegów, oszacowane oszczędności 1240 zł") |
| D+33 | Jeśli nie skonwertował | Last call: „Tęsknimy. 50% rabatu na pierwszy miesiąc jeśli wrócisz w 7 dni" — kupon Stripe |
Metryki sukcesu sekwencji:
Wszystkie touchpoints konfigurowalne w admin panelu (s.10) jako szablony email/push z możliwością A/B testów.
saveSettingsFarm() (l. 6412) — zapis nazwy gospodarstwa, regionu, telefonu → użyjemy w kroku 1showFieldForm() / saveField() (l. 5728/5805) → wykorzystamy w kroku 2 (uproszczony)renderWorkForm('protection') (l. 8086) → w kroku 4, ale w drastycznie skróconej wersjisorDatabase (l. 7338) — 12 preparatów seed → dostępne od razu po onboardingufieldWorkHistory (l. 7864) to zmienna globalna w pamięci — po reload znika. W SaaS to krytyczne — pierwszy zabieg w onboardingu MUSI przeżyć reload. Wymaga: (a) doraźnie zapis w localStorage przed migracją, (b) docelowo POST do backendu. To samo z invoicesRegister i costsRegister.
Po decyzji Adama: budujemy od nowa. Prototyp Przemka (agro360-mvp2.html, ~9700 linii vanilla JS) jest biblioteką wiedzy domenowej — dowodzi że logika biznesowa rolnicza (mieszaniny ŚOR, koszty zł/ha, magazyn auto-odliczanie, kalkulatory MTZ) jest przemyślana i działa. SaaS buduje się od zera w nowoczesnym stacku (s.12), z prototypu kopiujemy logikę i decyzje branżowe, nie kod.
(l. XXXX) w tej sekcji = pointery dla developera/projektanta: „idź zobacz w prototypie jak Przemek to rozwiązał, zachowaj decyzję merytoryczną, napisz po nowemu w React + TS". Prototyp NIE jest code base do migracji — jest dokumentacją wykonalną.
| Moduł w prototypie (linia) | Rola w SaaS | Decyzja MVP |
|---|---|---|
splash (l. 1033) | Loading screen — może zostać jako fallback ładowania danych z backendu | ✅ zachowujemy |
moduleSelect (l. 1129) | Niepotrzebny — w SaaS user trafia od razu do gospodarstwa | ❌ usuwamy |
farmRoleSelect | Niepotrzebny — rola wynika z kont użytkownika (sekcja 6) | ❌ usuwamy |
ownerDashboard (l. 1129) | Główny ekran dla roli „właściciel" | ✏️ refaktor (dane z backendu, nie hardcoded) |
workerDashboard (l. 1404) | Główny ekran dla roli „pracownik" — uproszczony widok | ✏️ refaktor |
ownerProfilePanel (l. 1701) | Profil gospodarstwa (nie usera) — sekcja 9 to user profile | ✏️ rozdzielić user/farm |
fieldWorkPanel (l. 2519) | CORE — prace polowe, najczęściej używany ekran | ✅ zachowujemy + bug fix (persystencja) |
warehousePanel (l. 2596) | CORE — magazyny zboża/ŚOR/nasion | ✅ zachowujemy |
invoicesPanel (l. 2627) | Faktury zakupu/sprzedaży | ✅ + bug fix (persystencja) |
fieldMapPanel (l. 2655) | Mapa i lista pól | ✏️ realne GPS zamiast mock |
settingsPanel (l. 2744) | Ustawienia gospodarstwa — patrz też sekcja 9 (user) | ✏️ rozdzielić |
reportsPanel (l. 2990) | Raporty PDF — ARiMR, koszty, zabiegi | ✏️ server-side PDF zamiast print |
costsPanel (l. 3121) | Analiza kosztów per uprawa/pole | ✅ + bug fix |
paymentsPanel (l. 2501) | Historia płatności gospodarstwa (wydatki) — NIE billing SaaS | ✅ zachowujemy (zmienić nazwę żeby nie myliło z billing — patrz s.8) |
fertilizerPricesPanel | Ceny nawozów dostawców | ✏️ realne ceny zamiast hardcoded |
historyPanel | Historia zabiegów | ✅ zachowujemy |
labelSearchPanel | Wyszukiwanie ŚOR po etykietach | ✏️ rozbudować sorDatabase (12 → 100+) |
calculatorPanel | Kalkulatory siewu/gęstości/inwestycji | ✅ zachowujemy |
hireServicePanel | Zlecenie usługi (kombajn, opryskiwacz) | ✅ zachowujemy |
flowchart TD
Login([Login]) --> Dash[ownerDashboard]
Dash --> P1{Co chcę zrobić?}
P1 -->|Wpisać zabieg| FW[fieldWorkPanel]
FW --> Type[Wybór typu:
uprawa/siew/nawóz/
ochrona/żniwa]
Type --> Form[renderWorkForm]
Form --> Save[saveFieldWork]
Save --> Wh[Odejmij z magazynu
+ koszty]
Wh --> Dash
P1 -->|Sprawdzić magazyn| Mag[warehousePanel]
Mag --> Sub[Sub-screen:
zboże/ŚOR/nasiona]
Sub --> Dash
P1 -->|Dodać fakturę| Inv[invoicesPanel]
Inv --> InvSave[saveInvoice]
InvSave --> CostUpd[Update costsRegister]
CostUpd --> Dash
P1 -->|Wygenerować raport ARiMR| Rep[reportsPanel]
Rep --> PDF[Server PDF]
PDF --> Download[Pobierz]
P1 -->|Sprawdzić koszty| Costs[costsPanel]
Costs --> Filter[Filtr: uprawa/pole/data]
Filter --> Dash
style Dash fill:#4a7c2c,stroke:#2d5016,color:#fff
style Save fill:#e8f0e0,stroke:#4a7c2c
style PDF fill:#fff3d6,stroke:#c08a2a
To 60% wartości produktu. Krok po kroku jak działa w apce dziś — i co zmieni SaaS:
renderWorkForm('protection')addToMixture() dodaje preparaty z chemWarehouse, dla każdego dawka + nr zezwoleniarenderMixtureOrder() sortuje kolejność wg formulacji (WG → SC → EC → SL)saveFieldWork(): zapis do fieldWorkHistory (in-memory!), odejmuje z magazynu, dolicza kosztGET /api/farms/:farmId/fields zamiast localStorage.farmFieldschemWarehouse dzielona z global sorDatabase (rozszerzonym do 100+) + custom userasaveFieldWork() → POST /api/farms/:farmId/work-history, z optimistic update (offline → kolejka sync)Przemek już to przemyślał i działa — w nowym buildzie zachowujemy identyczne reguły biznesowe, ale piszemy w React + TypeScript:
Prototyp ma znaną logikę, ale wygląd i interakcje wymagają unowocześnienia:
Wszystko związane z SaaS jako modelem biznesowym:
Trzeba świadomie nie kopiować:
fieldWorkHistory, invoicesRegister, costsRegister żyją tylko w pamięci JS (l. 7864, 4963, 3480) i znikają po reload. W SaaS: zawsze backend persist + optimistic UI.grainWarehouse ma fieldId 1-3, ale farmFields pusty. W SaaS: walidacja referential integrity przez foreign keys + seeds spójne.paymentsPanel = wydatki rolnika (NIE billing SaaS). W SaaS używamy farmExpenses dla rolnika, subscription dla billingu SaaS.Apka dziś ma proste rozróżnienie owner / worker (zmienna currentRole, l. 3464). To za mało dla SaaS. Persona „Marta-agronom" obsługuje wiele gospodarstw — potrzebujemy modelu user należy do wielu gospodarstw przez różne role.
| Rola | Kto to | Może | Nie może |
|---|---|---|---|
| Właściciel 👑 | Zakładający konto / przekazane uprawnienia | Wszystko: dodawać/usuwać użytkowników, zmieniać plan, zarządzać billingiem, usunąć gospodarstwo | — |
| Współwłaściciel 🤝 | Małżonek, brat, wspólnik | Wszystko poza usuwaniem gospodarstwa i odbieraniem ról innym właścicielom | Usunąć gospodarstwo, usunąć innego właściciela |
| Pracownik 👷 | Operator ciągnika, syn na żniwa | Wpisywać zabiegi, sprawdzać magazyn, widzieć swoje pola, generować raporty operacyjne | Widzieć faktury / koszty / billing, zarządzać użytkownikami, edytować ustawienia gospodarstwa |
| Agronom (gość) 👩🌾 | Doradca z zewnątrz (Marta) | Czytać wszystko, dodawać rekomendacje (notki), eksportować raporty. Może być w wielu gospodarstwach. | Edytować zabiegi, zmieniać magazyn, widzieć billing |
erDiagram
User ||--o{ Membership : "ma"
Farm ||--o{ Membership : "ma"
Membership }o--|| Role : "jest"
User {
uuid id PK
string email UK
string name
string phone
timestamp createdAt
}
Farm {
uuid id PK
string name
string region
timestamp createdAt
string subscriptionStatus
}
Membership {
uuid id PK
uuid userId FK
uuid farmId FK
enum role
timestamp joinedAt
timestamp invitedAt
string status
}
Role {
enum value
}
Kluczowe: User i Farm to relacja many-to-many przez Membership. Jeden user może być właścicielem jednego gospodarstwa i agronomem w 5 innych. Jedno gospodarstwo ma wielu users.
flowchart TD
Owner([Właściciel]) --> Settings[Ustawienia →
Użytkownicy]
Settings --> Add[+ Zaproś osobę]
Add --> Form[Email + rola]
Form --> Send[Wyślij
zaproszenie]
Send --> Email[Email z linkiem
i 7-dniowym tokenem]
Email --> Click{Klika link}
Click -->|Ma już konto| LinkAccount[Doklej Membership
do istniejącego konta]
Click -->|Nie ma konta| Register[Skrócona rejestracja
od razu z Membership]
Click -->|Nie klika 7 dni| Expire[Token wygasa
email przypominający]
LinkAccount --> Notify[Notyfikacja:
nowe gospodarstwo]
Register --> Notify
Notify --> InApp[User loguje się
widzi switcher gospodarstw]
style Owner fill:#e8f0e0,stroke:#4a7c2c
style InApp fill:#4a7c2c,stroke:#2d5016,color:#fff
Przy zmianie gospodarstwa: reload danych (lub przeładowanie strony), context (currentFarmId) aktualizowany w session i URL.
| Imię / Email | Rola | Dołączył | Ostatnio aktywny | Akcje |
|---|---|---|---|---|
| Tomek (Ty) | 👑 Właściciel | 2026-05-01 | teraz | — |
| Anna Kowalska anna@mail.com | 🤝 Współwłaściciel | 2026-05-03 | wczoraj | Zmień rolę / Usuń |
| Marek Nowak marek@mail.com | 👷 Pracownik | 2026-05-10 | 2h temu | Zmień rolę / Usuń |
| Marta (agronom) marta@mail.com | 👩🌾 Agronom | 2026-05-15 | 3 dni temu | Cofnij dostęp |
| jan@mail.com | ⏳ Pracownik (oczekuje) | zaproszony 2026-05-20 | — | Wyślij ponownie / Anuluj |
[+ Zaproś osobę] Limit planu Pro: 3 użytkowników (2 wykorzystanych + Ty)
Plan Pro ma limit „3 userów" — ale 4 role oraz pomysł 5. (księgowa). Decyzja: licznik dzielony na 3 kategorie:
| Kategoria | Kto się liczy | Limit Pro | Limit Enterprise |
|---|---|---|---|
| Operatorzy | Właściciel + Współwłaściciele + Pracownicy | 3 łącznie | Bez limitu |
| Agronomi-goście | Każdy Agronom z planem „Agronom Pro" zaproszony przez właściciela | Bez limitu (oni płacą za siebie) | Bez limitu |
| Księgowi | Rola read-only do faktur i kosztów | 1 gratis | Bez limitu |
Przykład realny dla Tomka (Pro 49 zł/mies): Tomek (właściciel) + Anna (współwłaścicielka) + Marek (pracownik) = 3 operatorzy = limit OK. Plus Marta (agronom) gratis, plus pani Kowalska (księgowa) gratis. Razem 5 osób w gospodarstwie za 49 zł.
Co się dzieje gdy właściciel chce zaprosić 4. operatora (limit 3 wykorzystany)? Soft block w UI + jasne opcje:
flowchart TD
Click[+ Zaproś osobę] --> Form[Email + rola: Pracownik]
Form --> Submit{Submit}
Submit --> Check{Limit operatorów
w planie OK?}
Check -->|Tak| Send[Wyślij zaproszenie]
Check -->|Nie — 3/3| Block[⚠️ Modal:
'Wykorzystałeś limit 3
operatorów planu Pro']
Block --> Opts{User wybiera}
Opts -->|Upgrade do Enterprise| Up[Stripe checkout
upgrade]
Opts -->|Usuń istniejącego| Remove[Lista 3 obecnych
z opcją usunięcia]
Opts -->|Anuluj| Cancel[Zamknij modal]
Remove --> RemoveUser[Backend usuwa
membership]
RemoveUser --> Send
Up --> Active[Subscription upgraded]
Active --> Send
style Block fill:#fff3d6,stroke:#c08a2a
style Send fill:#e8f0e0,stroke:#4a7c2c
Backend: POST /api/farms/:id/invitations z body {email, role} sprawdza limit per role-category. Zwraca 402 Payment Required + payload {current, limit, upgrade_url} jeśli przekroczono. Frontend renderuje modal z 3 opcjami.
Analogicznie dla limitu „1 gospodarstwo" przy próbie utworzenia 2-giego w planie Pro — modal „Drugie gospodarstwo wymaga Enterprise. [Skontaktuj się z nami] [Anuluj]".
| Funkcja / Panel | Właściciel | Współwł. | Pracownik | Agronom | Księgowa |
|---|---|---|---|---|---|
| Dashboard | 👑 pełny | 👑 pełny | uproszczony | pełny (read) | tylko widget kosztów |
| Prace polowe — wpisać | ✅ | ✅ | ✅ | ❌ | ❌ |
| Prace polowe — edytować cudze | ✅ | ✅ | ❌ tylko swoje | ❌ | ❌ |
| Magazyn — czytać | ✅ | ✅ | ✅ | ✅ | ❌ |
| Magazyn — dodawać/edytować | ✅ | ✅ | ❌ | ❌ | ❌ |
| Faktury — czytać | ✅ | ✅ | ❌ | ❌ | ✅ |
| Faktury — dodawać | ✅ | ✅ | ❌ | ❌ | ❌ |
| Koszty — czytać + eksport | ✅ | ✅ | ❌ | ❌ | ✅ |
| Pola — dodawać/edytować | ✅ | ✅ | ❌ | ❌ | ❌ |
| Raporty PDF | ✅ | ✅ | ✅ (operacyjne) | ✅ | ✅ (finansowe) |
| Rekomendacje / notki | ✅ | ✅ | ✅ | ✅ | ❌ |
| Ustawienia gospodarstwa | ✅ | ✅ | ❌ | ❌ | ❌ |
| Zarządzanie users | ✅ | ✅ (poza właścicielami) | ❌ | ❌ | ❌ |
| Billing | ✅ | ❌ | ❌ | ❌ | ❌ |
| Usunięcie gospodarstwa | ✅ | ❌ | ❌ | ❌ | ❌ |
Dla wszystkich zmian zapisujemy: {userId, action, resource, before, after, timestamp}. Dostępne w panelu „Ustawienia → Historia zmian" (właściciel + współwłaściciel). Kluczowe gdy ktoś zarzuca „kto skasował tę fakturę".
Persona Marta obsługuje 10-30 gospodarstw klientów. Sam switcher gospodarstw (s.6.4) to za mało — Marta potrzebuje jednego widoku z którego widzi co dzieje się u wszystkich klientów. To dedykowany dashboard po loginie dla usera z planem „Agronom Pro".
Centralna funkcja agronoma — wszystko co wymaga jego reakcji w jednym miejscu:
| Typ alertu | Trigger | Akcja |
|---|---|---|
| Pytanie klienta | Rolnik napisał notatkę z @marta | Odpowiedz / Wezwij na rozmowę |
| Niska dawka / zła kolejność | Klient wpisał zabieg z parametrami poza normą | Komentarz „rozważ X" / Akceptuj |
| Pogoda krytyczna | Prognoza mróz/deszcz dla regionu klienta | SMS do klienta z rekomendacją |
| Sezonowy reminder | Zbliża się termin agronomiczny (siew, ochrona) | Wygeneruj plan zabiegów |
| Health score spadł | Klient nie loguje się > 14 dni | Zadzwoń, zaproponuj pomoc |
Privacy: Marta widzi dane konkretnego klienta TYLKO za zgodą (s.6.7). Klient może w każdej chwili odebrać dostęp. Cross-farm raporty są zawsze pseudonimizowane gdy wyświetlane (np. „Klient #3" zamiast nazwy), z opcją odsłonięcia tylko przez Martę (audit log).
ownerDashboard, workerDashboard) świetnie mapują się na role „Właściciel" i „Pracownik". Współwłaściciel używa ownerDashboard, agronom — nowego (sekcja 5: rozszerzenie). Mniej pracy niż się wydawało.
selectRole() (l. 3520) pozwala userowi wybrać czy jest właścicielem czy pracownikiem. W SaaS to jest dziura bezpieczeństwa — pracownik kliknąłby „właściciel" i widział faktury. Trzeba: (a) usunąć farmRoleSelect z bootu, (b) rolę pobierać z API GET /api/me/memberships/:farmId, (c) wymuszać role gate na poziomie backendu (frontend gating to tylko UX, nie security).
Fundament SaaS. Dziś apka ma jedno gospodarstwo per device w localStorage. Docelowo: N gospodarstw w jednej bazie, z twardą izolacją między nimi. Tej sekcji nie da się odpuścić — wszystkie pozostałe są od niej zależne.
| Strategia | Opis | Plus | Minus |
|---|---|---|---|
| DB per tenant | Każde gospodarstwo = osobna baza | Maks izolacja, łatwy eksport/usunięcie | Operacyjnie ciężko przy 1000+ tenantach, migracje schematu mordercze |
| Schema per tenant | Wspólna baza, osobny schemat PostgreSQL | Średnia izolacja, łatwiejsze niż osobne DB | Nadal kosztowne migracje, limity PG na ~liczbę schematów |
| Shared DB + tenant_id | Jedna baza, każda tabela ma farm_id, RLS Postgres pilnuje | Operacyjnie najprościej, migracje jedna, koszty niskie | Wymaga rygoru — jeden zapomniany WHERE = wyciek danych |
Wybieramy shared DB + tenant_id z Row Level Security w PostgreSQL — standard branżowy dla SaaS naszej skali.
erDiagram
User ||--o{ Membership : ma
Farm ||--o{ Membership : ma
Farm ||--o{ Field : ma
Farm ||--o{ WorkHistory : ma
Farm ||--o{ ChemWarehouse : ma
Farm ||--o{ SeedWarehouse : ma
Farm ||--o{ GrainWarehouse : ma
Farm ||--o{ Invoice : ma
Farm ||--o{ Cost : ma
Farm ||--o{ Subscription : ma
Field ||--o{ WorkHistory : "dotyczy"
User ||--o{ WorkHistory : "wpisał"
WorkHistory }o--|| MixtureItem : "ma"
Subscription ||--o{ Invoice_SaaS : "generuje"
Farm {
uuid id PK
string name
string region
timestamp createdAt
uuid ownerId FK
}
Field {
uuid id PK
uuid farmId FK
string name
decimal areaHa
string crop
json gpsPolygon
json parcelNumbers
}
WorkHistory {
uuid id PK
uuid farmId FK
uuid fieldId FK
uuid createdByUserId FK
enum type
date workDate
decimal cost
json metadata
}
ChemWarehouse {
uuid id PK
uuid farmId FK
string name
enum category
string registrationNo
decimal qty
decimal price
}
Subscription {
uuid id PK
uuid farmId FK
enum plan
timestamp trialEndsAt
timestamp currentPeriodEnd
string stripeId
}
Każde zapytanie SQL automatycznie filtruje po farm_id aktywnego użytkownika. Backend nigdy nie polega tylko na WHERE w aplikacji — RLS jest siatką bezpieczeństwa.
-- Policy na tabeli fields: user widzi tylko pola gospodarstw w których ma membership
CREATE POLICY field_isolation ON fields
FOR ALL
USING (
farm_id IN (
SELECT farm_id FROM memberships
WHERE user_id = current_setting('app.current_user_id')::uuid
)
);
ALTER TABLE fields ENABLE ROW LEVEL SECURITY;
Backend ustawia SET app.current_user_id = '...' na początku każdego requestu (z JWT). Reszta dzieje się sama.
| localStorage (dziś) | Tabela (jutro) | Klucz tenant | Uwagi |
|---|---|---|---|
farmFields | fields | farm_id | Migracja 1:1, dodać gpsPolygon JSON |
chemWarehouse | chem_warehouse | farm_id | Część danych (12 ŚOR) wydzielić do sor_catalog jako global read-only |
seedWarehouse | seed_warehouse | farm_id | 1:1 |
grainWarehouse | grain_warehouse | farm_id | 1:1 |
agro360_settings | farms (kolumny) | — | Splittuje się: nazwa/region → farms, profil usera → users |
fieldWorkHistory (in-mem!) | work_history | farm_id | BUG — dziś nie persystuje. Krytyczna migracja |
invoicesRegister (in-mem!) | invoices | farm_id | BUG — j.w. |
costsRegister (in-mem!) | costs | farm_id | BUG — j.w. (lub compute on-fly z work_history) |
sorDatabase (hardcoded) | sor_catalog | — | Global, shared między wszystkimi gospodarstwami, zarządzane przez super-admina (sekcja 10) |
| (brak) | users | — | Nowe — konta SaaS |
| (brak) | memberships | — | Nowe — user ↔ farm |
| (brak) | subscriptions | farm_id | Nowe — billing |
| (brak) | audit_log | farm_id | Nowe — kto co zmienił |
Część danych nie należy do żadnego konkretnego gospodarstwa, tylko do całego SaaS:
sorDatabase, 12 wpisów hardcoded → docelowo 100+ aktualizowane co kwartał z rejestru gov.pl)Każdy z tych katalogów może mieć overrides per farm: gospodarstwo może dodać własny ŚOR (regionalny, nieobjęty katalogiem) lub własnego dostawcę nawozów.
| Opcja | Czas do MVP | Koszt początkowy | Skalowalność | Kiedy wybrać |
|---|---|---|---|---|
| Supabase (PG + Auth + Realtime) | 4-8 tygodni | Free do 500MB, potem ~25$/mies | Dobra do 10k userów, dalej trudniej | MVP, mała ekipa, polski hosting przez self-host później |
| Firebase (Firestore + Auth) | 3-6 tygodni | Free spora pojemność | Świetna, ale NoSQL — komplikuje raporty SQL-style | Jeśli zależy nam na czasie i tolerujemy lock-in Google |
| Własny Node.js + PG | 10-16 tygodni | VPS ~50-100 zł/mies | Pełna kontrola, brak limitów | Gdy mamy doświadczonego backenders i czas |
| Pocketbase (Go, single binary) | 4-6 tygodni | Self-host od $5/mies | Średnia, ale prosta | Jeśli zależy na pełnym self-host i prostocie |
Rekomendacja wstępna: Supabase na MVP. PostgreSQL pod spodem (znamy go, RLS gotowy), auth wbudowany, realtime, storage na pliki (skany faktur). Możliwość self-host później bez przepisywania. Hosting w EU (Frankfurt) zgodny z RODO.
Subscription.farmId FK — subskrypcja per gospodarstwo. Ale rejestracja (s.3) tworzy konto usera z 30-dniowym trialem. Jeśli user założy 2. gospodarstwo w trakcie trialu — czy 2. też ma trial? A jak ma już płatne 1. gospodarstwo i zakłada 2. — automatycznie nowa subskrypcja 49 zł/mies czy trial dla 2.?
Po analizie 3 opcji w pierwotnym audicie — wybieramy opcję A z poprawką:
Subscription.farm_id)Konsekwencje dla UI:
| Scenariusz | Co się dzieje |
|---|---|
| User ma 1 gospodarstwo Pro | 1 subskrypcja, 1 faktura/mies, jedna karta |
| User zakłada 2-gie gospodarstwo (Pro) | 2 subskrypcje na tym samym Stripe Customer, 2 osobne faktury, ta sama karta domyślnie (można zmienić per gospodarstwo) |
| User-właściciel + zaproszony jako współwł. do innego | 1 jego subskrypcja własna + 0 dodatkowych (płaci tamten właściciel) |
| Marta (Agronom Pro) + zaproszona do 5 gospodarstw | 1 jej subskrypcja Agronom Pro (99 zł) + 0 zł od 5 gospodarstw (każde płaci swoje Pro osobno) |
| Anulowanie subskrypcji jednego gospodarstwa | Tylko to gospodarstwo przechodzi w read-only / suspend. Pozostałe nadal aktywne. |
| Transfer własności gospodarstwa | Subscription metadata.owner_id zmienia się; nowy właściciel ma 7 dni na wpięcie własnej karty; jeśli nie — auto-anulowanie po obecnym okresie |
Co z userami którzy używają lokalnej apki dziś i ich danymi w localStorage?
exportAppData() (l. 6449) — generuje JSON z całościąfarm_id nowo utworzonego gospodarstwaexportAppData()/importAppData() (l. 6449/6472) generują/przyjmują JSON z całością danych gospodarstwa. To idealny format wymiany przy migracji do SaaS. Trzeba tylko: (a) dodać do exportu wersję schematu, (b) w importerze SaaS mapować pola → kolumny w bazie.
fieldWorkHistory, invoicesRegister, costsRegister) nie są dziś persystowane. Przed jakąkolwiek migracją trzeba je najpierw zapisać do localStorage (tymczasowy fix), potem zmigrować do backendu. Inaczej user „testuje SaaS" i traci całą historię.
deleted_at. „Kosz" dostępny w ustawieniach 30 dni. Po 30 dniach hard delete batch jobem. Ratuje przed pomyłkami („skasowałem fakturę, jak ją odzyskać?") i ułatwia debug.
sor_catalog, fertilizer_catalog są publiczne (każdy może je czytać). Wystawić jako public REST API (np. api.agro360.pl/v1/sor) — przyda się: (a) aplikacjom mobilnym, (b) potencjalnym partnerom (sklepy rolnicze), (c) SEO (każdy preparat = strona indexowana w Google).
Cel: jak najmniej tarcia przy pierwszej płatności i jak najmniej wsparcia. Dla persony Tomek (42, smartfon) — musi działać z BLIKa, w 3 klikach, na telefonie.
| Dostawca | Plus | Minus | Koszt |
|---|---|---|---|
| Stripe | Globalny standard, świetna dokumentacja, subskrypcje out-of-the-box, hosted checkout, faktury VAT, Customer Portal gratis | Brak BLIK-a (krytyczne dla PL), brak Przelewy24 — głównie karta | 1.4% + 2 zł / transakcja w EU |
| Przelewy24 (Przelewy24) | BLIK, Apple/Google Pay, przelewy bankowe — pokrywa 95% PL | Subskrypcje słabsze (recurring trzeba budować ręcznie), gorsze API | 1.5-2.5% + 0.39 zł |
| Tpay | BLIK, przelewy, dobre PL wsparcie, subskrypcje OK | Mniejsza dokumentacja niż Stripe, mniejszy ekosystem | 1.7% + 0 zł |
| Stripe + Przelewy24 (hybryda) | Stripe do logiki subskrypcji (Customer, Subscription, Invoice), P24 jako payment method w Stripe (jest natywnie) | Dwa SLA, dwa rozliczenia | ~1.5-2% łącznie |
Rekomendacja: Stripe + Przelewy24 jako payment method w Stripe. Stripe ma natywne wsparcie P24 i BLIK (przez P24) od 2024. Dostajemy najlepszy z dwóch światów: solidną logikę subskrypcji + PL metody płatności. Drugi wybór: Tpay solo, jeśli okaże się że Stripe-P24 ma jakieś niespodzianki w PL.
| Plan | Stripe Price ID | Cena / interval | Trial | Limity (enforced w app) |
|---|---|---|---|---|
| Trial | — | 0 zł, 30 dni | — | 1 farm, 3 users, wszystko inne bez limitu |
| Pro miesięczny | price_pro_monthly | 49 zł / mies | 30 dni | 1 farm, 3 users |
| Pro roczny | price_pro_yearly | 490 zł / rok (= 2 mies gratis) | 30 dni | 1 farm, 3 users |
| Enterprise | price_enterprise_* | Negocjowane | — | N farms, N users, API, dedicated |
flowchart TD
Trial[Trial dzień 1-23] -->|7 dni przed końcem| Email1[Email + banner:
'Zostało 7 dni']
Email1 --> Trial24[Trial dzień 24-29]
Trial24 -->|2 dni przed końcem| Email2[Email + banner:
'Zostało 2 dni - wybierz plan']
Email2 --> Day30{Dzień 30:
karta wpięta?}
Day30 -->|Tak, auto-charge| Charge[Stripe pobiera 49 zł]
Day30 -->|Nie| ReadOnly[Read-only mode:
można czytać, nie pisać]
Charge -->|OK| Active[Subscription active]
Charge -->|Fail| RetryFlow[Retry: 1d, 3d, 5d, 7d
email + banner]
RetryFlow -->|Sukces| Active
RetryFlow -->|7 dni fail| ReadOnly
ReadOnly --> Choice{User decyduje}
Choice -->|Wybiera plan| Checkout[Checkout Stripe]
Choice -->|Eksport + wyjście| Export[exportAppData JSON]
Checkout --> Active
Choice -->|Nic przez 60 dni| Suspend[Konto zawieszone
dane zachowane 12 mies]
style Active fill:#4a7c2c,stroke:#2d5016,color:#fff
style ReadOnly fill:#fff3d6,stroke:#c08a2a
style Suspend fill:#ffe9e9,stroke:#e88a8a
Anulowanie zawsze proste (nie ukrywać, nie utrudniać — RODO + dobre obyczaje). Flow:
flowchart LR
Click[Klik 'Anuluj'] --> Reason[Krótki form:
dlaczego? opcjonalne]
Reason --> Confirm{Potwierdź}
Confirm -->|Tak| Cancel[Subskrypcja kończy się
na koniec okresu rozliczeniowego]
Cancel --> Until[Do końca okresu:
pełna funkcjonalność]
Until --> Expire[Po końcu okresu:
read-only mode]
Expire --> Retain[Dane zachowane 12 mies
możliwy reactivate]
style Cancel fill:#fff3d6,stroke:#c08a2a
style Retain fill:#e8f0e0,stroke:#4a7c2c
Stripe generuje faktury VAT automatycznie, ale dla polskich firm trzeba dodać:
FV/2026/05/0001) — Stripe Invoice Numbering: prefix per regionSubscription.farmId, więc gospodarstwo ma subskrypcję. Pytania bez odpowiedzi:
| Event | Akcja w naszym systemie |
|---|---|
customer.subscription.created | Set subscription.status = active, set currentPeriodEnd |
customer.subscription.updated | Sync planu, current period end, status |
customer.subscription.deleted | Set status = canceled, schedule read-only po current period |
invoice.payment_succeeded | Send confirmation email, update subscription period |
invoice.payment_failed | Send dunning email (1d/3d/5d/7d retry), banner w aplikacji |
customer.subscription.trial_will_end | Send email 3 dni przed końcem trialu (Stripe robi to 3 dni przed automatically) |
paymentsPanel (l. 2501) i showPaymentsPanel() to wydatki rolnika (płatności za nawozy, paliwo, usługi). W SaaS „Płatności" / „Billing" to opłaty za SaaS. Kolizja nazewnictwa. Zmienić: paymentsPanel → farmExpensesPanel, a nowy moduł billingu nazwać subscriptionPanel (Subskrypcja).
Backend egzekwuje limity per request. Frontend pokazuje przyjazne komunikaty.
| Limit | Trial / Pro | Co się dzieje przy przekroczeniu |
|---|---|---|
| Users w gospodarstwie | 3 | Backend rzuca 402. UI: „Limit 3 użytkowników w planie Pro. Upgrade do Enterprise lub usuń kogoś" |
| Liczba gospodarstw | 1 | Backend rzuca 402. UI: „Drugie gospodarstwo wymaga Enterprise. [Skontaktuj się]" |
| Storage (skany faktur) | 5 GB | Upload zwraca 413. UI: „Wykorzystano 5 GB z 5 GB. Usuń stare lub upgrade" |
| Eksporty PDF / mies | Bez limitu w Pro | — |
| API calls | — | API tylko w Enterprise (rate limit 1000/godz) |
Kluczowe rozróżnienie: ustawienia konta (usera) vs ustawienia gospodarstwa (farmy). Dziś w apce wszystko jest pomieszane w settingsPanel + agro360_settings. W SaaS rozdzielamy — bo jeden user może być w wielu gospodarstwach, a jego profil jest jeden.
| Urządzenie | Lokalizacja (IP) | Ostatnia aktywność | Akcja |
|---|---|---|---|
| iPhone Safari (ta sesja) | Poznań | teraz | — |
| Chrome / Windows | Poznań | 2h temu | [Wyloguj] |
| Android Chrome | Konin | 5 dni temu | [Wyloguj] |
[Wyloguj wszystkie poza tą]
| Powiadomienie | SMS | Push (PWA) | |
|---|---|---|---|
| Niski stan magazynowy | ✅ | — | ✅ |
| Zaproszenie do gospodarstwa | ✅ zawsze | — | — |
| Nowy zabieg dodany przez kogoś | — | — | ✅ |
| Koniec trialu (7d / 2d / 0d) | ✅ zawsze | — | ✅ |
| Płatność powiodła się / nie | ✅ zawsze | — | — |
| Cotygodniowy raport („w tym tygodniu w gospodarstwie") | ✅ | — | — |
| Alerty pogodowe (mróz, deszcz przed opryskiem) | — | ✅ | ✅ |
Każda kategoria z togglem on/off per kanał. SMS tylko gdy zweryfikowany numer + odpowiedni plan (Enterprise lub plan z dodatkiem SMS — alternatywnie limit np. 10 SMS/mies w Pro).
RODO daje userowi prawo: (a) wiedzieć jakie dane mamy, (b) otrzymać je w formacie maszynowym, (c) żądać usunięcia.
exportAppData() z apki — l. 6449)Z poprawką: żaden user nie może zostać uwięziony w SaaS. Jeśli jest jedynym właścicielem, dajemy mu wybór per gospodarstwo (przekaż / opuść / usuń).
flowchart TD
Click[Klik 'Usuń konto'] --> Warn[Ekran:
co się stanie?
lista skutków + Twoje gospodarstwa]
Warn --> HasMember{Mam jakieś
memberships?}
HasMember -->|Nie| Confirm
HasMember -->|Tak| Resolve[Ekran 'Najpierw załatwmy gospodarstwa':
lista wszystkich memberships
z opcją per gospodarstwo]
Resolve --> Per{Per gospodarstwo}
Per -->|Jestem jedynym właścicielem| Choice1[Wybierz:
• Przekaż współwłaścicielowi
• Usuń gospodarstwo wraz z kontem
• Zaproś nowego właściciela i odczekaj]
Per -->|Jestem współwłaścicielem| Choice2[Wybierz:
• Tylko opuść
• Wymuś usunięcie gospodarstwa
jeśli jestem ostatnim aktywnym]
Per -->|Jestem pracownikiem/agronomem/księgową| Choice3[Auto: opuszczam membership]
Choice1 --> AllDone
Choice2 --> AllDone
Choice3 --> AllDone
AllDone{Wszystkie
załatwione?}
AllDone -->|Nie| Resolve
AllDone -->|Tak| Confirm[Wpisz email + 'USUWAM'
żeby potwierdzić]
Confirm --> Email[Email z linkiem
'Potwierdź usunięcie']
Email --> Click2[Klik w emailu w 24h]
Click2 --> Soft[Soft-delete:
konto deactivated]
Soft --> Grace[30 dni grace period
możliwy reactivate]
Grace --> Hard[Po 30 dniach:
hard delete + anonimizacja]
Hard --> AuditLog[Wpis w gov audit:
'user X deleted on Y']
style Soft fill:#fff3d6,stroke:#c08a2a
style Hard fill:#ffe9e9,stroke:#e88a8a
style Resolve fill:#e8f1fb,stroke:#7aa8e0
Lista wszystkich memberships usera z dedykowaną akcją per pozycja:
| Gospodarstwo | Moja rola | Status | Akcja |
|---|---|---|---|
| Gospodarstwo Kowalskich | 👑 Jedyny właściciel | ⚠️ wymaga decyzji | [Przekaż] [Usuń też gospodarstwo] |
| Pole Nowaka | 👩🌾 Agronom | OK | [Opuść] |
| Spółka Sąsiedzka | 👷 Pracownik | OK | [Opuść] |
Przycisk „Kontynuuj usuwanie konta" jest disabled dopóki wszystkie pozycje nie są załatwione.
Opcja „Usuń też gospodarstwo" wywołuje flow z s.9.9 (usunięcie gospodarstwa) — z osobnym double opt-out + emailem do wszystkich userów gospodarstwa o usunięciu przez właściciela. Cały proces zaprojektowany żeby user mógł zawsze wyjść — zgodnie z RODO Art. 17.
Co się dzieje przy hard delete: user usunięty, jego memberships usunięte, ale dane gospodarstwa (zabiegi, faktury które wpisał) zostają — z atrybucją „usunięty użytkownik" (audit log). To zgodne z RODO (legitimate interest właściciela gospodarstwa do dokumentacji).
exportAppData() z apki, ale rozszerzone: ZIP zawiera JSON + PDF raporty + skany fakturTylko właściciel. Identyczny flow jak usunięcie konta (double opt-out, 30 dni grace, hard delete + anonimizacja). Dodatkowo: wszyscy users dostają email „gospodarstwo zostało usunięte przez właściciela".
settingsPanel rozdzielić
Obecnie settingsPanel (l. 2744) miesza: nazwę gospodarstwa, imię właściciela, region. W SaaS te pola należą do różnych encji (user vs farm). Refaktor: (a) oddzielić formularze, (b) imię/email/telefon → users, (c) nazwa/region/NIP → farms. Istniejące saveSettingsFarm() (l. 6412) trzeba rozbić na updateMyProfile() + updateFarmProfile().
exportAppData() i importAppData() (l. 6449/6472) realizują połowę wymogów RODO Art. 20 (portability). Wystarczy: (a) dodać do exportu sygnaturę (kto wyeksportował, kiedy), (b) wystawić jako endpoint backendu GET /api/farms/:id/export, (c) dorzucić skany faktur i PDFy.
Wewnętrzne narzędzie dla zespołu AGRO 360. Nigdy nie publiczne, dostępne pod osobnym subdomenem (admin.agro360.pl) z IP whitelistą + obowiązkowym 2FA. Cel: prowadzić biznes (metryki) + pomagać klientom (support).
| Rola | Kto | Może |
|---|---|---|
| Super-admin | Założyciele, CTO | Wszystko: zarządzać innymi adminami, robić refund, usuwać konta klientów, mieć dostęp do danych klientów |
| Support | Customer support, helpdesk | Czytać dane klientów (z logiem!), impersonate (z zgodą), zmienić plan, robić refund < 200 zł |
| Read-only | Inwestor, księgowa, marketing | Tylko metryki agregowane (MRR, churn), bez dostępu do indywidualnych klientów |
| Content | Osoba aktualizująca katalog ŚOR | Edycja sor_catalog, fertilizer_catalog, crop_catalog (sekcja 7) — nic więcej |
| Metryka | Wartość (przykład) | Δ m/m |
|---|---|---|
| MRR | 14 740 zł | +12% ✅ |
| Aktywne gospodarstwa (płatne) | 301 | +18 ✅ |
| Trial w toku | 89 | +24 ✅ |
| Trial → paid conversion | 34% | +2 pp ✅ |
| Churn (anulowane / aktywne) | 3.2% / mies | −0.4 pp ✅ |
| ARPU | 49 zł | 0 |
| Dunning failures (open) | 4 | −2 ✅ |
Osobny widget na dashboardzie admina — Marta i inni doradcy są drugim filarem przychodów:
| Metryka | Wartość (przykład) | Δ m/m |
|---|---|---|
| Aktywni agronomi (Agronom Pro) | 14 | +3 ✅ |
| MRR z planów Agronom Pro | 1 386 zł (= 14 × 99) | +297 zł ✅ |
| Średnia liczba klientów per agronom | 8 (mediana 6, max 24) | +0.5 ✅ |
| Rolnicy sprowadzeni przez agronomów | 87 (28% wszystkich Pro) | +12 ✅ |
| Conversion: zaproszenie agronoma → akceptacja klienta | 62% | +4 pp ✅ |
| Churn klientów z agronomem vs bez | 2.1% vs 3.8% | — |
| NPS agronomów | 52 | +6 ✅ |
| Wykorzystane kupony afiliacyjne | 34 | +8 ✅ |
Hipoteza do udowodnienia metrykami: klienci sprowadzeni przez agronoma mają niższy churn (potwierdzone wstępnie: 2.1% vs 3.8%). Jeśli się utrzyma, to mocny argument za inwestycją w kanał (więcej kuponów, większy retainer dla agronoma).
Klient dzwoni z problemem, support musi zobaczyć dokładnie to co widzi user. Implementacja:
flowchart TD
Support[Admin Support] --> Search[Szukaj usera / farmy]
Search --> Find[Wybór klienta]
Find --> Request[Klik 'Impersonate']
Request --> Consent{Wymagana zgoda?}
Consent -->|Tak - zawsze poza CRITICAL| EmailUser[Email do usera:
'Support chce mi pomóc - zgadzasz się?']
EmailUser --> UserConsent{User zgadza się?}
UserConsent -->|Tak, klik w emailu| Token[Token tymczasowy
ważny 30 min]
UserConsent -->|Nie / brak akcji 24h| Deny[Brak dostępu]
Token --> SessionLog[Session jako user
z banerem czerwonym
'IMPERSONATING - widzi user']
SessionLog --> AuditAll[Każda akcja w audit log
z atrybucją 'support@: admin X']
style Token fill:#fff3d6,stroke:#c08a2a
style SessionLog fill:#ffe9e9,stroke:#e88a8a
Anti-patterns do uniknięcia:
| Operacja | Kto może | Audit / notification |
|---|---|---|
| Reset hasła klienta | Support | Email do klienta + log |
| Wymuszone wylogowanie | Support | Log |
| Refund < 200 zł | Support | Log + faktura korygująca |
| Refund >= 200 zł | Super-admin | Log + faktura korygująca + Slack alert |
| Zmiana planu klienta | Support | Email do klienta + log |
| Zawieszenie konta | Super-admin | Email + log + Slack |
| Usunięcie konta klienta | Super-admin (2 person approval) | Email + log + Slack + backup |
| Eksport wszystkich danych klienta (RODO) | Support | Email + log |
| Edycja danych klienta (np. korekta NIPu) | Support | Diff w audit log |
status.agro360.pl z incydentami (transparentność dla klientów)app-staging.agro360.pl. Wszyscy developerzy + 2-3 power-userów beta tu testują nowe ficzery.is_internal=true, nie liczone do metryk MRR/churn w admin panelu (s.10.3), używane do smoke testów po deployu.Trzy poziomy izolacji od produkcji — żeby zespół mógł rozwijać bez psucia danych klientów:
| Środowisko | Adres | Cel | Konfiguracja |
|---|---|---|---|
| Local dev | localhost:5173 |
Każdy developer ma własną kopię — szybkie iteracje, eksperymenty bez wpływu na innych | Supabase local (Docker) + Stripe Test Mode + seed data z generatora (faker) |
| Staging | app-staging.agro360.pl |
Pre-production: testy QA, demo dla klientów, walidacja przed deployem na prod | Osobny projekt Supabase (refresh raz na tydzień z anonimizowanej kopii prod) + Stripe Test Mode + email do @agro360.pl tylko (sandbox provider) |
| Production | app.agro360.pl |
Klienci — prawdziwe dane, prawdziwe pieniądze | Supabase EU + Stripe Live Mode + Resend + monitoring 24/7 |
Czasem zespół musi przetestować coś na realnej produkcji (np. webhook Stripe Live, nowy flow OAuth). Mechanizm:
is_internal=true w baziePo każdym deployu na prod, GitHub Actions uruchamia Playwright E2E na test accounts:
Jeśli któryś fail → automatyczny rollback do poprzedniej wersji + Slack alert.
Mechanizm rollout per cohort (alpha, beta, % users, plan). Implementacja: tabela feature_flags(name, value_json, audience_filter) + endpoint GET /api/me/flags. Przykłady flagów:
new_dashboard_ui — testujemy nowy dashboard na 10% userówarimr_integration — tylko Enterpriseweather_api_v2 — przejście z mock na real, gradual rolloutmaintenance_mode — ekran „przerwa techniczna" dla wszystkichagro360-mvp2.html. Może być stack tym samym co główna apka, ale deployment osobny (osobne repo lub osobny subdomain w monorepo). Klient SaaS nie powinien móc nawet wykryć że panel istnieje.
Każdy edge case nieobsłużony to wycieczka klienta do konkurencji. Ta sekcja katalogizuje wszystkie scenariusze „co jeśli" zidentyfikowane w sekcjach 1-10 plus te specyficzne dla naszej branży (offline w polu, kontrola ARiMR „za 30 minut", BLIK się skończył w żniwa).
| Scenariusz | Zachowanie |
|---|---|
| Zapomniane hasło | Email z magic link (ważny 1h) → ustaw nowe hasło → auto-login. Rate limit: 3 prośby / godzinę / email. |
| Wpisałem zły email przy rejestracji | Konto nie zostanie zweryfikowane → wygasa po 7 dniach → email zwolniony. User może zarejestrować się ponownie z poprawnym. |
| Email weryfikacyjny nie dochodzi | [Wyślij ponownie] z rate limit 1x / 60s. Po 3 próbach sugestia: „Sprawdź spam / dodaj noreply@agro360.pl do kontaktów / skontaktuj się z supportem" |
| 2FA — straciłem telefon | Wpisanie jednego z 10 kodów zapasowych (wygenerowanych przy włączeniu 2FA). Brak kodów = ticket do supportu z weryfikacją tożsamości (dowód, selfie). |
| Konto przejęte (phishing) | User dzwoni do supportu → support weryfikuje (telefon zapisany w profilu) → reset hasła + 2FA + audit log (sprawdzić co zrobiono z konta) → opcjonalnie reverse zmiany. |
| Próba logowania z innego kraju | Email „nowe logowanie z [kraj/miasto]" + opcjonalny block (Enterprise feature). Standard: pozwól ale alertuj. |
| Brute force loginu | Po 5 nieudanych: CAPTCHA. Po 10: lockout konta na 15 min + email do właściciela. |
| Wygaśnięcie sesji w trakcie pisania zabiegu | Auto-save draftu lokalnie → po re-login wraca do tego samego formularza z danymi. |
Offline-first to wartość rdzeniowa — Tomek w polu nie ma zasięgu, ale chce wpisać zabieg. Strategia:
sync_queuePersona Tomek wpisuje zabieg offline, pracownik Marek równolegle online wpisuje inny zabieg na tym samym polu. Co dalej?
| Konflikt | Resolver |
|---|---|
| Dwa zabiegi na tym samym polu w tym samym dniu (różne typy) | Brak konfliktu — oba zapisują się (mogą być różne) |
| Dwa identyczne zabiegi (ten sam typ, te same preparaty, ten sam dzień) | Backend wykrywa duplikat → wraca alert „prawdopodobnie dublujecie się — pokaż oba" → user decyduje |
| Edycja tego samego pola (np. zmiana powierzchni) | Last-write-wins + notyfikacja drugiej osoby „Anna zmieniła powierzchnię Pole #3 z 12.5 na 13.0 ha" |
| Magazyn — dwóch ludzi odejmuje z tego samego produktu | Atomowy UPDATE z WHERE qty >= X w bazie. Jeśli drugi przegrał (qty < X po pierwszym) → alert „brak X kg w magazynie, ktoś już odjął" |
| Usunięcie pola na którym ktoś właśnie wpisuje zabieg | Backend reject, frontend: „To pole zostało usunięte przez [Anna]. Wybierz inne lub przywróć pole" |
| Scenariusz | Zachowanie |
|---|---|
| Karta wygasła | Email 30 dni przed wygaśnięciem (Stripe Card Updater) + przypomnienie w aplikacji |
| BLIK nieudany (limit, brak akceptacji w 60s) | Retry zgodnie z dunning sequence (1d/3d/5d/7d) + email z linkiem do ponownej próby |
| Klient zapłacił przelewem ręcznie (poza Stripe) | Admin może oznaczyć fakturę jako „paid manually" → przedłuża subskrypcję o okres |
| Refund (klient żąda zwrotu w trakcie miesiąca) | Pro-rated refund (Stripe oblicza automatycznie). Jeśli > 200 zł — wymaga super-admina (sekcja 10) |
| Klient anulował, ale wrócił po 60 dniach | Dane jeszcze są (12 mies retention). Reactivate konto, nowy okres rozliczeniowy. Trial nie odnawia się. |
| Klient anulował, wrócił po 18 miesiącach | Dane usunięte. Pełna nowa rejestracja, nowy trial 30 dni (rozsądnie, nie blokujemy returnera). |
| Klient stwierdza „nie zamawiałem, blokuje płatność" | Stripe Dispute → my mamy 7 dni na ripostę z dowodami (logi loginu, audit log, faktury PDF) → Stripe decyduje. Loss = chargeback fee 15-25 €. |
| Scenariusz | Zachowanie |
|---|---|
| User wpisał zabieg z dawką znacznie wyższą niż dozwolona | Soft warn: „Dawka 5 l/ha to 3x więcej niż etykietowa max 1.6 l/ha. Czy na pewno?" [Tak / Popraw] |
| Magazyn w minusie (wpisał więcej niż jest) | Soft warn: „Po tym zabiegu w magazynie będzie −12 kg. Sprawdź stan." [Zapisz mimo to / Popraw] |
| Praca polowa wpisana w przyszłości (data > dziś) | OK — to plan, oznaczany jako 'planowany'. Konwertowany na 'wykonany' po dacie. |
| Praca polowa wpisana w dalekiej przeszłości (> 3 lata wstecz) | Soft warn: „Wpisujesz datę 2022. Na pewno?" — czasem ma sens (importują historię) |
| Mieszanina ŚOR z niekompatybilnymi preparatami | Backend sprawdza w katalogu (przyszła funkcja) → alert „Artea + Roundup — nie zalecane łączenie wg etykiety" [Zapisz mimo to / Usuń jeden] |
| Eksport JSON dużego gospodarstwa (500 zabiegów, 50 MB) | Generowanie async w background → email z linkiem gdy gotowe (ważny 24h) |
| Import JSON z błędnym schematem | Walidator pokazuje line-by-line co nie pasuje → user koryguje i wgrywa ponownie |
| Serwis | Co jeśli padnie |
|---|---|
| Stripe down | Płatności kolejkowane → retry. Klienci nie powinni stracić dostępu nawet przy 24h przerwie Stripe. |
| Email provider (Resend/Postmark) down | Fallback na drugi provider (Sendgrid backup). Kolejka emailowa nie ginie. |
| Pogoda API (IMGW / OpenWeather) down | UI pokazuje „Dane pogodowe niedostępne — sprawdzimy później". Nie blokuje reszty. |
| MATIF / notowania API down | Pokaż ostatnio znane + znacznik „dane sprzed Xh". Nie blokuje reszty. |
| Supabase / DB down (rzadkie ale możliwe) | Aplikacja PWA z lokalnymi danymi nadal działa READ-ONLY z IndexedDB. Mutacje w kolejce do sync. |
| OCR faktury (jeśli dodamy) | Fallback: user wpisuje ręcznie + retry OCR w tle. |
| Scenariusz | Zachowanie |
|---|---|
| Pole dzielone na sezony — pszenica ozima + poplon | Model: pole ma wiele upraw w czasie (history). Aktualna uprawa = ostatnia, historia widoczna w szczegółach. |
| Pole wynajęte / oddane innej osobie | Soft delete + flaga „dzierżawione komuś" + opcjonalne udostępnienie read-only dzierżawcy (rozszerzenie ról z s.6). |
| Kontrola ARiMR „za 30 minut, daj papier" | Killer feature: przycisk „Pakiet kontrolerski" w 1 kliku (sekcja 9 — Pomysł). PDF off-line dostępny zawsze, nawet bez internetu w polu. |
| Zmiana sezonu (1 września przejście na nowy rok agronomiczny) | Automatyczny przegląd: „Sezon 2025/2026 się skończył. Zarchiwizuj uprawy? Przygotuj plany na 2026/2027?" — wizard na start sezonu. |
| Wycofanie środka ŚOR z rejestru (zmiana w prawie) | Katalog sor_catalog aktualizowany przez super-admina. Gdy preparat wycofany: oznaczenie w magazynie + alert „Bumper 250 EC wycofany z rejestru od 30.06.2026 — zużyj do tej daty". |
| Klęska żywiołowa, susza, grad — strata uprawy | Możliwość oznaczenia uprawy jako „strata całkowita" + dokumentacja (zdjęcia) — przyda się do wniosku o pomoc. |
| Wymiana sprzętu (sprzedaż ciągnika) w trakcie sezonu | Maszyny — moduł na przyszłość. Edge: historia zabiegów z tym ciągnikiem zostaje (historia jest immutable). |
agro360-mvp2.html ma głównie happy path. try/catch sporadycznie, brak globalnego error handlera, brak loading states (bo localStorage jest synchroniczny). Migracja do SaaS = zero asynchroniczności staje się 100%. Każdy JSON.parse(localStorage.X) → await api.X(). To wymaga wprowadzenia: loading states, error states, retry logic, optimistic updates. Wcale niemały refaktor — patrz s.5 i s.12.
Sekcja sumuje wszystkie wcześniejsze ustalenia w konkretny plan działania. Założenia: jedna osoba (Adam) + Claude jako pair, iteracyjnie, fragmentarycznie. Cel: pierwszy płacący klient w 4-6 miesięcy od decyzji „budujemy".
| Warstwa | Decyzja v2.0 | Dlaczego |
|---|---|---|
| Frontend framework | React 18 + TypeScript + Vite | Greenfield build (prototyp Przemka jest specyfikacją domenową, nie code base). React = największy ekosystem, najwięcej devów, długoterminowy bet. |
| Routing | React Router v6+ | Standard, deep linking, code splitting |
| Stan | TanStack Query (server state) + Zustand (UI state) | Query załatwia 90% potrzeb (cache, retry, optimistic updates), Zustand do reszty (modale, filters) |
| Design system | Tailwind CSS + shadcn/ui + Lucide icons | shadcn = komponenty kopiujesz do repo (pełna kontrola, brak vendor lock-in), Tailwind = szybkość, Lucide = spójne ikony |
| Design tokens | CSS custom properties (z palety prototypu: #4a7c2c, #2d5016) | Zachowanie wizualnej tożsamości AGRO 360 z prototypu |
| Font | Inter (latin-ext dla PL znaków) | Bezpłatny, czytelny, wszystkie wagi, self-host |
| Forms | React Hook Form + Zod | Performance, walidacja type-safe, mała kanapka |
| Build & PWA | Vite + vite-plugin-pwa (manifest, basic SW), pełne offline w fazie 2 | Vite szybszy od CRA/Webpack, plugin daje fundamenty PWA |
| Hosting frontu | Vercel (preferowane) lub Cloudflare Pages | Vercel ma lepsze preview deploys per PR, Cloudflare tańsze przy skali. Start: Vercel. |
| Backend + DB + Auth | Supabase (PostgreSQL + Auth + Storage + Realtime) | Najszybciej do MVP, RLS gotowy, hosting EU (Frankfurt). Self-host na backupie. |
| API layer | Supabase client SDK + osobny BFF (Hono na Cloudflare Workers) dla logiki niestandardowej (Stripe webhooks, generacja PDF, OCR) | SDK do CRUD, BFF do operacji wymagających sekretów / orchestration |
| PDF generation | Klient-side: react-pdf (MVP) → server-side: Puppeteer w Cloudflare Workers (faza 2) | MVP: prosta generacja po stronie klienta. Faza 2: pixel-perfect z asetami |
| Płatności | Stripe + Przelewy24 jako payment method w Stripe | Najlepsze subskrypcje (Stripe) + BLIK/przelewy PL (P24 w Stripe) |
| Email transakcyjny | Resend (primary), Postmark (fallback) | Świetne deliverability, polskie hosting EU, dev experience |
| Email marketing | Resend Audiences (start) → ConvertKit (gdy > 500 subskrybentów) | Resend Audiences darmowy, ConvertKit ma lepsze sekwencje retencji |
| SMS | SerwerSMS.pl / Twilio | SerwerSMS taniej w PL, Twilio bardziej niezawodny — start na SerwerSMS |
| Error tracking | Sentry (free tier) | Standard, free do 5k events/mies |
| Analytics | Plausible (RODO-friendly) + PostHog (events / funnele) | Plausible do podstawowych metryk, PostHog do retention cohortów |
| Helpdesk | Crisp (start) → wbudowane (gdy > 100 ticketów/mies) | Free na start, wystarczające na MVP |
| Status page | Better Stack | Transparentność dla klientów, alerty SMS dla on-call |
| CI/CD | GitHub Actions + Vercel preview deploys | Każdy PR → preview URL, CI uruchamia testy + lint + a11y |
| Testing | Vitest (unit) + Playwright (E2E) | Standard React/TS, Playwright cross-browser |
| Mapy (pola) | OpenStreetMap + Leaflet (MVP) → ARiMR API (Pro/Enterprise) | Free na start, integracja gov-owa później |
| Pogoda | OpenWeather (start) → IMGW (gdy stabilne) | OWM ma free 1000 calls/dzień, IMGW niedeterministyczne API |
| Repo | Monorepo (Turborepo) — apps/web, apps/admin, apps/landing, packages/ui, packages/types | Współdzielenie komponentów, jeden CI, jeden lockfile |
Łączny stack to ~12-15 zewnętrznych zależności. Większość ma free tier wystarczający na pierwsze 6 miesięcy. Szacunkowy koszt miesięczny po pierwszych 50 klientach: ~120-180 zł (Supabase Pro $25, Stripe transakcje, Resend pro $20, Sentry hobby, reszta free).
grainWarehouse ma fieldId 1-3, ale farmFields pusty)paymentsPanel → farmExpensesPanelagro360.pl, app.agro360.pl, admin.agro360.pl, status.agro360.plkontakt@, noreply@, support@ — DNS SPF/DKIM/DMARCCel: Pierwsze 10 płatnych gospodarstw + 2 aktywnych agronomów. Wszystko inne odpuszczamy. Estymata wyższa niż w v1.0 (była 8-10 tyg) — bo greenfield build = wszystko od zera, nie refaktor.
gantt
title Faza 1 — MVP SaaS greenfield (~14-16 tygodni)
dateFormat YYYY-MM-DD
section Setup
Monorepo + design system (Tailwind+shadcn) :s1, 2026-06-01, 5d
Supabase setup + schema + RLS :s2, 2026-06-01, 7d
Vercel hosting + CI/CD :s3, after s1, 3d
section Auth & Multi-tenancy
Auth UI (rejestracja, login, reset) :a1, after s2, 7d
Membership model + role gating :a2, after a1, 5d
Switcher gospodarstw + invitations :a3, after a2, 7d
section Core Modules
Pola — CRUD + lista + szczegóły :c1, after s3, 7d
Magazyn — 3 typy + alerty + auto-odejmowanie :c2, after c1, 10d
Prace polowe — formularz uniwersalny :c3, after c2, 14d
Mieszaniny ŚOR + kolejność WG/SC/EC/SL :c4, after c3, 7d
Faktury + koszty + dashboard :c5, after c4, 10d
Raporty PDF (klient-side, pakiet kontroler) :c6, after c5, 7d
section Onboarding & Retention
Onboarding wizard A + B :o1, after a3, 7d
Post-onboarding journey (emaile) :o2, after o1, 5d
Empty states + skeleton loaders :o3, after c1, 5d
section Billing
Stripe integration + plany + checkout :b1, after c5, 7d
Subscription panel + dunning + faktury VAT :b2, after b1, 7d
section Landing & Launch
Landing page (Next.js static) :l1, 2026-06-01, 14d
Beta testers (5-10) feedback loop :l2, after b2, 14d
Smoke testy + monitoring + go-live :l3, after l2, 7d
| # | Zadanie | Odp. sekcja | Estymata |
|---|---|---|---|
| 1 | Monorepo (Turborepo) + design system base (Tailwind config, shadcn init, tokens z palety prototypu, Inter font) | s.12.1 | 5 dni |
| 2 | Supabase: schemat tabel (Users, Memberships, Farms, Fields, ChemWarehouse, SeedWarehouse, GrainWarehouse, WorkHistory, Invoices, Subscriptions, AuditLog, sor_catalog) + RLS policies | s.7 | 1 tydz |
| 3 | Landing page (Next.js static): 9 sekcji, analytics Plausible, cookie banner | s.2 | 2 tyg (równolegle) |
| 4 | Auth UI: rejestracja + weryfikacja email + login (email/hasło + Google OAuth) + reset hasła + magic link (Supabase Auth) | s.3 | 1 tydz |
| 5 | Multi-tenancy: currentFarmId context, RLS enforcement w API client, switcher gospodarstw w top barze | s.6, s.7 | 1 tydz |
| 6 | Membership: zaproszenia (email z tokenem), akceptacja, role gating w UI + backend, matrix uprawnień | s.6 | 1 tydz |
| 7 | Onboarding wizard A (founder, 5 kroków) z aha momentem na kroku 4 | s.4 | 1 tydz |
| 8 | Onboarding wizard B (zaproszony user, 3 ekrany) | s.4.10 | 3 dni |
| 9 | Moduł Pola: CRUD, lista, szczegóły, dodawanie ręczne (mapy w fazie 2) | s.5.5 | 1 tydz |
| 10 | Moduł Magazyn: 3 typy (zboże, chemia, nasiona), alerty progowe, auto-odejmowanie przy zabiegu | s.5.4 | 1.5 tyg |
| 11 | Moduł Prace polowe: formularz uniwersalny (8 typów), kalkulacja kosztów zł/ha, persystencja | s.5.3 | 2 tyg |
| 12 | Mieszaniny ŚOR: multi-add, sortowanie WG/SC/EC/SL, walidacja numerów zezwoleń, koszt sumaryczny | s.5.4 | 1 tydz |
| 13 | Moduł Faktury: dodawanie ręczne, kategoryzacja, link do zabiegów | s.5 | 1 tydz |
| 14 | Moduł Koszty: agregacja z zabiegów i faktur, dashboard, filtry per uprawa/pole/data | s.5 | 4 dni |
| 15 | Dashboardy: ownerDashboard, workerDashboard, agronomDashboard (s.6.8), accountantDashboard | s.5, s.6.8 | 1.5 tyg |
| 16 | Raporty PDF klient-side (react-pdf): pakiet kontrolerski ARiMR (Pro feature, brak w trialu) | s.9 | 1 tydz |
| 17 | Stripe: konfiguracja planów (Pro mies/rok, Agronom Pro), checkout, webhook handler w Cloudflare Worker | s.8 | 1 tydz |
| 18 | Subscription panel: aktualny plan, metoda płatności, faktury VAT, anulowanie | s.8 | 5 dni |
| 19 | Dunning: webhook payment_failed → email sequence (1d/3d/5d/7d), banner w apce, read-only mode | s.8 | 4 dni |
| 20 | Ustawienia konta vs gospodarstwa (rozdzielenie zgodnie z s.9), flow usunięcia konta z obsługą jedynego właściciela | s.9 | 1 tydz |
| 21 | Eksport/import JSON gospodarstwa (RODO Art. 20) | s.9.8 | 3 dni |
| 22 | Email transakcyjny (Resend): templates dla 12 events (welcome, weryfikacja, reset, zaproszenie, dunning x4, retencja x4) | s.4.11 | 1 tydz |
| 23 | Post-onboarding journey: 9 touchpoints w 30 dni trialu (scheduler w Cloudflare Cron Triggers) | s.4.11 | 4 dni |
| 24 | Empty states + skeleton loaders + retry buttons + globalny error boundary | s.11.8 | 1 tydz |
| 25 | Smoke testy E2E (Playwright): 5 najważniejszych flowów (login, onboarding, wpisanie zabiegu, generacja PDF, billing webhook) | s.10.8.2 | 1 tydz |
| 26 | Monitoring: Sentry + Better Stack uptime + Crisp helpdesk | s.10.6 | 3 dni |
| 27 | Beta testers (5-10 osób z grupy docelowej, w tym Przemek) — feedback loop 2 tyg | — | 2 tyg (równolegle) |
Łącznie: 27 zadań × średnio 5-7 dni roboczych = ~140-180 person-days = 14-16 tygodni dla solo foundera (Adam) lub 8-10 tygodni dla pary (Adam + 1 dev).
Cel: 100+ płatnych gospodarstw, NPS > 40, zero blokerów do skalowania.
| Faza | Kluczowe metryki | Targety |
|---|---|---|
| Faza 1 (MVP) | Liczba beta testerów aktywnych, trial → paid conversion, time-to-aha-moment | 10 płatnych gospodarstw, >25% conversion z trialu, <15 min onboarding |
| Faza 2 (Wzrost) | MRR, churn, NPS, organiczny ruch na landing | 5000+ zł MRR, <5% mies churn, NPS > 40, 1000+ unique/mies na landing |
| Faza 3 (Enterprise) | ARR, # enterprise contracts, ARPU | 100k+ zł ARR, 3+ enterprise klientów, ARPU > 70 zł |
| Ryzyko | Prawdopodobieństwo | Wpływ | Mitigacja |
|---|---|---|---|
| Nikt nie chce płacić — rolnicy nie SaaS-people | Średnie | Krytyczny | Walidacja w fazie 0: rozmowy z 10-20 rolnikami przed kodem. Jeśli „za drogo / nie potrzebuję" — pivot. |
| Konkurencja (eDWIN, SatAgro) wyprzedzi z polskim UI | Niskie | Średni | Trzymać się obietnicy „prostota i polskie realia" |
| Refaktor vanilla JS okaże się większy niż myśleliśmy | Średnie | Średni | Time-box: jeśli faza 1 punkt 6+7 zajmie > 6 tyg, rozważyć cięcie scopu (np. najpierw tylko 'pola' migrujemy, reszta na potem) |
| Supabase okaże się za drogi / wolny / lock-in | Niskie | Średni | Schema PostgreSQL — łatwo zmigrować na własny self-host gdy będzie sens |
| RODO / podatki / regulacje rolnicze utrudnią start | Średnie | Wysoki | Prawnik na pokładzie od fazy 0. Status „beta" pozwala na uproszczone obowiązki. |
| Stripe / Przelewy24 odrzuci nasz typ biznesu | Niskie | Krytyczny | Walidacja w fazie 0 (rozmowa z merchant supportem przed pełnym onboardingiem) |
| Burnout solo founder (Adam) | Średnie | Krytyczny | Pair z Claude, automatyzacja, mówić „nie" featurom poza scopem, regularny czas off |
Przemek — Ty jesteś autorem prototypu agro360-mvp2.html i to dzięki Tobie wiemy że logika agronomiczna działa. Cała reszta dokumentu to nasza propozycja przekształcenia tego w pełnoprawny SaaS. Ta sekcja jest specjalnie dla Ciebie — zostawiamy listę konkretnych pytań na które tylko rolnik może odpowiedzieć rzetelnie. Twoje „nie tak" zaoszczędzi nam tygodnie pracy.
Opisaliśmy Tomka (s.1.1): 42 lata, 60-150 ha, własny ciągnik + opryskiwacz, kombajn często wynajmowany, 0-2 stałych pracowników, smartfon Android, Excel na poziomie podstawowym, korzysta z Top Agrar / Agrofakt.
Założyliśmy że doradcy agronomiczni (firmy chemiczne, niezależni doradcy) obsługują 10-30 rolników i chętnie zapłacą 99 zł/mies za apkę z dostępem do danych klientów.
Zakładamy 49 zł/mies za gospodarstwo (490 zł/rok). Dla Tomka 60-150 ha to ok 0.5-0.8 zł/ha/mies — czyli koszt 4-5 litrów Roundupa rocznie.
Powtarzamy 3 obietnice (s.1.5):
W prototypie zaimplementowałeś sortowanie mieszanin wg formulacji (WG → SC → EC → SL). To jest jedna z naszych „kopiujemy 1:1" decyzji (s.5.4).
Prototyp ma 12 preparatów hardcoded. Plan: rozszerzyć do 100+ w MVP i aktualizować co kwartał z gov.pl.
Rolnictwo to silnie sezonowy biznes. W dokumencie wspominamy „tryb żniw" (s.11), zmiana sezonu 1 września (s.11.7), klęski żywiołowe.
Założyliśmy 4 role (s.6.1): Właściciel, Współwłaściciel, Pracownik, Agronom. Plus pomysł na Księgową.
Killer feature MVP: PDF pod kontrolę ARiMR/PIORiN.
Glosariusz (s.0.4) zawiera 25+ terminów. Sprawdź czy używamy tych samych słów co rolnik:
Ten dokument jest naszą hipotezą. Bardzo prawdopodobne że czegoś nie pomyśleliśmy.
W Fazie 1 (s.12.4) potrzebujemy 5-10 beta testerów z grupy docelowej. To kluczowy zasób.
Po Twoich odpowiedziach planujemy:
Dzięki za prototyp, Przemek. Bez Twojej wizji to byłoby teoretyzowanie. Z Twoją wizją — mamy specyfikację działającą na żywym organizmie, z prawdziwą logiką rolniczą i polskimi realiami. Twoja walidacja tej sekcji to ostatni krok przed kodem.