~/lebenslauf/projekte/venevio
cat projekte/venevio.md

Venevio.

Reservierungssystem für Gastronomiebetriebe, betrieben als mandantenfähiges SaaS: Ein Betrieb bucht ein Abo und bekommt eine eigene Buchungsseite — unter eigener Domain, als Subdomain, als Pfad oder eingebettet in die bestehende Website. Kern des Produkts ist kein Kalender, sondern der Umgang mit dem teuersten Problem der Branche: Gäste, die nicht erscheinen.

Rolle
Produkt, Entwicklung
und Betrieb — allein
Umfang
70 API-Routen
62 SQL-Migrationen
Stack
Next.js, React,
Supabase, Stripe
Status
live unter
venevio.de
01

Ausgangslage

Das eigentliche Problem

Ein reserviertes Tischpaar, das nicht erscheint, kostet doppelt: Der Tisch bleibt leer, und die Gäste, die man dafür abgewiesen hat, sind auch weg. Am Freitagabend um 20 Uhr trifft das härter als am Dienstagmittag — und bei acht Personen härter als bei zweien.

Die naheliegende Antwort wäre, für jede Reservierung eine Kaution zu verlangen. Das funktioniert in der Praxis nicht: Wer für zwei Personen am Dienstag zwanzig Euro hinterlegen soll, bucht woanders. Die Lösung muss also unterscheiden können — nach Gästezahl, Wochentag, Uhrzeit und danach, wie sich dieser Gast in der Vergangenheit verhalten hat.

Genau das war die Produktentscheidung: nicht das hundertste Reservierungsformular, sondern ein Regelwerk, das der Betrieb selbst einstellt und das im Normalfall unsichtbar bleibt.

02

No-Show-Schutz

Kernstück

Jeder Betrieb hinterlegt eine Regel: ab wie vielen Gästen, an welchen Wochentagen, in welchem Zeitfenster, wie viel pro Person — und einen Deckel nach oben. Eine Buchung wird gegen diese Regel geprüft, bevor überhaupt nach einer Zahlung gefragt wird. Fällt sie durch eines der Kriterien, ist keine Kaution fällig und der Gast merkt vom ganzen System nichts.

deposit_rules
-- eine Regel pro Betrieb, vom Betreiber im Dashboard gepflegt
min_guest_count     INTEGER   DEFAULT 6        -- ab 6 Personen
amount_per_person   INTEGER   DEFAULT 1000     -- 10,00 € in Cent
max_deposit_amount  INTEGER   DEFAULT 2000     -- Deckel: 20,00 €
days_of_week        INTEGER[] DEFAULT '{5,6}'  -- nur Fr + Sa
time_range_start    TIME      DEFAULT '18:00'
time_range_end      TIME      DEFAULT '22:00'
high_risk_override  BOOLEAN   DEFAULT true     -- Risiko sticht Regel

-- Betrag, gedeckelt:
amount = min(gäste × amount_per_person, max_deposit_amount)

Der Deckel ist wichtiger, als er aussieht. Ohne ihn zahlt eine Zwölfergruppe 120 Euro Kaution und bucht nicht — obwohl gerade große Gruppen das eigentliche Risiko sind. Mit Deckel bleibt die Kaution ein Signal, keine Hürde.

Nach dem Besuch läuft die Rückerstattung von selbst: Ein täglicher Cronjob sucht bezahlte Kautionen zu vergangenen Terminen, erstattet sie über Stripe zurück und schickt eine Bestätigung. Bleibt die Reservierung als No-Show markiert, verfällt der Betrag — auch darüber wird der Gast informiert. Der Betreiber muss in beiden Fällen nichts tun.

03

Gästereputation

Ohne Klarnamen

Damit „hat schon dreimal nicht abgesagt" überhaupt bewertbar ist, führt das System eine Historie pro Gast — betriebsübergreifend. Das ist datenschutzrechtlich der heikelste Teil des Produkts, also liegt in dieser Tabelle keine Telefonnummer und keine E-Mail-Adresse im Klartext:

guest_reputation
hashed_phone     VARCHAR(64)   -- Hash, kein Klartext
hashed_email     VARCHAR(64)   -- Hash, kein Klartext
total_bookings   INTEGER
no_shows         INTEGER
cancellations    INTEGER

Nachgeschlagen wird über den Hash der eingegebenen Kontaktdaten. Wer die Datenbank liest, sieht Zahlen und Hashes — aber keine Liste von Personen. Daraus entsteht beim Buchen eine Einstufung in low, medium oder high, die auf der Reservierung gespeichert wird. Steht die Regel auf high_risk_override, kann diese Einstufung eine Kaution auslösen, die sonst nicht fällig wäre.

Wer wiederholt nicht erscheint, landet auf einer Sperrliste. Das ist die Sorte Funktion, bei der die Umsetzung mehr über Verantwortung entscheidet als über Technik — deshalb Hashes statt Klartext und eine Einstufung, die begründbar bleibt, statt einer undurchsichtigen Punktzahl.

04

Mandantenfähigkeit

Ein System, viele Betriebe
[01]

Vier Wege zur richtigen Buchungsseite

Das System erkennt den Betrieb an der Subdomain (mario.venevio.de), an einer eigenen Domain des Kunden (pizzamario.de), am Pfad (/r/pizza-mario) — oder liefert die Seite als eingebettetes Widget in eine bestehende Website. Vier Einstiege, eine Codebasis.

[02]

Trennung in der Datenbank, nicht im Code

Jede relevante Tabelle trägt eine restaurant_id, und Row Level Security in Postgres setzt die Grenze durch. Eine vergessene Abfragebedingung im Anwendungscode kann damit keine fremden Reservierungen ausliefern — die Datenbank gibt sie schlicht nicht heraus.

[03]

Vier Tarife, ein Regelwerk

Basic, Premium, Professional und Ultra unterscheiden sich in freigeschalteten Funktionen. Welcher Tarif was kann, steht in einer Tabelle statt in Bedingungen quer durch den Code; die Oberfläche fragt an einer Stelle nach und blendet entsprechend frei.

[04]

Geld fließt am Betreiber vorbei

Abogebühren laufen über Stripe an Venevio. Kautionen dagegen gehören dem Restaurant — sie laufen über Stripe Connect auf dessen eigenes Konto. Der Betrieb verbindet sein Konto im Dashboard selbst; die Plattform hält fremdes Geld nie.

[05]

Ein Cronjob für alles Wiederkehrende

Statt fünf Zeitplänen läuft ein täglicher Lauf, der alles Fällige abarbeitet: abgelaufene, nie bestätigte Reservierungen löschen, Erinnerungen per E-Mail und WhatsApp verschicken, Kautionen erstatten oder verfallen lassen, Bewertungsanfragen versenden. Ein Einstiegspunkt, ein Log, ein Ort für Fehler.

[06]

Empfehlungen mit echter Abrechnung

Wer einen Betrieb wirbt, bekommt 25 % Provision auf jede monatliche Zahlung. Ausgelöst wird das nicht beim Vertragsabschluss, sondern bei jedem erfolgreichen Zahlungseingang über den Stripe-Webhook — Provision entsteht also nur, wenn auch wirklich Geld geflossen ist.

05

Funktionsumfang

Was der Betrieb bekommt

Buchungsseite

Eigene Seite mit Logo und Farben des Betriebs, wahlweise eingebettet in die bestehende Website.

Tische & Zeitblöcke

Tischverwaltung mit Konfliktprüfung, dazu Öffnungszeiten und Sperrzeiten für Feiertage oder private Feiern.

Manuelle Freigabe

Optional landet jede Anfrage zuerst beim Betrieb: E-Mail mit allen Angaben, Freigabe oder Ablehnung per Klick.

Erinnerungen

Automatische Erinnerung vor dem Termin per E-Mail und WhatsApp — das wirksamste Mittel gegen vergessene Reservierungen.

Gutscheine

Verkauf über Stripe, Zustellung als PDF, Einlösung im Haus über einen QR-Scanner im Dashboard.

Auswertungen

Auslastung, Stornoquote und No-Show-Quote als Diagramme, dazu Gästelisten mit Filtern und Sortierung.

Aktionscodes

Rabattcodes mit Gültigkeitsprüfung und Einlöseprotokoll, getrennt von den Gutscheinen.

Abo-Verwaltung

Tarifwechsel mit Vorschau auf die anteilige Verrechnung, Kündigung und Reaktivierung — alles im Dashboard.

Eigene Domain

Kunden verbinden ihre Domain selbst; die Zuordnung und Prüfung läuft automatisch über die Domain-Schnittstelle der Hosting-Plattform.

06

Technik & Entscheidungen

Und was sie gekostet haben

Der Stack: Next.js 16 mit React 19 auf Vercel, Oberfläche mit Tailwind und Radix-Bausteinen, Daten und Authentifizierung über Supabase (Postgres mit Row Level Security) plus NextAuth, Zahlungen über Stripe inklusive Connect und Webhooks, E-Mails über Resend, Fehlerüberwachung mit Sentry. Rund 257 JavaScript-Dateien, 70 API-Routen, 62 SQL-Skripte.

Regeln in Daten statt in Code. Kautionsbedingungen, Tariffunktionen und Öffnungszeiten liegen als Zeilen in der Datenbank, nicht als Bedingungen im Anwendungscode. Ein Betrieb, der freitags ab vier Personen kassieren will, braucht damit keine Anpassung im Code — nur einen anderen Datensatz. Der Preis: mehr Prüflogik zur Laufzeit und Migrationen, die man sauber schreiben muss.

Ein Cronjob statt vieler. Fünf separate Aufgaben wären einzeln einfacher zu verstehen. Ein gemeinsamer Lauf ist dafür an einer Stelle nachvollziehbar, wenn nachts etwas schiefgeht — bei einem Projekt, das eine Person betreibt, zählt das mehr als saubere Trennung.

Kautionen über Stripe Connect statt über das eigene Konto. Der bequemere Weg wäre gewesen, das Geld selbst einzuziehen und weiterzuleiten. Das hätte aus dem Projekt ein Treuhandgeschäft mit allem gemacht, was daran hängt. Über Connect gehört das Geld dem Betrieb — die zusätzliche Einrichtung pro Kunde war es wert.