Files
Webklar-Kundenbereich/docs/superpowers/specs/2026-10-03-terminplaner-tp2-verkauf-design.md

44 KiB
Raw Permalink Blame History

Terminplaner – TP2 Verkauf: Design

Stand 2026-10-03. Basis: Portal main 3a0b008, WOMS test 65e9931, TP1-Spec docs/superpowers/specs/2026-10-02-terminplaner-design.md (E1–E8, Abschnitt „TP2 Verkauf“), TP1-Ledger R1–R55 (/root/reports/terminplaner-tp1/progress.md). Erkundung: /root/reports/terminplaner-tp2/erkundung-2026-10-03.json. Pfade: P = Portal-Repo (kundenbereich.webklar.com), W = WOMS-Repo (ticket.webklar.com). Alles unten ist aus dem Code gelesen (nichts gegen Produktion oder Stripe gelaufen), außer Aussagen mit „(Annahme)“. „Festlegung“ = technische Entscheidung. Die geschäftlichen und rechtlichen Entscheidungen D1–D8 hat der Inhaber am 2026-10-03 getroffen (Abschnitt 10); sie sind in alle Abschnitte eingearbeitet.


1. Ziel und Abgrenzung

TP2 liefert: Ein Kunde mit laufendem Wartungs-Abo bestellt den Terminplaner selbst: Kachel → Fragebogen (Entwurf) → Bestellung mit Zustimmungen → Stripe (149 € einmalig) → automatisches Ticket mit Einbauanleitung → Einbau durch das Team → Freischaltung → Abo-Kopplung (endet das Abo, endet der Terminplaner, Löschung nach 30 Tagen). Dazu der Rechtstexte-Satz (Leistungsbeschreibung, AVV, Datenschutz 6c, Gästetexte final) und danach TERMINE_ENABLED=on.

Bleibt für TP3: Online-Zahlung der Gäste (Stripe Connect, Erstattungen, Connect-Webhook), Abo-Bedingungen v6 bzw. Rahmen-AVV mit Anlage je Funktion, Stripe-Absatz in 6c, Nachweisdatensätze über 90 Tage.

Pilotkunde „test“ (kenso.gri@gmail.com): bleibt per WOMS „Aktivieren (ohne Zahlung)“ aktiv (unlockedByPurchase=false). Festlegung: Der tägliche Abo-Abgleich (Abschnitt 6) nimmt solche Dokumente aus, sonst würde der Pilot beim ersten Lauf abgeschaltet. Seine Daten und Einstellungen bleiben unverändert. Er dient als Prüfkunde für den Livegang-Test (Abschnitt 9).

Festlegungen zum Umfang:

  • Ein Terminplaner gilt je Website-Projekt (Feature-Dokument je Projekt, wie News). 149 € fällt je Projekt an und umfasst ein Widget an einer Stelle mit Gestaltung nach Fragebogen und den Einbau; Mehrwünsche (zweites Widget, Knöpfe je Leistung, Sonderdesign) nach Stunden (D4).
  • Nur der Modus „vor Ort bezahlen“ (E7). Keine Gruppen, kein Gesundheitsbereich (Spec „Nicht Teil“).
  • News bleibt unverändert nutzbar; es wird nur auf das Register umgestellt (Abschnitt 4), Verhalten und Ticket-Text von News bleiben identisch.

2. Ablauf aus Kundensicht

Zustand Kachel (P/public/funktionen.js) Ansicht #termine (P/public/termine.js) Mail
ohne Wartungs-Abo „Im Wartungs-Abo enthalten. Zu Verträge“ (wie analyseTile), kein Fragebogen Hinweis –
off (Entwurf, auch ohne Doc) „Terminplaner einrichten · 149 €“ bzw. „Fragebogen fortsetzen“ Fragebogen, 8 Schritte + „Prüfen und bestellen“ –
ordered „Zahlung offen“ Statuskarte, Knopf „Zahlung fortsetzen“, Einstellungen nur lesbar –
setup „In Einrichtung“ Statuskarte (Ticketnummer, Rechnung), Einstellungen nur lesbar K15, T16
active „Termine öffnen“ wie TP1 K16
disabled „Termine ansehen“ lesen + CSV-Export, Banner nach Grund (Abo beendet + Löschdatum) K22

Schritte:

  1. Kunde mit laufendem Wartungs-Abo (Prüfung Abschnitt 6) sieht die Kachel. Er startet den Fragebogen; jeder Schritt speichert sofort als Entwurf (Abschnitt 3).
  2. „Prüfen und bestellen“: Zusammenfassung, offene Punkte (Server rechnet bestellpruefung), Zustimmungen (Abschnitt 4.3), Knopf „Zahlungspflichtig bestellen“.
  3. POST /api/funktionen/termine/checkout legt ordered an, Stripe Checkout (Karte + SEPA wie News). Rücksprung #termine?bestellt=1&projekt=<id>.
  4. Webhook (bestehender Pfad, abo.js:520-525) → markPaid: setup, Ticket, K15 an Kunde, T16 an Team.
  5. Team baut ein (Anleitung im WOMS), Abnahme, Freischaltung durch Ticket „Erledigt“ oder Staff-Knopf (Abschnitt 5) → active, K16.
  6. Ab dann Betrieb wie TP1. Endet das Abo: täglicher Abgleich → disabled, K22, nach 30 Tagen Löschung (Abschnitt 6).

Festlegung Sperre ab Bestellung: Ab ordered ist die Einstellung für den Kunden nur lesbar, weil das Team gegen den bezahlten Stand baut (sonst driftet Ticket-Kurzfassung gegen Einstellung). Änderungen laufen über den Chat; Staff in Kundenansicht darf weiter per PATCH ändern. Scheitert die Zahlung (markCheckoutFailed, ordered → off), ist der Entwurf wieder bearbeitbar.

Mails (alle Du-Form, Layouts der Inhaber-Vorlagen, nur Platzhalter ergänzen; Annahme: Inhaber akzeptiert das):

  • K15 Bestellbestätigung: {{funktion}}, {{preis}} aus Register, Block „kein Abo“ nur für News. Neu: Anhänge Leistungsbeschreibung + AVV (PDF, Fassung) und Widerrufsbelehrung mit Muster-Formular auf dauerhaftem Datenträger (Muster K18/legalPdfAttachments).
  • K16 „bereit“: {{funktion}}, {{portal_url}} = #${returnHash}, Knopftext aus Register.
  • T16 Team: {{funktion}}, Link zur Anleitung, neue Blöcke kein_abo und bereits_aktiv (Abschnitt 4.5).
  • K22 „Terminplaner beendet“ (Varianten Abo beendet, auf Hosting gewechselt, vor Einrichtung beendet mit Erstattung) und K23 „läuft wieder“; T19 an das Team „Erstattung fällig“ (4.6). IDs K22, K23, T19 sind frei (test/mailTemplates.test.js führt die Liste und den Kopfkommentar). Layouts nach den Vorlagen des Inhabers, Texte gehen in seine Mail-Überarbeitung.
  • K04/T09 (Widerruf): {{produkt}}, {{preis}} statt fester „News-Pinnwand, 30 €“-Texte.

3. Fragebogen

3.1 Speicherung (Festlegung: ein Dokument, kein Kopieren)

  • Der Entwurf ist die spätere Live-Einstellung: termineConfig.einstellung, Doc-ID = Feature-ID (kontext.js:12). Kein Compile- oder Kopierschritt bei Bestellung oder Freischaltung.
  • Neues Attribut termineConfig.fragebogen (JSON, 16 000 Zeichen, Schema 1): bestaetigt[] (Schrittschlüssel), bundeslandGewaehlt, preiseGeprueft, einbau{modus inline|knopf, knopfText ≤40, stelle ≤300, gestaltung ≤1000, bemerkung ≤1000}. Grund: normalisiereEinstellung verwirft unbekannte Schlüssel (einstellungen.js:505-541), und Einbauwünsche dürfen nie in die öffentliche Konfiguration. Setup über scripts/termine-setup.mjs (idempotent), test/termineSetup.test.js:78 und test/support/schema.js mitziehen.
  • Schritt „erledigt“ lässt sich nicht aus den Daten ableiten (Vorlagen machen jeden Stand gültig), deshalb die gespeicherte Bestätigung je Schritt.
  • PUT /api/termine/fragebogen schreibt nur fragebogen, updatedAt, updatedBy unter withLocks('termine-cfg:'+id), ohne version zu erhöhen (offene Editoren bleiben gültig). Nicht auf der Kundenansicht-Allow-Liste: Schritte bestätigt nur der Kunde selbst.

3.2 Zugriff in Entwurfszuständen

  • withTermine({ entwurf: true }) (P/server/routes/termine.js:137-157) nur für GET /einstellungen, POST /einstellungen/vorlage, PATCH /einstellungen, PUT /fragebogen. Erlaubt off, active; ordered/setup nur lesend (GET), Schreiben dort nur mit req.session.viewAs. disabled bleibt wie TP1. Alle anderen Routen behalten die Schranke active|disabled (Gäste- und Job-Pfade unberührt: kontextPerFeature bleibt, neuer Helfer kontextFuerPortal).
  • Ohne Feature-Dokument antwortet GET 200 {state:'off', einstellung:null, vorlagen, branchen} und legt nichts an; der erste Schreibzugriff ruft ensureFeatureDoc (Doc off, dauerhafter siteKey).
  • Entwürfe sind nie öffentlich: kontextPerSiteKey → findActiveBySiteKey verlangt active (siteFeatures.js:390-400).
  • PATCH ruft normalisiereEinstellung(..., {entwurf: state!=='active'}) (überspringt nur R13/R13a), Antwort enthält fehlt: fehlendePflichtangaben(...); der Client (termine-speichern.js:116) übernimmt es statt []. Tests termineRoutes.test.js:247-264 ändern sich entsprechend.

3.3 Schritte und Wiederverwendung der TP1-Editoren

# Schritt Umsetzung
1 Branche, Vorlage, Gesundheits-Ausschluss BRANCHEN-Liste in vorlagen.js; Eintrag „Gesundheitsbereich“ ohne Vorlage blockt; POST vorlage mit ersetzen:true im Entwurf, optionalen betrieb-Feldern (Name, E-Mail, Telefon, Anschrift, Bundesland, Website) und Vorbelegung der Website aus dem Projekt
2 Wer wird gebucht neue kleine Form: Einzelperson / Team / Plätze, Namen; setzt neue Ressourcen im selben PATCH allen Leistungen zu (Validator verlangt aktive Ressource)
3 Leistungen termine-tab-leistungen.js render unverändert, plus Haken „Preise geprüft“ (Vorlagenpreise sind Platzhalter)
4 Zeiten, Bundesland, Urlaub Wochenplan-Helfer aus termine-tab-team.js:69 exportiert, abwesenheiten/feiertage/sonderoeffnungen (bereits exportiert); Bundesland ohne Vorauswahl (Server-Default RP bei fehlendem Wert, einstellungen.js:512)
5 Regeln, sofort/Anfrage regeln, storno, rechtliches (gaesteSind, freizeit), zahlung aus termine-tab-einstellungen.js exportiert; Sammelknopf „sofort/Anfrage für alle“
6 Gastfelder gastFelder exportiert
7 Benachrichtigungen, Anrede, Erinnerung darstellung, erinnerung, betrieb.email (einzige Adresse; sie ist auch Reply-To und Ziel von K20/K21)
8 Website betriebLinks (Impressum, Datenschutz, AGB, Website), telefon (= Ausweichtelefon), Einbauwünsche-Form
9 Prüfen und bestellen siehe Abschnitt 4

Festlegungen UI: neue Datei P/public/termine-fragebogen.js (Name in die Liste test/termineView.test.js:277-280; Du-Form, kein innerHTML, kein href); die Bestellseite wegen der Link-Regel (termineView.test.js ~290: nur ein exportUrl-href) in P/public/funktion-bestellen.js (heißt nicht termine*.js, wird relativ importiert, R4). Jeder Schritt = ein <form> mit Weiter = ein PATCH über neuerSpeicher (einziger PATCH-Pfad), danach PUT /fragebogen. Pro Betrieb nur ein Ressourcentyp (Annahme: Person+Platz gemischt ist im Validator nicht verboten, das Verhalten ist ungeprüft).

3.4 Bestell-Gate (serverseitig, server/services/termine/fragebogen.js, reine Funktion bestellpruefung)

Offen = Liste {schritt, text}; Checkout lehnt bei nicht-leerer Liste ab, GET zeigt sie. Festlegung strikt: bestellbar nur wenn (a) fehlendePflichtangaben leer, (b) mind. eine sichtbare Leistung, (c) alle Schritte bestätigt, (d) Bundesland gewählt, (e) einbau.modus gesetzt, (f) Branche nicht Gesundheit, (g) impressumUrl und datenschutzUrl vorhanden (der Gästehinweis verweist auf die Datenschutzerklärung; das Widget lässt den Link sonst still weg, embed/termine.js:519), (h) Wartungs-Abo sauber laufend (Abschnitt 6). Offene Punkte klärt der Kunde vor der Bestellung, nicht per Chat danach.

3.5 Gesundheits-Ausschluss (D6)

Heute nur Hinweistexte (rechtstexte.js:169), keine Sperre. Umsetzung nach D6: Branchenfrage mit hartem Ausschluss (kein Entwurf), Haken „Mein Angebot liegt nicht im Gesundheitsbereich“ im Bestellschritt (mit Zeit im Zustimmungsnachweis), und eine weiche Stichwortprüfung über Leistungsnamen, Beschreibungen und Zusatzfeld-Beschriftungen (Liste in fragebogen.js, z. B. Physio, Therapie, Rezept, Beschwerden, Diagnose, Behandlung, Arzt, Heilpraktiker): nur ein Warnblock in T16 und im Ticket, kein Fehler für den Kunden. Das Team kann die Einrichtung ablehnen (Erstattung nach 4.6, Variante Widerruf/Kulanz). „Sonstiges“ bleibt wählbar.

3.6 Einbauwünsche

Modus inline oder knopf (Snippet erweitert um data-modus, data-knopf; Widget kann das, embed/termine.js:232-234), Knopftext, Stelle auf der Website in Worten, Gestaltungswunsch, Bemerkung. Festlegung V1: keine Knöpfe je Leistung (data-leistung nur auf Nachfrage über Bemerkung, Mehraufwand nach Stunden). Texte des Kunden werden im Ticket nur als Text und in einem Block „Kundenwunsch (Text des Kunden)“ ausgegeben.

3.7 Entwürfe aufbewahren und löschen

Heute löscht niemand einen Entwurf (Job loescheAbgeschaltete nur disabled; disableCustomerFeatures überspringt off, siteFeatures.js:321-326). Festlegung: Tagesjob-Schritt löscht termineConfig + Feature-Doc von Dokumenten in off (nie ordered/setup), die 90 Tage nicht geändert wurden (Annahme: 90 Tage passt zur Datenschutz-Aussage, Rechtsrunde bestätigt); Kundenlöschung (customerAdmin.js:289) entfernt Entwürfe ebenfalls. Die Datenschutzerklärung (6c, §7) nennt Entwurfsdaten und die Frist.


4. Bestellung und Zahlung

4.1 Register config.features (ersetzt feste News-Werte)

P/server/config.js: neben newsSetup neuer Katalogeintrag termineSetup = {key, name:'Terminplaner – Einrichtung', amountCents:14900, lookupKey:'webklar_termine_einrichtung_14900', oneTime:true, priceNickname}, Stripe-Metadaten-Schlüssel termine-einrichtung. Neu config.features = { news:{…}, termine:{…} } mit: product, label{name,kurz,keine}, priceLabel, setupEstimate, consent{version, items[]}, leistungsbeschreibungVersion, returnHash, ticket{topic(project), details(ctx)}, mails{funktion, knopf, danach, preisHinweis}, requiresWartungsAbo, disabledMessage, orderedConflictMessage. config.funktionen.{setupEstimate,consentText} bleiben als News-Alias, damit funktionenRoutes.test.js:63-64 nur gezielt geändert wird.

Zu ersetzende News-Festverdrahtungen (alle verifiziert): stripeBilling.js:54-61 (PRODUCT_METADATA, catalogFor), :649 (ensurePrice('newsSetup')), :661, :665-666 (URLs), routes/funktionen.js:122,137,158-171, siteFeatures.js:17-18 (DISABLED_MESSAGE), :360 (activateClosedTickets nur news), :406 (portalUrl = #news), :537-538, :584-587, ticketAdmin.js:80,85,95-102, public/funktionen.js:9,261-317.

4.2 Stripe

createFeatureCheckout(customer, {projectId, key, consentAt}) liest Preis, Rechnungsbeschreibung, Rücksprung und Abbruch-URL aus dem Register; invoice_data.metadata mit featureKey, projectId. SEPA bleibt wie bei News (Festlegung; Einbau startet erst bei setup, also nach Zahlungseingang). Der Preis termineSetup wird vor dem ersten Live-Kauf per scripts/stripe-products.mjs --pruefen/--anlegen angelegt (KEYS :20 und catalogEntry :30-34 ergänzen; Test nach Vorbild stripeBilling.test.js:246-260), sonst legt ihn der erste Kunden-Checkout lazy an (und ensurePrice sucht nur in den ersten 100 aktiven Produkten, :81-82).

4.3 Route und Zustimmungen

  • POST /api/funktionen/:key/checkout (Schlüssel gegen Register; /news/checkout bleibt Alias). Nicht unter /api/termine/: dort erlaubt kundenansichtGuard.js:19 jedes POST für Staff in Kundenansicht. Zusätzlich req.session.viewAs → 403 (wie funktionen.js:139).
  • Reihenfolge: viewAs, Zustimmungen vollständig und Fassung = aktuelle Serverfassung (sonst 409 mit neuer Fassung), Projekt gehört dem Kunden, termineFuerKunde(email) (Pilot-/Modusschranke), bestellpruefung leer, Wartungs-Abo (Abschnitt 6), Lock feature-order:<customer>:<project>, Zustandsprüfung (Meldungen aus dem Register), markOrdered.
  • Zustimmungen für Termine (Wortlaut ist Rechtsrunde, hier nur Struktur; nach D1 werden alle Besteller wie Verbraucher behandelt): (1) Leistungsbeschreibung + AVV Terminplaner, Fassung X, mit versionierten Links; (2) „kein Gesundheitsbereich“; (3) ausdrückliches Verlangen des Beginns vor Ablauf der Widerrufsfrist mit Hinweis auf Erlöschen (§ 356 Abs. 5 Nr. 2 BGB) und Wertersatz (§ 357a Abs. 2 BGB; die News-Zustimmung hat nur die erste Hälfte); (4) Pflichtinformationen (Leistung, Gesamtpreis, Ende mit dem Wartungs-Abo, Einrichtungsdauer) und volle Widerrufsbelehrung + Muster-Formular vor dem Knopf; (5) Knopf „Zahlungspflichtig bestellen“; ob „· 149 €“ am Knopf steht, entscheidet die Rechtsrunde (Recherche rät zu nichts außer den gesetzlichen Worten; die Spec schreibt „· 149 €“).
  • Nachweis: neue append-only Collection featureConsents (Muster analyseConsents; Felder customerId, projectId, featureKey, stripeSessionId, Fassungen, vollständige gezeigte Texte, Hash, Zeit, IP, UA, Konto). Die 1000-Zeichen-Spalte portalFeatures.consentText reicht für den Satz nicht (setup-abo-collections.mjs:277). IP/UA kommen in Datenschutz §7.
  • Widerrufs-Anker: Zeit des Klicks (Vertragsschluss) aus featureConsents, nicht orderedAt (der bei Neubestellung überschrieben wird). Annahme: so ist die Frist korrekt gedeutet, Rechtsrunde.

4.4 Rechnung

Stripe erzeugt sie nach Zahlung (invoice_creation), backfillInvoiceLinks ergänzt den Link später (K15 geht deshalb oft ohne Link, Block rechnung; unverändert). Preistext „149 € (ohne Umsatzsteuer gemäß § 19 UStG)“ statt „brutto/inklusive Mehrwertsteuer“ (Spec E2 sagt „brutto“; Stripe-Footer und Abo sagen § 19, Annahme: WEBklar bleibt Kleinunternehmer, Rechtsrunde bestätigt; gilt gleichzeitig für News).

4.5 Fehlerfälle

  • Verschluckte Zahlung (heute real): Aktiviert Staff ein ordered-Dokument per „Aktivieren (ohne Zahlung)“, sieht der Webhook created:false und tut nichts (siteFeatures.js:249,521). Festlegung: onFeaturePaid schickt in dem Fall T16-Block bereits_aktiv (Zahlung ohne Ticket), und WOMS blendet die Aktion in ordered/setup für Termine aus (Abschnitt 5).
  • Zahlung ohne Abo (Abo endete während der 24-h-Sitzung): Dokument geht trotzdem auf setup samt Ticket (Geld ist da), T16 mit Block kein_abo; Aktivierung bleibt gesperrt, bis ein Abo läuft. Nach D3 wird voll erstattet, wenn das Abo vor der Fertigstellung endet (siehe 4.6).
  • Ticket scheitert nach Zahlung: bestehende Wiederaufnahme (setup ohne ticketId, Stripe-Retry) bleibt; neu zusätzlich Fallback auf Minimal-Details (Abschnitt 5).
  • Zahlung offen: Knopf „Zahlung fortsetzen“ (derselbe Checkout liefert die offene Sitzung, openOrderSession); SEPA kann Tage in ordered bleiben.

4.6 Erstattung (D3)

  • Gesetzlicher Widerruf: Erstattung abzüglich Wertersatz für einen schon begonnenen Einbau (nach Stundenaufwand, § 357a Abs. 2 BGB).
  • Zusätzlich volle Erstattung, wenn das Wartungs-Abo endet oder widerrufen wird, bevor der Terminplaner active ist (Zustand ordered/setup). Festlegung: Der tägliche Abgleich (Abschnitt 6) erkennt den Fall, setzt das Dokument auf disabled mit disabledReason='abo_ende_vor_einrichtung', schließt keine Tickets selbst, und meldet ihn dem Team per Mail T19 „Erstattung fällig: Terminplaner-Einrichtung“ (T17/T18 sind vergeben) (Kunde, Projekt, Stripe-Sitzung, Rechnung). Die Erstattung selbst löst der Inhaber in Stripe aus (keine automatische Rückzahlung, keine Live-Stripe-Aufrufe durch Code ohne Freigabe). Der Kunde bekommt K22 in der Variante „vor Einrichtung beendet, Betrag wird erstattet“.
  • Das Ticket bleibt offen, bis das Team es mit Vermerk schließt; die Freischaltung beim Schließen prüft Abo und Zustand und schaltet dann nicht frei.

4.7 Widerrufsformular

  • Widerruf: widerrufen.html bekommt eine eigene Auswahl „Terminplaner (Einrichtung)“; withdrawFeature/paidFeatureOrders rechnen die Frist je Funktion (heute Maximum über alle Bestellungen, abo.js:1091-1093), T09 nennt die Funktion. Abwicklung bleibt Handarbeit des Teams (Rückerstattung, Abschalten, Ticket).

5. Ticket, Einbauanleitung, WOMS

Ticket: markPaid übergibt topic/details aus dem Register an createFeatureTicket (kann es schon, analyse.js nutzt es). Festlegungen: topic auf 128 Zeichen kürzen (heute ungekürzt, ticketAdmin.js:85), Details-Deckel 7000 Zeichen (Schema nennt 8000, ungeprüft, das Setup-Skript ist für portalFeatures nachweislich veraltet); scheitert createDocument mit langen Details, einmal mit Minimal-Details (Kopfzeilen + Verweis) wiederholen. Zuweisung: keine, wie News; T16 meldet das Ticket. Ticket-Hinweis „Nicht auf das Stundenkontingent anrechnen“ (nach analyseTexts.js).

Generator: P/server/services/termine/einbauanleitung.js, rein und deterministisch (now Parameter), liest ladeEinstellung(featureId) (kontext.js:75, kein Modus-/Zustandscheck), nie feedToken (Feldweißliste + Test). Ausgabe {stand{version,updatedAt}, warnungen[], abschnitte[{id,titel,text,kopierbar}], text} mit: Modell, Leistungen, Ressourcen + Zeiten, Regeln, Gastfelder, Benachrichtigungen, Einbau (Snippet mit siteKey; einbauSnippet aus termine.js:185-196 hierher verschoben und um knopf erweitert; CSS-Variablen --wkt-akzent aus farbe, Kontrastfarbe per WCAG-Helfer; Ausweichtext; Impressum/Datenschutz-Links), Datenschutz-Absatz (Sie/Du nach anrede), Abnahme-Checkliste (bedingt: Verbraucher → Widerruf, Knopf → dauerhafter Widerrufsknopf), Abrechnung („Einrichtung bezahlt (149 €, Rechnung …), Zeit im Arbeitsblatt, nicht auf Abo-Stunden“). Die Quelle des Datenschutz-Absatzes wandert aus dem Markdown als datenschutzAbsatz(einstellung, anrede) nach rechtstexte.js (Test pinnt beide, wie bisher).

  • Kurzfassung in ticket.details (Klartext, WOMS zeigt pre-wrap, TicketCard.jsx:124-127): Kopf (Funktion, Projekt, Domain, Kunde, siteKey, „Konfigurationsstand: Version N, Datum“), Modus/Stelle/Gestaltungswunsch, Zählungen + erste Namen mit Kürzung, Snippet, 5–6 Schritte, Verweis womsBaseUrl/customers/<id>?tab=funktionen. Ohne Datenschutz-Absatz (4,8 k Zeichen je Variante).
  • Route: GET /api/team/funktionen/:customerId/termine/anleitung?projectId= in funktionenAdmin.js (requireStaffJwt, assertOwnProject, getFeatureDoc; kein TERMINE-Modus-Gate, kein viewLog, nie aus Kundenseite erreichbar). Vergleich mit dem Ticket-Stand liefert Warnung „Einstellungen seit der Bestellung geändert (Ticket Version N, jetzt M)“. Fehlen Einstellungen: 200 mit Warnung und nur Snippet.
  • Datenschutz-Absatz-Sperre: Solange die Gästetexte als ENTWURF gekennzeichnet sind, druckt die Anleitung das Banner mit; mit der Freigabe (Abschnitt 7) fällt es weg.

WOMS (W/src/components/customer/CustomerFunktionen.jsx, W/src/lib/funktionenView.js, teamApi.js):

  • Je Termine-Zeile Knopf „Anleitung“, klappt ein Panel unter der Zeile auf (Stand, Ticketlink, Warnungen, „Alles kopieren“, Teilkopien für Snippet/CSS/Datenschutz Sie/Du); Zeilen werden zu Fragmenten mit Key. Neu teamApi.getTermineAnleitung.
  • aktionen(state, key): für Termine kein „Aktivieren (ohne Zahlung)“ in ordered/setup; in off/disabled bleibt es. Neu in setup: „Freischalten (Abnahme)“ (aktiviert wie Ticket-Schließen, schickt K16, setzt Ticket nicht zurück) und bei „Ticket fehlt“ „Ticket nachlegen“ (idempotent). Tests funktionenView.test.mjs:48-55 anpassen.

Freischaltung (Festlegung): activateClosedTickets filtert Query.equal('featureKey', [Schlüssel mit Ticket-Freischaltung]) (heute fest news, siteFeatures.js:360). Für Termine verlangt sie vorhandene Einstellungen und ein laufendes Abo, sonst bleibt setup (Log + T16-Hinweis). Der Öffentlich-API-Zugriff gibt es nur bei active (kontext.js:106); eine echte Probebuchung vor dem Livegang ist damit nicht möglich. Deshalb der Ablauf: Einbau auf der Kundenseite (Widget bleibt unsichtbar, da aktiv:false) → „Freischalten (Abnahme)“ → Probebuchung nach Checkliste und Stornierung → Ticket „Erledigt“. Reines Schließen des Tickets aktiviert weiterhin automatisch (Fallback, Idempotenz via Lock feature-activate:<id>). Ein Kundenfreigabe-Schritt entfällt (Festlegung, wie News).


6. Abo-Kopplung

Gemeinsame Prüfung (Festlegung): P/server/services/aboStatus.js zieht hasWartungsAbo aus analyse.js:43-46 heraus; liest nur den Appwrite-Spiegel subscriptions (getCurrentSubscription, stripeBilling.js:178), ruft Stripe nicht auf. Zwei Prädikate:

  • laeuft(customerId) = productKey||'wartung' (wie abo.js:68; die Analyse vergleicht heute strikt === 'wartung', Annahme: Altbestand ohne productKey existiert, Produktion nicht geprüft) und Status ∈ ACTIVE_STATUSES (active, trialing, past_due). Dient dem Weiterlaufen und dem Abgleich. Lesefehler werfen (nie abschalten bei Fehler).
  • bestellbar(customerId) = Status active und keine Kündigung vorgemerkt (cancelAtPeriodEnd/cancelAt, Muster Zusatzspeicher abo.js ~92-95). Dient Kachel, Checkout und markPaid. Verhindert 149 € in der letzten Woche eines gekündigten Abos oder in der Widerrufs-Probezeit. Meldungstexte je Funktion (kein_abo(funktion) statt „Website-Analyse“); analyseEligibility ändert sein Verhalten nicht. Pro Lauf Memo je customerId (Feature-Docs sind je Projekt, das Abo je Kunde).

Schema (vor dem Deploy ausführen, sonst Unknown attribute): portalFeatures.disabledReason (abo_ende|team|kunde_geloescht|frist), aboEndeMailAt (datetime), in scripts/setup-abo-collections.mjs (test/support/schema.js liest sie). disable(reason) wird idempotent: ein bereits disabled Dokument behält disabledAt (heute überschreibt es siteFeatures.js:304-312 und würde die 30-Tage-Löschung endlos verschieben). activate() löscht zusätzlich disabledReason/aboEndeMailAt (R36 löscht disabledAt schon).

Täglicher Abgleich gleicheAboAb in P/server/services/termine/aboAbgleich.js, im taeglicherJob (jobs.js:420-429) vor loescheAbgeschaltete, Ergebnis nur als Zählung im Tagesbericht (aboAbgleich{geprueft, abgeschaltet, wiederEin, mails, fehler}), Listen mit listAllDocuments.

  1. active-Termine gruppiert je Kunde; Kunde ohne laeuft → unter withLocks('feature-admin:<projectId>') neu lesen, noch aktiv und noch kein Abo → disable('abo_ende', 'Abgeschaltet: Wartungs-Abo beendet'). Ausgenommen: unlockedByPurchase !== true (Pilot/Kulanz, siehe Abschnitt 1). Lesefehler → Kunde überspringen. 1b. ordered/setup-Termine (bezahlt oder in Zahlung) eines Kunden ohne laeuft → unter derselben Sperre neu lesen → disable('abo_ende_vor_einrichtung'), T19 an das Team (Erstattung nach D3, 4.6), K22 Variante „vor Einrichtung beendet“. Eine offene Stripe-Sitzung (ordered) wird nicht storniert; kommt die Zahlung danach noch an, meldet onFeaturePaid den Fall ebenfalls mit T19 statt ein Ticket anzulegen.
  2. Mail-Nachlauf: disabled + abo_ende + kein aboEndeMailAt → K22 (Enddatum, Löschdatum = disabledAt + 30 Tage, was bleibt möglich: ansehen, CSV-Export, Gäste dürfen absagen; Betrieb soll seine Gäste informieren) und erst nach erfolgreichem Versand aboEndeMailAt setzen (Analyse sendet K19 nur einmal ohne Nachlauf, das wäre hier der Verlust der Löschmitteilung).
  3. Wiederkehr: disabled + abo_ende + laeuft + termineConfig noch vorhanden → activate() (Daten und Einstellungen bleiben), K23. Nach der Löschung nie automatisch (Konfig fehlt).

Sofortpfade: Widerruf eines Wechsels in der Frist (abo.js:1051-1058, beendet heute schon die Analyse) → Termine ebenfalls sofort, abo_ende, K22. Kundenlöschung deaktiviert Termine bereits (customerAdmin.js:285-292).

Löschung nach 30 Tagen: unverändert (loescheAbgeschaltete, nochAbgeschaltet). Neu: vor jedem unumkehrbaren Schritt bei abo_ende frische Abo-Prüfung (bricht ab, wenn das Abo wieder läuft). Nach der Löschung setzt der Job das Feature-Doc auf off (disabledAt leer, unlockedByPurchase=false, ticketId leer), damit es nicht täglich neu gescannt wird (final-deferred.md:25) und neu bestellt werden kann (heute blockt routes/funktionen.js:160 jedes disabled); der siteKey bleibt, das eingebettete Snippet bleibt gültig. Test termineJobs.test.js ~771 („Funktionsdokument bleibt“) ändert sich. Die Bestellung nach Löschung kostet 149 € erneut (D2).

Modus: Der Abgleich läuft im taeglicherJob und steht bei TERMINE_ENABLED=off still (jobs.js:93); im Pilot läuft er für alle Termine-Docs, nicht nur Pilot-Adressen. Festlegung: Abgleich und Löschung laufen auch bei off, solange es Dokumente mit unlockedByPurchase=true gibt (Aufteilung des Guards in fuehreAus), denn die Datenschutz-Zusage „30 Tage danach“ darf nicht an einem Schalter hängen. Der Abgleich muss vor on live sein (der Satz in 6c ist sonst falsch).

Rückkehr (D2): Innerhalb der 30 Tage schaltet der Abgleich automatisch und ohne Gebühr wieder ein (Daten und Einstellungen bleiben). Nach der Löschung braucht es eine neue Einrichtung: neuer Fragebogen und neue 149 €. Dieser Satz steht in LB, AVV, 6c und K22.

Mehr: K02/K03 (Kündigungsbestätigungen) bekommen einen Block „Analyse und Terminplaner enden mit dem Abo“. Banner/Meldung in public/termine.js:65-67 und routes/termine.js:59 unterscheiden nach disabledReason und zeigen das Löschdatum; featureView liefert disabledReason/disabledAt, WOMS zeigt „Abgeschaltet (Abo beendet), Löschung am …“. Erinnerungen an Gäste (G05) laufen weiter, wie R34 entschieden (unverändert; K22 weist darauf hin).


7. Rechtstexte und Rechtsrunde

7.1 Zu erstellen (Quellen Markdown in P/docs/rechtliches/, HTML + PDF in P/public/rechtliches/)

  1. Leistungsbeschreibung Terminplaner (Geschwister der LB Website-Analyse): Geltung/Rangfolge (Zusatz zum Wartungs-Abo, Hosting reicht nicht), Leistung, Nicht enthalten (SMS, Online-Zahlung, Kalender-Sync, Gesundheitsbereich), Voraussetzungen und Pflichten des Betriebs (Impressum/Datenschutz/AGB, Telefon-Ausweichlösung, Texte unverändert lassen, Beschäftigte informieren), Verfügbarkeit, Haftung für verpasste Termine, Einrichtung 149 € (Leistung, Ablauf per Ticket, Dauer, Preis nach § 19 UStG, nicht auf Abo-Stunden), Ende mit dem Abo (Abschalten spätestens am Folgetag, Mail, Export, Löschung nach 30 Tagen, auch bei Wechsel auf Hosting und Widerruf), Einstellungsrecht, keine Rechtsberatung/Mustertexte, BFSG-Satz.
  2. AVV Terminplaner (Geschwister des AVV Analyse, kein Zusatz dazu, damit Analyse-Kunden nicht neu zustimmen müssen): eigene Nr. 1–3 (Gast-Name, E-Mail, optional Telefon/Nachricht/Zusatzfelder, Termin, Hashes, Mailstatus; Betroffene Gäste und Beschäftigte; Art. 9 ausgeschlossen), Nr. 7 Hetzner Falkenstein und Nürnberg (Buchungsmails über Webhosting-SMTP), Nr. 8 Betroffenenrechte (Anonymisieren, CSV), Nr. 9 Löschfristen genau wie gebaut (Abgeschaltet 30 Tage, 48 h/30 Tage/aufbewahrungTage, Outbox 7 Tage/48 h, Backups bis 7 Tage), TOM-Anlage; Abschluss per Haken + Staff nur mit Textform.
  3. Datenschutz 6c aus termine-datenschutz-6c-entwurf.md als <h2 id="termine"> in P/public/datenschutz.html, die 8 offenen Punkte klären; §6 (Kundenansicht: Staff darf Termine lesen und ändern, Protokoll) und §7 (Nachweise bei Funktionsbestellung, Entwurfsaufbewahrung) ergänzen; docs/WEBklar-Datenschutzerklaerung.pdf neu bauen; Wächtertest termineRechtstexte.test.js:267-274 entfernen, legal_datenschutz_kundenansicht anpassen.
  4. Gästetexte final: ENTWURF-Köpfe in termine-hinweise.md, -widerrufsbelehrung-muster.md, -datenschutz-absatz.md entfernen, rechtstexte.js und Markdown gemeinsam ändern (Tests pinnen wörtlich), RECHTSTEXTE_VERSION anheben, Archiv je Fassung (Nachweis speichert nur Hashes, ablauf.js:279-290).
  5. Bestelltexte (Abschnitt 4.3) und Zustimmungsfassungen im Register; Widerrufsformular-Beschriftung; K15-Anhänge.
  6. WOMS „Rechtliches“: Karten leistungsbeschreibung-terminplaner, avv-terminplaner (und ggf. Datenschutz) in W/scripts/setup-legal.mjs DOCUMENTS; Upload der Versionen macht der Inhaber (Löschen nicht möglich).
  7. Technik dazu: analyseLegal.js zu kleiner Dokument-Registry verallgemeinern (bzw. paralleles Modul), eigene Fassungskonstante config.termine.avvVersion unabhängig von config.analyse.avvVersion, build-legal-pdfs.sh um Modus termine; Mails und Zustimmungstexte verlinken nur versionierte Adressen.

7.2 Prüfung durch eine Kanzlei (D5)

Das Kernpaket prüft eine Kanzlei, bevor der erste fremde Betrieb startet: Widerrufsbelehrung und Muster-Formular der 149-€-Bestellung, Zustimmungssatz (vorzeitiger Beginn, Erlöschen, Wertersatz), AVV Terminplaner, Haftungs- und Ende-mit-dem-Abo-Klauseln der Leistungsbeschreibung, sowie die Gästetexte mit Gesetzlichkeitsfiktion (Widerrufsbelehrung Gäste, Hinweise im Widget). Alles andere (6c, Portal-Hinweise, Mailtexte) prüft die Hausrunde wie bei der Analyse. Lieferung von uns: Kanzlei-Paket P/docs/rechtliches/termine-kanzlei-paket.md mit den Texten in Endfassung, dem Produktablauf in Kurzform, den konkreten Fragen (Liste 7.3) und den Fundstellen im Code; der Inhaber beauftragt die Kanzlei. Befund und Änderungen fließen vor Livegang-Schritt 9 ein.

7.3 Rechtliche Punkte, vor on zu entscheiden oder zu erledigen (Fragen ans Kanzlei-Paket markiert mit ⚖)

  • ⚖ Stand der Widerrufsbelehrung für Funktionen: LB News §6 hat keine gesetzliche Musterbelehrung, K15 keine Belehrung/Anhänge. Meine Lesart (kein Rechtsurteil): die Frist läuft für Verbraucher womöglich nicht an (§ 356 Abs. 3 BGB). Für News-Altkunden entscheidet der Befund (D7): Nur wenn die Frist danach nicht lief, wird die Belehrung per Mail nachgeholt; sonst gelten die neuen Texte nur künftig.
  • Zitat „§ 356 Abs. 4“ → „Abs. 5 Nr. 2“ in config.js:192,196, funktionen-leistungsbeschreibung.html:51,73, funktionen-news-design.md:73 und Test legalTexts.test.js:485; fehlende Wertersatz-Hälfte in der News-Zustimmung; „inklusive Mehrwertsteuer“ (funktionen.js:272, LB :49,:60) widerspricht § 19 UStG. Gleiche Runde (die Spec schiebt die Korrektur ausdrücklich nach TP2).
  • Wer fügt den Datenschutz-Absatz auf der Kundenseite ein und ist das im Preis (Analyse LB Nr. 4 Abs. 4 sagt: WEBklar, nicht auf Stunden). Eine Regel für beide LB.
  • Portalseitige Rechtsaussagen: WIDERRUF_HINWEIS, FREIZEIT_HINWEIS (Massage/Weiterbildung „in der Regel keine Freizeitleistung“ trägt die Recherche nicht, termine-tab-einstellungen.js:16-23), stornoMuster, Gesundheitshinweise; Storno-Vorgabe 50 %/24 h gilt auch für kostenlose Leistungen (Festlegung: bei Leistungen ohne Preis keine Storno-Vorgabe und kein Pflichthaken, Text als Muster kennzeichnen).
  • Anfrage mit Festpreis und der Bestätigungslink als „Bestellung“ (G01-Knopf, Seitenknopf „Zahlungspflichtig bestätigen“), Einstufung Massage/Fotograf/Coaching (Spec Offene Punkte 2).
  • Pflicht von Datenschutz-/Impressum-Link (hier als Bestell-Gate gesetzt) und der „jederzeit widerrufen“-Satz unter dem Erinnerungshaken.
  • Abo-Ende-Texte (LB, AVV, 6c, K22) und Aussagen „Drittland“ (Kalender-Abos bei Google/Apple), „Falkenstein und Nürnberg“, Anker für „30 Tage“; BFSG-Satz nur so weit getestet (kein axe-/Screenreader-Lauf in TP1).
  • Staff-Aktivierung (adminActivate, siteFeatures.js:351-356) prüft heute weder Einwilligung noch AVV. Festlegung: ab Modus on verlangt adminActivate für Termine einen Textform-Nachweis wie die Analyse (analyse.js ~262-270, WOMS-Chatvorlage nach analyseView.js:55-90), ausgenommen Pilot-Adressen.
  • Owner-Prüfung bei Hetzner (nur im Konto prüfbar): AV-Vertrag für Cloud und Webhosting, Anlage 1 mit „Terminbucher/Beschäftigte/Buchungsdaten“, Kontoadresse = Impressum, Backups an.

7.4 Später

Abo-Bedingungen v6 bzw. Rahmen-AVV mit Anlage je Funktion; TP3-Themen (Stripe-Absatz, ZAG/Connect, Vorauszahlung in AGB, PAngV); Geschäftsbrief-Fußzeilen § 37a HGB; Rechtsmissbrauch bei spätem Widerruf; Minderjährige; Mitgliedschaften; wiederholtes Versenden von G08/K21 nach Fristende; Widerruf nur vor Terminbeginn im Verwaltungslink.


8. Technische Voraussetzungen aus TP1

Skalen-Schätzungen (Annahmen: 6 Buchungen je Betrieb und Tag, 12 Zellen je Buchung, 20 Tage Vorlauf, Spitzenfaktor 6; echte Werte können um Faktor 2–3 abweichen). Heute reicht alles für 1 Betrieb; die Listen laufen bei 40–60 Betrieben (bei Aufbewahrung 365 Tage schon bei ~10) in list_limit_exceeded, der nur geloggt wird.

MUSS vor TERMINE_ENABLED=on (ein fremder Betrieb im Pilot braucht #1, #3, #5, #6):

# Maßnahme Ort
1 Aufbewahrungs-Löschung begrenzt und seitenweise: je Feature Query.limit(100) + Index (status, endsAt), Deckel je Lauf (z. B. 2000 je Feature), Rest läuft an Folgetagen; heute listet loeschKandidaten alle (Deckel 20 000, buchungen.js:191), Überlauf = nie Löschung von Gastdaten, nur Fehlerlog. Gleiches Muster für abgesagt/abgelehnt/verfallen, loescheAbgeschaltete und export.csv jobs.js:296-313,350-353, routes/termine.js:450-453
2 Zellen-Sweep seitenweise über idx_bis (lessThan('bis', jetzt-1d), Limit 100, Deckel 5000/Lauf) statt alleZellen() (Deckel 100 000); Map.get().push() statt quadratischem [...old, zelle] (zellen.js:247); Zeitfenster-Prüfung in ungueltigeZellen zellen.js:46,224-270,92-99
3 Kundenansicht-Wildcard POST /api/termine/.+ (kundenansichtGuard.js:19) durch die vier heutigen Routen ersetzen (vorlage, feed, buchungen, aktion) + Test „unbekanntes POST = 403“; neue TP2-Routen bleiben unter /api/funktionen kundenansichtGuard.js
4 Mail-Fairness: Kontingent je Feature (z. B. 40/h unter globalen 200/h), Vorrang G01/G02/G08 vor G05/K20/K21, veraltete G01 (älter als Haltezeit + 1 min) verwerfen statt toter Link, Empfänger-Kappe über Betriebe (z. B. 3/h, 10/Tag je Adresse für G01 und Freitext-G04/G06), Frühausstieg in verarbeite bei Pause/leerem Budget und begrenzte Liste (orderAsc('naechsterVersuch'), limit, select; Query-Helfer und appwriteMock.js:145 um select/isNull/notEqual erweitern); HALTUNGEN_JE_SITE 60 → ~25/h outbox.js, limits.js, appwriteAdmin.js
5 Sichtbarkeit: Warnlog stündlich ab 70 % Budget, bei ältestem wartet > 10 min, bei SMTP-Pause (pausiertBis() ist ungenutzt), bei Tagesjob-Fehlern; GET /api/team/termine/status (Budget, Queue, Alter, Pause, letzter Tagesjob) und ein Staff-Mail je Vorfall jobs.js, neuer Team-Route
6 disable() idempotent (Abschnitt 6) und activateClosedTickets verallgemeinert siteFeatures.js
7 Messprüfungen mit Freigabe: Seiten-Listing > 500 Dokumente auf der Live-Appwrite 1.8.1 (bisher nur Mock/Spike), tatsächliche Hetzner-Antwort bei > 500 Mails/h, tatsächliche Größe workorders.details siehe Abschnitt 9

SOLLTE (TP2 oder kurz danach): minutenJob in zwei unabhängig geschützte Jobs (Verfall, Mailversand) teilen; Transport pool:true, Parallelität der einreihen-Sendungen begrenzen (mailer.js:25); Budget nach Neustart aus der Outbox rekonstruieren; /tage und /zeiten außerhalb des Horizonts vor imZeitraum kurzschließen (publicTermine.js:450,471); Index termineOutbox(bookingId); Hetzner-Überlimit-Antworten als Pause statt dauerhaft fehler einstufen (outbox.js:82-88); mailSicher schreibt bei Fehlschlag einen fehler-Marker, damit das Portal „nicht zugestellt“ zeigt; Tagesjob zu fester Nachtzeit (heute 180 s nach jedem Start, index.js:220); optional TERMINE_TOKEN_SECRET mit Fallback SESSION_SECRET; engines >=20; axe-/Screenreader-Lauf von Widget und Gästeseite; CSV-Export und loescheAbgeschaltete seitenweise für sehr große Einzelbetriebe. Kann warten: Lock-Wartezeiten, halte ohne termine-cfg-Lock, Fetch-Timeouts, Widget-Monolith.

Mailweg (D8): Terminplaner-Mails gehen weiter über den Hetzner-SMTP, mit Kontingent je Betrieb und Alarm (Punkt #4/#5). Ab 25 aktiven Terminplaner-Betrieben (Zählung im Tagesbericht; Warnlog und Team-Mail ab 20) wird der Umstieg auf einen EU-Versanddienst geplant (eigenes kleines Vorhaben: Anbieterwahl, AVV/6c-Nachtrag, DKIM/SPF).

Betriebsannahme: Ein Portal-Prozess (Locks, Limiter, Mailbudget liegen im Speicher; Zellen bleiben durch atomares Bulk-Create sicher). Jeder zweite Prozess bricht das; im Livegang-Plan festhalten.


9. Livegang TP2 (je Schritt Freigabe des Inhabers)

  1. Rechtsrunde: Texte (Abschnitt 7.1) und Kanzlei-Paket (7.2) fertigstellen; der Inhaber beauftragt die Kanzlei; Hausrunde für den Rest. Kein Push auf main durch Claude, das macht der Inhaber.
  2. Code in Worktrees (Portal, WOMS), TDD je Block: Register + Checkout, Fragebogen, Anleitung/WOMS, Abo-Kopplung, TP1-Schuld (Abschnitt 8). Test-Pins aus den Berichten mit ändern (funktionenRoutes, termineRoutes, termineSetup, termineView, kundenansichtGuard, legalTexts, mailTemplates, termineJobs).
  3. Schema in Produktions-Appwrite (Schreibzugriff, Freigabe): scripts/termine-setup.mjs --check, dann anwenden (termineConfig.fragebogen, Index termineBookings(status,endsAt), termineOutbox(bookingId)); setup-abo-collections.mjs (portalFeatures.disabledReason, aboEndeMailAt, neue Collection featureConsents). Vor dem Code-Deploy (Indexaufbau ist asynchron, Abfragen funktionieren auch ohne).
  4. Stripe (Aufruf, Freigabe): stripe-products.mjs --pruefen, dann --anlegen für termineSetup; Inhaber prüft im Dashboard, dass der Live-Webhook async_payment_succeeded/failed und expired abonniert hat (Annahme: ja, News läuft seit 2026-09-30).
  5. Messprüfungen (Freigabe): details-Größe, Seitenlisting > 500, Hetzner-Mailantwort (Abschnitt 8 #7); Hetzner-AV-Prüfung durch den Inhaber (7.2).
  6. Deploy Portal + WOMS (Inhaber pusht): Modus bleibt pilot mit leerem TERMINE_PILOT_EMAILS plus Pilotkunde „test“; Abo-Abgleich läuft zunächst nur beobachtend (Zählung im Tagesbericht, Pilotkunde ausgenommen).
  7. Rechtstexte live: PDFs bauen, HTML/PDF unter /rechtliches, 6c + PDF-Datenschutz, WOMS-Karten (Skript mit API-Key, Freigabe) und Uploads durch den Inhaber, Gästetexte final (RECHTSTEXTE_VERSION).
  8. Ende-zu-Ende-Test mit einem Testkunden und echtem Wartungs-Abo: Fragebogen → Bestellung (echte 149 €, Rückerstattung durch den Inhaber danach; Annahme, es gibt kein Test-Konto für diesen Preis) → Ticket + Anleitung → „Freischalten (Abnahme)“ → Probebuchung → Abo-Ende-Simulation (Testkunde) → K22 → Rückkehr K23 → Löschlauf auf Wegwerfdaten.
  9. Erster fremder Kunde im Pilot (erst nach dem Kanzleibefund, D5): nur seine Adresse in TERMINE_PILOT_EMAILS; Beobachtung Tagesbericht, Mailbudget, Status-Route (Freigabe der Adresse).
  10. TERMINE_ENABLED=on (Compose-Änderung, nur kundenbereich-server neu erstellen) als letzter Schritt; danach 1 Woche Beobachtung, News-Altfälle (D7) und Entwurfs-Löschjob prüfen.

9b. Umsetzung in einem Plan, sechs Blöcke

A Technik-Voraussetzungen aus TP1 (Abschnitt 8, MUSS-Liste) · B Register + Checkout + Zustimmungsnachweis (4) · C Fragebogen (3) · D Ticket, Einbauanleitung, WOMS (5) · E Abo-Kopplung (6) · F Rechtstexte + Kanzlei-Paket + News-Korrektur (7). Reihenfolge A → B → E → C → D → F (F kann parallel zu C/D laufen, da überwiegend Text). Jeder Block endet mit grünen Tests; der Livegang (Abschnitt 9) beginnt erst nach F.

10. Entscheidungen des Inhabers (2026-10-03)

# Frage Entscheidung
D1 Verbraucher oder Unternehmer bei der 149-€-Bestellung Alle wie Verbraucher: Haken „Beginn vor Fristablauf, Erlöschen, Wertersatz“, volle Belehrung + Muster-Formular für alle (4.3)
D2 Abo endet und kommt wieder Innerhalb von 30 Tagen automatisch und gratis wieder an; nach der Löschung neuer Fragebogen und neue 149 € (Abschnitt 6)
D3 Erstattung der 149 € Gesetzlicher Widerruf (mit Wertersatz für begonnenen Einbau) und volle Erstattung, wenn das Abo vor der Fertigstellung endet (4.6)
D4 Umfang der 149 € Je Website-Projekt ein Widget an einer Stelle, Gestaltung nach Fragebogen, Einbau; Mehrwünsche nach Stunden (Abschnitt 1)
D5 Rechtsprüfung Kanzlei für das Kernpaket, Rest Hausrunde; erster fremder Betrieb erst nach dem Befund (7.2)
D6 Gesundheits-Ausschluss Branchenfrage + Haken + Stichwort-Warnung ans Team; Team kann ablehnen (3.5)
D7 News-Altkunden nach Textkorrektur Nach dem Kanzleibefund entscheiden; Belehrung nur nachholen, wenn die Frist nicht lief (7.3)
D8 Mailweg bei mehr Betrieben Hetzner-SMTP mit Kontingent und Alarm, Umstieg auf EU-Versanddienst ab 25 aktiven Betrieben (Abschnitt 8)

11. TP3 (Ausblick, eigener Plan nach TP2)

Online-Zahlung der Gäste über Stripe Connect (Standard-ähnliches Konto, direkte Zahlungen, Hosted Checkout, eigener Connect-Webhook, Erstattungen nach Regel des Betriebs; PayPal nicht möglich, SEPA ausgeschlossen; Haltezeit ≥ 32 min). Voraussetzung: Connect-Plattform im Stripe-Live-Modus eingerichtet (bisher nur Sandbox, Konto acct_1UM7AVBONHfkVuDM).