Rodzinny biznes wynajmu → produkcyjny SaaS
w 6 tygodni, z Claude Code jako co-pilotem
Zbudowałem od zera system zarządzania najmem krótkoterminowym (PMS) dla rodzinnego biznesu — multi-tenant, SaaS-ready, z pełną automatyzacją przepływu umów, integracjami, testami i audytem. Działa produkcyjnie, obsłużył 5 prawdziwych rezerwacji. Prototyp zajął 4 godziny, produkcja 6 tygodni „przy okazji".
Kontekst
Rodzina prowadząca najem. Excel, Messenger, email — chaos.
Nieruchomość wynajmowana krótkoterminowo, rozliczana ryczałtem. Ojciec — właściciel, podpisuje umowy. Córka — koordynuje rezerwacje i obsługuje klientów, to główny aktywny użytkownik systemu.
Stan „przed": rezerwacje w Excelu, koordynacja na grupie Messengera, szablony umów rozproszone po mailach, ręczne generowanie i wysyłanie kolejnym klientom, kaskada odpowiedzi zwrotnych z podpisanymi skanami, brak miejsca na koszty i rozliczenie podatkowe.
Prosty problem, typowy dla małych firm — ale żaden komercyjny PMS nie dopasowany: za drogi, za generyczny, wielu kanałów których nie używamy, brak wsparcia dla polskiego ryczałtu.
Ból procesu
- • Rezerwacje w Excelu, łatwo o zdublowanie daty
- • Grupka Messengera jako „system koordynacji"
- • Chaos z szablonami umów — kto ma aktualną wersję?
- • Ręczne generowanie umów → wysyłka → czekanie → druk → podpis → skan → drugi mail
- • Brak widoczności kosztów, brak zestawień do PIT
- • Zero metryk rentowności
Hipoteza
Jedna osoba + LLM jako co-pilot = produkcyjny SaaS w tygodniach, nie miesiącach.
Priorytet #1
Bezpieczeństwo. Rodzinne dane klientów (PESEL, adresy, podpisane umowy) nie mogą wypłynąć. Zero half-baked security.
Priorytet #2
Pełna automatyzacja umów. Od pobrania danych gościa przez formularz, przez generowanie umowy z szablonu, wysyłkę, odesłanie podpisanej, aż po powiadomienie właściciela — zero ręcznej ingerencji.
Priorytet #3
Ship fast, iteruj. Nie enterprise clean code — good-enough z testami. Deploy na produkcję cały czas, feedback od realnych użytkowników.
Core feature
Automatyzacja przepływu umów — od zapytania do archiwum
Centralny flow całego systemu. Córka tworzy rezerwację, dalej wszystko dzieje się samo — włącznie z wysyłką podpisanej umowy do ojca, który ją kontrasygnuje.
- 1
Rezerwacja wpływa
Bezpośrednio przez admin panel lub przez formularz zapytania na stronie (widget embed). Automatyczne sprawdzenie overlap z istniejącymi terminami + logika turnover day (checkout w dniu check-in innego gościa to OK).
actions/reservations.ts · lib/pricing · /api/public/availability
- 2
Link do formularza gościa
Córka klika „wygeneruj link", system tworzy jednorazowy token (256-bit entropy, 7 dni ważności), URL przygotowany do skopiowania (z ochroną przed trailing newline w env). Gość dostaje link i wypełnia swoje dane bez logowania.
actions/guest-form.ts · randomBytes(32) · /formularz/[token]
- 3
Gość wypełnia formularz
Walidacja Zod (PESEL z decodePesel, email regex, rate-limit 10/15min per token). Token jest unieważniany po pierwszym użyciu — brak replay attacków.
Zod schema · rate-limit · token invalidation after submit
- 4
Automatyczne wygenerowanie umowy
System bierze szablon umowy (domyślny dla org lub wybrany dla rezerwacji — np. „Zapłata z góry za całość"), wypełnia placeholdery ({{najemca_imie_nazwisko}}, {{data_od}}…) danymi z rezerwacji + formularza. Nowy rekord Contract w bazie, share link UUID v4.
generateContractFromTemplateInternal · 15 placeholderów
- 5
Email do gościa z linkiem
Resend wysyła email „Umowa najmu — Domek Wiśniew (6 czerwca 2026)" z przyciskiem „Otwórz umowę" (strona /share/[token] sanityzowana przez sanitize-html, bez XSS). Każdy email trafia do EmailLog z pełną treścią HTML do podglądu w adminie.
Resend API · sanitizeContractHtml · /share/[token] · EmailLog
- 6
Gość podpisuje i odsyła PDF
Gość otwiera link, drukuje/podpisuje tradycyjnie lub kwalifikowanym PAdES (mObywatel). Wgrywa podpisany PDF przez /share/[token]/upload (public endpoint, walidacja: tylko PDF, max 20MB, idempotentność przez shareToken). Jeśli podpis elektroniczny jest — weryfikacja PAdES przez node-forge, metadane wystawcy zapisane.
/api/public/upload-signed-contract · Vercel Blob · node-forge PAdES
- 7
Automatyczna notyfikacja właściciela
System wysyła do ojca (properties.ownerEmail) maila z linkiem do podpisanego PDF + zielonym banerem „Podpis elektroniczny zweryfikowany" (jeśli PAdES) lub pomarańczowym „weryfikacja manualna" (jeśli skan). Ojciec otwiera, kontrasygnuje, proces zakończony.
contract.status → GUEST_SIGNED → OWNER_SIGNED → FINALIZED
- 8
Wszystko w jednym miejscu
Cały flow widoczny w timeline rezerwacji: guest form filled, contract sent, signed, owner notified. Historia w audit logu, globalny log emaili z akcją „Wyślij ponownie" gdyby coś padło. Zero ręcznej pracy na każdym etapie.
19 tabel Prisma · AuditLog · EmailLog · ReservationTimeline
Co potrafi system
14 modułów, grupowanych po efekcie biznesowym
Zarządzanie rezerwacjami
CRMLista z filtrami (status, platforma, płatność), szczegóły z tabami, ochrona przed overlapping, status workflow (NEW → CONFIRMED → WAITING_DEPOSIT → COMPLETED → CANCELLED) z dozwolonymi tranzycjami, turnover day logic.
Umowy
CoreTiptap WYSIWYG editor, szablony ze zmiennymi (15 placeholderów), workflow statusów (DRAFT → SENT → GUEST_SIGNED → OWNER_SIGNED → FINALIZED), inline podgląd podpisanego PDF przez iframe, weryfikacja PAdES z mObywatel, IDOR guards na każdej mutacji.
Portal gościa
CRMSamoobsługowy formularz dla gościa (imię, adres, PESEL z decodePesel), walidacja Zod, jednorazowy token 256-bit, rate-limit 10/15min, auto-invalidate po użyciu, automatyczne wygenerowanie i wysłanie umowy.
Komunikacja email
Marketing automation5 wyzwalaczy cyklu życia (reservation created → contract sent → confirmed → pre-checkin → post-checkout), edytor szablonów, globalny log /emails z filtrami, „Wyślij ponownie" jednym kliknięciem, sandbox mode dla demo.
Widgety web
FrontendKalendarz dostępności, kalkulator ceny, opinie gości, formularz zapytania — wszystkie jako iframe embed do Framera/dowolnej strony. Generator kodu HTML w adminie. ISR z cache 5 min, rate-limited API, CORS.
Finanse
KPIPrzychód roczny + tracking progu podatkowego (rozliczenie ryczałtem 8,5% / 12,5%), rejestr kosztów po kategoriach, listy wydatków, zestawienie do PIT, widok „unsettled" rezerwacji.
Cennik
LogicReguły cenowe z priorytetami: BASE, SEASONAL (zakres dat), DAY_OF_WEEK (weekendowa stawka), SPECIAL (święta/long-stay), minimum nights, czasowe overlapy. Używane w widgecie pricing + w kreatorze rezerwacji.
Kalendarz operacyjny
OpsMiesięczny widok rezerwacji, drag-over detalami (gość, telefon, status), eksport iCal (publiczny feed dla Booking/Google Calendar), filtry po nieruchomości.
Sprzątanie
OpsKanban board (PENDING → IN_PROGRESS → COMPLETED → VERIFIED), automatyczne taski przy checkout, assignees, kolorowanie przy deadline.
Ankiety gości
CXPost-stay survey (5 ocen: overall, cleanliness, communication, check-in, value + wouldRecommend + comments), email automation wysyłka po checkout, agregacja ocen i statystyki na /surveys.
Zapytania (inquiries)
FunnelFormularz zapytania na zewnętrznej stronie → automatyczny INSERT do Inquiry → notyfikacja emailem do adminów → konwersja jedną akcją do rezerwacji (zachowanie estimatedPrice, guestName, dat).
Multi-tenant SaaS
ArchPełna izolacja między organizacjami via IDOR guards (wszystkie mutacje scoped przez organizationId w chainie). Custom domains per org, middleware routing, sandbox mode. Gotowe do commercialization.
Audit log
CompliancePełna historia zmian (CREATE/UPDATE/DELETE/STATUS_CHANGE/SIGN/SEND_EMAIL), diff old → new, kto i kiedy, filtry per entity/user/data. Ważne dla księgowości i regulacji RODO. Automatic PII redaction (PESEL/password nigdy w metadanych).
Self-service konto
UXUser może sam zmienić hasło w /settings/account (stare + nowe, bcrypt 12 rund, walidacja długości). Admin reset hasła też istnieje, ale user nie musi do niego iść gdy sam pamięta stare.
Podgląd
System w akcji
Pełny panel adminowy, kalendarz operacyjny, moduł umów z edytorem WYSIWYG, globalny log maili z akcją „wyślij ponownie", kokpit finansowy, embeddable widgety do strony Framer — wszystko działa z realnymi danymi.

Dashboard
Przegląd rezerwacji, KPI, nadchodzące meldunki

Lista rezerwacji
Filtry po statusie, płatności, platformie. Widoczne różne statusy

Szczegóły rezerwacji
Timeline, dane gościa, umowy, email log, komentarze, audit history

Kalendarz operacyjny
Miesięczny widok, drag-over detalami, eksport iCal

Lista umów
Status badges, inline download podpisanego PDF

Umowa — edytor + workflow
Tiptap WYSIWYG, status stepper, sidebar z share linkiem i uploaded PDF

Globalny log emaili
Filtry po statusie, okresie, gościu. Akcja „Wyślij ponownie" na wpisach FAILED

Automatyzacja email
Szablony i tryby wysyłki (auto/manual/disabled) dla 5 triggerów

Ankiety gości
Agregacja ocen, rekomendacje, recenzje po pobycie

Finanse
Przychód roczny, próg ryczałtu, koszty per kategoria

Cennik — reguły
Base / Seasonal / Day-of-week / Special z priorytetami

Widgety embed
Generator iframe do osadzenia na zewnętrznej stronie

Kalendarz publiczny
Widget osadzony na domekwisniew.pl — wybór dat, obliczanie ceny, zapytanie
Bezpieczeństwo (priorytet #1)
Sprint pod kątem security — 6 znalezionych, 6 naprawionych
Przy rodzinnych danych nie ma kompromisów. Przeprowadziłem pełny audyt z diagnozą i fiksami, pokryty testami jednostkowymi.
IDOR na mutacjach umów
updateContract / deleteContract / updateContractStatus nie weryfikowały czy umowa należy do org callera — user z org A mógł modyfikować umowy z org B znając tylko ID. Typowy IDOR bug.
Fix: contractBelongsToOrg() helper scoping przez relację Contract → Reservation → Property → organizationId. 7 nowych testów.
XSS na stronie /share/[token]
dangerouslySetInnerHTML na contract.content bez sanityzacji. Admin (lub compromised account) mógłby wstrzyknąć <script> który odpalił się w przeglądarce gościa.
Fix: sanitize-html z whitelist Tiptap tagów, rel="noopener noreferrer nofollow" na linkach, 19 testów pokrywających payloady (onerror, javascript: URL, iframe, etc).
Trailing newline w env var
NEXT_PUBLIC_GUEST_FORM_URL w Vercelu miał \n na końcu (wpisany z Enterem). Copy-link do formularza produkował złamany URL: "https://panel.domekwisniew.pl\n/formularz/TOKEN".
Fix: lib/urls.ts z getBaseUrl()/getClientBaseUrl() — trim + strip trailing slashes, fallback chain. 17 testów regresji.
Brak rate-limitingu na API cennikowym
Konkurencja mogłaby enumerować całą siatkę cenową przez /api/public/calculate-price (ceny per noc dla każdej daty). Zero kosztów dla atakującego, business-intelligence leak.
Fix: getClientIp() + isRateLimited() na /availability (120/min), /calculate-price (60/min), /inquiry (5/15min), /ical (60/min).
Token replay po użyciu
guestFormToken i surveyToken pozostawały ważne po pierwszym submissionie. Ktoś z leaked link (zrzut, forward email) mógł zobaczyć dane.
Fix: Token set to null w tej samej transakcji co save response. One-shot usage guaranteed.
PII w audit logu
Jeśli diffChanges() byłby użyty na rezerwacji z PESEL/passwordHash, wartości trafiłyby plaintext do JSON AuditLog.changes.
Fix: SENSITIVE_FIELDS whitelist w lib/audit.ts. Redakcja *ALL* PII keys do [REDACTED] przed zapisem.
Ship-to-prod
Realne incydenty produkcyjne, fixed in hours
Tu się dzieje najciekawsza robota — ship-first oznacza też radzenie sobie z problemami które wychodzą dopiero na proda. W każdym z tych przypadków: diagnoza → test reprodukujący → fix → deploy, średnio w ciągu godziny.
Auto-wysyłka umowy padła, klientka nie dostała
~60 minSymptom
Joanna Kałuska wypełniła formularz, guestFormFilledAt zapisane, ale 0 umów i 0 emailów w bazie. Status rezerwacji DRAFT.
Diagnoza
submitGuestForm wywoływał generateContractFromTemplate który robił requireAuth() → redirect(/login) rzuca wyjątek → catch cichutko połyka → form zwraca success ale nic się nie stało.
Fix
Extract _generateContractFromTemplate do shared internal fn (wzorowane na istniejącym sendContractEmailInternal). Public wrapper z auth nadal istnieje dla admin. 15 regression testów.
Vercel build crash po upgradzie Resend
~30 minSymptom
Deploy failed z "Missing API key. Pass it to the constructor new Resend(re_123)". Action: każdy route importujący @/lib/email zabijał build.
Diagnoza
Resend SDK ≥6.0 rzuca w konstruktorze jeśli API key brak. Vercel build phase "collect page data" nie ma dostępu do env (secret-only-runtime), eager new Resend(undefined) w lib/email.ts.
Fix
Lazy proxy. Instancja tworzona przy pierwszym resend.emails.send(), nie przy module load. Zero zmiany dla callerów.
Upload umowy blokowany dla skanów
~20 minSymptom
Klient wydrukował umowę, podpisał ręcznie, zeskanował i dostał 422 „Plik PDF nie zawiera podpisu cyfrowego, podpisz w mObywatel…".
Diagnoza
verifyPdfSignature() rzucał reject przy PDF bez podpisu PAdES. Blokada wynikała z zbyt rygorystycznej walidacji — rodzina Wiśniew głównie dostaje umowy skanowane.
Fix
Verification is now best-effort — try/catch, continue without. Jeśli podpis jest: zielony banner w emailu do właściciela. Jeśli nie: pomarańczowy „weryfikacja manualna".
Kalendarz blokował turnover days
~15 minSymptom
Formularz zapytania blokował wybór daty wymeldowania gdy inny gość miał w tym dniu zameldowanie. Klient nie mógł wybrać sensownego terminu.
Diagnoza
isBooked(dateStr >= r.from && dateStr < r.to) traktował dzień zameldowania jako „zajęty", ale w logice wynajmu to turnover day — checkout rano, check-in popołudniu, legitne.
Fix
Dual check: isBooked dla start-date picker (strict), isBookedMidstay dla end-date picker (tylko strictly inside another stay). Calendar znów klika turnover days.
Narzędzia
Stack — dobór pod efekt, nie pod framework
AI co-pilot
Claude Code
Opus 4.7 w 1M-context window jako primary vibecoder, nie dodatek. Opisuję problem biznesowy po polsku, Claude proponuje architekturę, iterujemy, test, deploy. Każdy feature z tej strony powstał w takiej pętli.
Frontend / Backend
- Next.js 14 App Router + Server Components
- TypeScript strict mode
- Prisma ORM + migracje
- NextAuth v5 (credentials)
- Tiptap WYSIWYG editor
- sanitize-html XSS guard
- Zod schema validation
- Vitest (200 testów)
Infra / integracje
- Vercel deploy + cron + domains
- Neon Postgres serverless DB
- Vercel Blob file storage (PDFs)
- Resend transactional email
- node-forge PAdES signature verify
- iCal public calendar feed
- Playwright screenshot automation
- GitHub + auto-deploy na Vercel
Metryki
Liczby — 6 tygodni, 1 osoba + Claude
Jak pracuję z AI
Claude jako pair-programmer, nie generator kodu
Pętla jest prosta: opisuję problem biznesowy po polsku („gość wypełnił formularz ale umowa nie przyszła"), Claude diagnozuje z bazy lub kodu, proponuje fix, ja review → testy → deploy.
Discovery po rollout. Zaczynam od mapowania flow biznesowego (kto, co, gdzie, kiedy), hipoteza, metryka sukcesu — potem dopiero stack i kod.
Ship nad clean code. Jak coś działa, idzie na proda. Testy i refactor jako reakcja na realne incydenty, nie prewencyjnie „enterprise".
Przykład z produkcji
User (ja)
sprawdź w bazie czy została wysłana umowa do klientki Joanna Kałuska
Claude (po kilku tool calls)
Rezerwacja istnieje, contractTemplateId ustawiony,
guestFormFilledAt = 15:31,
ALE: 0 umów, 0 emaili.
Status umowy: DRAFT. Fix: extract `generateContractFromTemplateInternal` — obecna wersja wywołuje `requireAuth()` w publicznym flow, rzuca redirect, catch cicho połyka.
Od zauważenia problemu do deployu fixa: ~60 minut, z testami reprodukującymi błąd.
Demo
Zaloguj się i poklikaj
Sandbox — fake data
Demo organizacja z 10 rezerwacjami, umowami, ankietami. Dane są zupełnie fikcyjne, izolowane od produkcji. Emaile są tylko logowane — Resend nigdy nie zostanie wywołane. Reset danych co 24h (Vercel Cron).
→ Otwórz demoCo warto sprawdzić
- Dashboard z przeglądem rezerwacji
- Lista rezerwacji — status badges, timeline, akcje
- Szczegóły umowy z inline podglądem PDF
- /emails — globalny log z filtrami + „Wyślij ponownie"
- Ankiety — agregacja ocen, recenzje
- Finanse — przychód roczny, próg podatkowy
- Cennik — reguły cenowe (base/sezonowe/weekendowe)
- Widgety embed — generator iframe do Framera
- /audit — pełna historia zmian (sandbox: tylko kilka wpisów)
Demo user ma rolę OPERATOR — widzi ~95% funkcji, bez dostępu do zarządzania użytkownikami i billingu.
Kontakt
Porozmawiajmy
Ten case study istnieje głównie pod ofertę AI VibeCoder for business w Locon. Jeśli Ty, rekruterze, to czytasz — wszystko czego szukasz (ship-fast, LLM-driven, business-first) znajdziesz tu w formie działającego produktu, nie slajdów.
Chętnie pokażę co jeszcze — od pracy z AI agentami i RAG, przez workflow automation (call center, lead scoring, marketing automation), po KPI dashboards i integracje.
Dane
Jakub Kazimierczyk
AI VibeCoder · Product builder