From 459efff10a57a112fc81a19c70e325c252f88592 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 17:05:34 +0000 Subject: [PATCH] Terminplaner: Rechtstext-Bausteine fuer Gaeste (Entwurf) Co-Authored-By: Claude Sonnet 5.5 --- .../termine-datenschutz-6c-entwurf.md | 35 +++ .../rechtliches/termine-datenschutz-absatz.md | 83 ++++++ docs/rechtliches/termine-hinweise.md | 83 ++++++ .../termine-widerrufsbelehrung-muster.md | 73 +++++ server/services/termine/rechtstexte.js | 157 ++++++++++ test/termineRechtstexte.test.js | 272 ++++++++++++++++++ 6 files changed, 703 insertions(+) create mode 100644 docs/rechtliches/termine-datenschutz-6c-entwurf.md create mode 100644 docs/rechtliches/termine-datenschutz-absatz.md create mode 100644 docs/rechtliches/termine-hinweise.md create mode 100644 docs/rechtliches/termine-widerrufsbelehrung-muster.md create mode 100644 server/services/termine/rechtstexte.js create mode 100644 test/termineRechtstexte.test.js diff --git a/docs/rechtliches/termine-datenschutz-6c-entwurf.md b/docs/rechtliches/termine-datenschutz-6c-entwurf.md new file mode 100644 index 0000000..3c685ea --- /dev/null +++ b/docs/rechtliches/termine-datenschutz-6c-entwurf.md @@ -0,0 +1,35 @@ +ENTWURF – nur Pilot, vor Einsatz bei fremden Betrieben prüfen + +# Datenschutzerklärung von WEBklar: Abschnitt 6c „Online-Terminbuchung auf Websites unserer Kunden“ + +Fassung 2026-10-02. Entwurf für `public/datenschutz.html`, **nicht eingebaut und nicht veröffentlicht**: Die Datei `public/datenschutz.html` bleibt bis TP2 unverändert (dort: Livegang des Terminplaners, Prüfrunde wie bei der Website-Analyse). Der Abschnitt beschreibt, was WEBklar selbst verarbeitet: als Auftragsverarbeiter für die Gäste der Betriebe und als Verantwortlicher für die Betriebsdaten im Kundenportal. Den Text für die Website des Betriebs enthält `termine-datenschutz-absatz.md`. + +## Einbau in TP2 (Checkliste) + +1. Abschnitt als `

` nach 6b (Website-Analyse) und vor 7 einfügen; Stil wie 6a und 6b. +2. Fassung (Stand) oben auf der Seite und in den Vertragsunterlagen nachziehen; `docs/WEBklar-Datenschutzerklaerung.pdf` neu erzeugen und in WOMS „Rechtliches“ hochladen (der Nutzer lädt die Fassung hoch). +3. Abschnitt 12 (Empfänger) nennt Hetzner bereits; keine neue Zeile nötig. Abschnitt 13 („Derzeit stützen wir keine Verarbeitung auf eine Einwilligung“) bleibt richtig, weil die Einwilligung zur Erinnerung beim Betrieb liegt. +4. Die Kachel und die Leistungsbeschreibung „Terminplaner“ sowie der AVV „Terminplaner“ (Geschwister des AVV Website-Analyse) müssen vor dem Livegang vorliegen; dieser Abschnitt verweist auf sie nicht, weil die Datenschutzerklärung keine Vertragsunterlagen zitiert. +5. Test `test/termineRechtstexte.test.js` (Absicherung „6c nicht in datenschutz.html“) in TP2 entfernen. + +## Abschnitt (HTML) + +```html +

6c. Online-Terminbuchung auf Websites unserer Kunden

+ +

Sobald diese Funktion freigeschaltet ist, können Kunden mit Wartungs-Abo auf ihrer eigenen Website eine Online-Terminbuchung anbieten („Terminplaner“). Dabei verarbeiten wir die Daten der Personen, die dort einen Termin buchen („Gäste“), ausschließlich als Auftragsverarbeiter nach den Weisungen des jeweiligen Kunden, der dafür Verantwortlicher ist. Wir verwenden diese Daten nicht für eigene Zwecke. Diese Datenschutzerklärung gilt für unsere eigenen Seiten und das Kundenportal. Wer auf der Website eines Kunden einen Termin bucht, findet die Informationen zur Buchung (Zwecke, Rechtsgrundlagen, Speicherdauer, Rechte) in der Datenschutzerklärung dieser Website. Anfragen von Gästen zu ihren Daten leiten wir an den Kunden weiter.

+

Für den Betrieb des Terminplaners speichern wir im Auftrag des Kunden: Name, E-Mail-Adresse und, wenn der Kunde diese Felder eingeschaltet hat, Telefonnummer, Nachricht und weitere Angaben des Gastes; die gewählte Leistung, Person oder den gewählten Platz und den Termin; den Stand der Buchung (zum Beispiel bestätigt, abgesagt oder widerrufen) mit den Zeitpunkten; als Nachweis den Zeitpunkt der Bestätigung und der Zustimmungen sowie die Fassung der angezeigten Hinweise; gegebenenfalls den Wunsch nach einer Erinnerung. Der Terminplaner ist nicht für Leistungen der Heilkunde und der Gesundheitsversorgung bestimmt; Gäste werden gebeten, keine Gesundheitsangaben zu machen.

+

Der Kunde sieht, ändert und löscht die Termine im Kundenportal. Dort verarbeiten wir als Verantwortliche seine Einstellungen für den Terminplaner (Betriebsangaben, Leistungen, Namen und Zeiten der gebuchten Personen oder Plätze, Öffnungszeiten, Absagefristen und Texte), wie die übrigen Portal-Daten (Abschnitt 6) auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO. Für jede Person oder jeden Platz kann der Kunde einen geheimen Link erzeugen, mit dem er die bestätigten Termine in seinem Kalenderprogramm abonniert; wer den Link kennt, sieht diese Termine. Der Kunde kann den Link jederzeit durch einen neuen ersetzen.

+

Im Namen des Kunden senden wir E-Mails an Gäste (Bestätigung der E-Mail-Adresse, Bestätigung, Änderung oder Absage eines Termins, Eingangsbestätigung eines Widerrufs und, wenn der Gast das gewählt hat, eine Erinnerung) und an den Kunden (neue Buchung, Absage, Widerruf). Der Versand läuft über den E-Mail-Dienst der Hetzner Online GmbH (Abschnitt 8); als Absender erscheint der Kunde, Antworten gehen an ihn. Wir verschicken keine Werbung, setzen keine Tracking-Pixel ein und erhalten keine Kopie. Bis zum Versand liegt jede E-Mail in einer Warteschlange. Nach dem Versand löschen wir dort Empfängeradresse und Inhalt, den Eintrag selbst nach sieben Tagen; E-Mails, die nicht zugestellt werden konnten, löschen wir nach 48 Stunden.

+

Beim Aufruf einer Kundenwebsite mit Terminbuchung lädt der Browser das Programm der Terminbuchung von unserem Server (project.webklar.com), beim Öffnen der Terminbuchung zusätzlich die Termine und Texte. Dabei gelten die Protokolle in Abschnitt 3. Die Terminbuchung setzt keine Cookies und speichert nichts im Browser des Gastes. Zum Schutz vor Missbrauch und um die Zahl der Anfragen zu begrenzen (höchstens zehn Buchungen je zehn Minuten und je IP-Adresse), merken wir uns die Zeitpunkte der Aufrufe je IP-Adresse nur im Arbeitsspeicher des Servers, nicht dauerhaft; mit der Buchung speichern wir die IP-Adresse nicht. Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO; unser berechtigtes Interesse ist der sichere und störungsfreie Betrieb.

+

Löschfristen: Nicht bestätigte Buchungen löschen wir 48 Stunden nach Ablauf der Bestätigungsfrist von 30 Minuten, abgesagte oder abgelehnte Termine nach 30 Tagen, bestätigte Termine nach der Frist, die der Kunde eingestellt hat (Vorgabe 90 Tage, einstellbar von 14 bis 365 Tagen nach dem Termin). Endet das Wartungs-Abo oder schaltet der Kunde die Funktion ab, löschen wir alle Termindaten des Kunden 30 Tage danach. Die Daten liegen auf unserem Server bei Hetzner im Rechenzentrum Falkenstein (Deutschland), wie die übrigen Portal-Daten (Abschnitt 3).

+``` + +## Für die Rechtsprüfung offen + +1. **Rolle**: WEBklar als Auftragsverarbeiter für die Gäste, als Verantwortliche für die Betriebsdaten und die Missbrauchsabwehr (IP im Arbeitsspeicher). Wie bei 6b anwaltlich bestätigen lassen. +2. **IP-Adresse im Arbeitsspeicher**: Der Satz „mit der Buchung speichern wir die IP-Adresse nicht“ muss zur fertigen Buchungsroute passen (Feld `nachweis` ohne IP, kein IP-Log); beim Livegang gegen den Code prüfen. +3. **Kalender-Abo-Link**: Der Link enthält nur ein geheimes Token je Person oder Platz und liefert nur bestätigte Termine der nächsten 90 Tage. Prüfen, ob der Kunde seine Beschäftigten darüber informieren muss (Beschäftigtendaten: Namen und Zeiten). +4. **Sicherungen**: Die Datensicherung bei Hetzner behält gelöschte Daten noch einige Zeit; die Frist steht nicht im Text und ist vor dem Livegang zu klären (AVV Terminplaner Nr. 9). +5. **Zahlung (TP3)**: Wenn Online-Zahlung kommt, braucht dieser Abschnitt einen Satz zu Stripe (Abschnitt 7 und `recherche-recht-zahlung.md` 7.3 G). +6. **Geschäftsbriefe**: Bestätigungen mit Preis sind keine Rechnungen; die Aufbewahrung nach Abschnitt 9 betrifft den Terminplaner nicht. diff --git a/docs/rechtliches/termine-datenschutz-absatz.md b/docs/rechtliches/termine-datenschutz-absatz.md new file mode 100644 index 0000000..0600384 --- /dev/null +++ b/docs/rechtliches/termine-datenschutz-absatz.md @@ -0,0 +1,83 @@ +ENTWURF – nur Pilot, vor Einsatz bei fremden Betrieben prüfen + +# Datenschutz-Absatz „Online-Terminbuchung“ für die Website des Betriebs + +Fassung 2026-10-02. Grundlage: `recherche-recht.md` Abschnitt 11.1. Der Absatz gehört in die Datenschutzerklärung der Website des **Betriebs** (der Betrieb ist Verantwortlicher, WEBklar Auftragsverarbeiter). Die Einbauanleitung für Mitarbeiter (`docs/funktionen/termine-einbau.md`, TP2) verweist hierher. Für die Datenschutzerklärung von WEBklar selbst gilt `termine-datenschutz-6c-entwurf.md`. + +Zwei Fassungen weiter unten: Sie und Du. Nimm die Fassung, die zur Datenschutzerklärung der Website passt (in der Regel zur `anrede` des Betriebs). + +## Platzhalter und optionale Sätze + +- `{{Firma}}`, `{{Anschrift}}`, `{{E-Mail}}`, `{{Telefon}}`: Betriebsangaben aus der Einstellung. `{{Tage}}`: Aufbewahrung nach dem Termin (`aufbewahrungTage`, Vorgabe 90, einstellbar 14 bis 365). +- `[[wenn …: …]]`: Satz oder Satzteil nur aufnehmen, wenn die genannte Einstellung aktiv ist; sonst samt Klammern streichen. + +## Löschfristen (Vorgaben des Löschlaufs, im Text unten verwendet) + +| Daten | Frist | +|---|---| +| Nicht bestätigte Buchung (Status „verfallen“) | 48 Stunden nach Ablauf der Bestätigungsfrist (30 Minuten) | +| Abgesagter oder abgelehnter Termin | 30 Tage | +| Bestätigter, erledigter oder nicht wahrgenommener Termin | `aufbewahrungTage` (Vorgabe 90) nach dem Ende des Termins | +| Terminanfrage, deren Beginn verstrichen ist | gilt als abgelehnt (durch das System), dann 30 Tage | +| Funktion abgeschaltet (zum Beispiel Wartungs-Abo beendet) | alle Termindaten 30 Tage nach dem Abschalten | +| Versandwarteschlange für E-Mails | Empfängeradresse und Inhalt werden nach dem Versand geleert, der Eintrag nach 7 Tagen gelöscht; nicht zustellbare Einträge nach 48 Stunden | + +## Abweichungen von Abschnitt 11.1 der Recherche + +1. **Server-Protokolle**: 11.1 sagt, IP-Adresse und Browserangaben würden „nicht gespeichert“. Der Server von WEBklar protokolliert sie aber kurzzeitig (Datenschutzerklärung von WEBklar Abschnitt 3: 14 Tage). Der Absatz sagt das jetzt offen. +2. **Rechtsgrundlage**: Bei einer sofort verbindlichen Buchung kommt der Vertrag mit der Bestätigung zustande; „vorvertragliche Maßnahmen“ passt nur für Terminanfragen. Beide Fälle stehen im Text. Neu: Widerruf über die Widerrufsfunktion (Art. 6 Abs. 1 lit. c DSGVO, § 356a BGB). +3. **Aufruf**: Das Skript der Terminbuchung wird beim Aufruf der Seite geladen, nicht erst beim Klick auf den Knopf; der Text sagt das. +4. **Nachweise**: Gespeichert werden auch der Zeitpunkt der Zustimmungen und die Fassung der angezeigten Texte (Spezifikation, Feld `nachweis`). +5. **Löschfristen** angepasst (siehe Tabelle); 11.1 kannte nur „nicht bestätigt 48 Stunden“, „abgesagt 30 Tage“ und „bestätigt {{90}} Tage“. + +## Für die Rechtsprüfung offen + +1. **Server-Protokolle**: Frist (14 Tage) und Rechtsgrundlage (Art. 6 Abs. 1 lit. f DSGVO) müssen zur Datenschutzerklärung von WEBklar passen; bei Änderung dort hier nachziehen. +2. **Drittländer**: „Eine Übermittlung in Drittländer findet nicht statt“ gilt nur, solange Server und E-Mail-Versand bei Hetzner in Deutschland bleiben (WEBklar-Datenschutzerklärung Abschnitte 3 und 8). +3. **Löschlauf**: „48 Stunden“ und „30 Tage“ sind die frühesten Zeitpunkte, an denen der Löschlauf löscht; er läuft in Abständen. +4. **Kontakt der Gäste**: Anfragen nach Auskunft oder Löschung gehen an den Betrieb; WEBklar leitet Anfragen, die bei ihr eingehen, als Auftragsverarbeiter weiter (in der Datenschutzerklärung von WEBklar, 6c). +5. **Gesundheitsbereich**: Der Terminplaner ist dafür nicht bestimmt; der Satz „Bitte keine Angaben zur Gesundheit“ steht auch im Formular. + +## Fassung Sie + +> ### Online-Terminbuchung +> +> Auf unserer Website können Sie Termine online buchen. Dafür verarbeiten wir die folgenden Daten: +> +> **Welche Daten?** Ihren Namen, Ihre E-Mail-Adresse, den gewählten Termin, die gewählte Leistung und die gewählte Person oder den gewählten Platz[[wenn Telefon-Feld aktiv: , auf Wunsch Ihre Telefonnummer]][[wenn Nachricht oder Zusatzfelder aktiv: und die Angaben, die Sie uns im Formular mitteilen]]. Außerdem speichern wir, wann Sie den Termin bestätigt haben, welche Fassung der Hinweise Sie bei der Buchung gesehen haben und welche Zustimmungen Sie erteilt haben[[wenn Erinnerung angeboten: , zum Beispiel ob Sie eine Erinnerung gewünscht haben]]. +> +> **Wofür und auf welcher Grundlage?** Wir brauchen die Daten, um Ihren Termin zu verwalten, Ihnen den Termin zu bestätigen und Sie über Änderungen oder Absagen zu informieren. Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO (Vertrag über den gebuchten Termin, bei einer Terminanfrage vorvertragliche Maßnahmen auf Ihre Anfrage). [[wenn Erinnerung angeboten: Wenn Sie eine Erinnerung vor dem Termin wünschen, beruht diese auf Ihrer Einwilligung nach Art. 6 Abs. 1 lit. a DSGVO. Sie können sie jederzeit mit Wirkung für die Zukunft widerrufen, indem Sie uns eine kurze Nachricht senden oder den Termin über den Link in der E-Mail absagen.]] [[wenn Gäste Verbraucher sein können: Widerrufen Sie einen Vertrag über die Schaltfläche „Vertrag widerrufen“, speichern wir Ihre Erklärung mit Datum und Uhrzeit und senden Ihnen eine Eingangsbestätigung (Art. 6 Abs. 1 lit. c DSGVO in Verbindung mit § 356a BGB).]] Zum Schutz vor Missbrauch (zum Beispiel automatisierten Buchungen) und um die Zahl der Anfragen zu begrenzen, merken wir uns Ihre IP-Adresse kurzzeitig nur im Arbeitsspeicher des Servers, nicht dauerhaft (Art. 6 Abs. 1 lit. f DSGVO; unser berechtigtes Interesse ist der sichere Betrieb der Terminbuchung). +> +> **E-Mails.** Sie erhalten eine E-Mail, in der Sie Ihre E-Mail-Adresse und den Termin per Link bestätigen, danach eine Bestätigung[[wenn Erinnerung angeboten: und, wenn Sie das angekreuzt haben, eine Erinnerung]]. Sagen wir den Termin ab oder verschieben ihn, informieren wir Sie ebenfalls per E-Mail. Die E-Mails enthalten keine Werbung und keine Tracking-Pixel. Absender ist {{Firma}}, Ihre Antworten gehen an uns. Bis zum Versand liegt die E-Mail in einer Warteschlange; nach dem Versand wird dort die Empfängeradresse samt Inhalt gelöscht, nicht zustellbare E-Mails nach 48 Stunden. +> +> **Wer verarbeitet die Daten in unserem Auftrag?** Den Terminkalender betreibt die WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler, als unser Auftragsverarbeiter nach Art. 28 DSGVO. Die Daten liegen auf Servern der Hetzner Online GmbH, Industriestraße 25, 91710 Gunzenhausen, in Deutschland (Rechenzentren Falkenstein und Nürnberg). Der E-Mail-Versand läuft ebenfalls über Hetzner. Eine Übermittlung in Drittländer findet nicht statt. +> +> **Beim Aufruf der Terminbuchung.** Beim Aufruf dieser Seite lädt Ihr Browser das Programm der Terminbuchung vom Server der WEBklar GbR (project.webklar.com), beim Öffnen der Terminbuchung zusätzlich die Termine und Texte. Dabei werden technisch bedingt Ihre IP-Adresse und Angaben zu Ihrem Browser übertragen. Der Server speichert sie kurzzeitig in Protokollen und löscht sie nach 14 Tagen (Art. 6 Abs. 1 lit. f DSGVO; berechtigtes Interesse ist der sichere und funktionsfähige Betrieb). Wir setzen dafür keine Cookies und speichern nichts auf Ihrem Gerät. +> +> **Wie lange speichern wir?** Nicht bestätigte Buchungen (der Bestätigungslink gilt 30 Minuten) löschen wir 48 Stunden nach Ablauf dieser Frist. Bestätigte Termine löschen wir {{Tage}} Tage nach dem Termin, abgesagte oder abgelehnte Termine 30 Tage nach der Absage oder Ablehnung. Terminanfragen, die wir bis zum Terminbeginn nicht beantworten, behandeln wir als abgelehnt. Wird die Online-Terminbuchung abgeschaltet, löschen wir alle Termindaten 30 Tage nach dem Abschalten. Gesetzliche Aufbewahrungspflichten für Geschäftsunterlagen (zum Beispiel Rechnungen) bleiben davon unberührt; sie erfüllen wir außerhalb des Terminkalenders. +> +> **Pflicht zur Angabe.** Ohne Name, E-Mail-Adresse und Terminwahl können wir keinen Termin online vergeben. [[wenn Telefon hinterlegt: Sie können uns auch telefonisch erreichen: {{Telefon}}.]] Eine automatisierte Entscheidungsfindung findet nicht statt. Bitte machen Sie bei der Buchung keine Angaben zu Ihrer Gesundheit. +> +> **Ihre Rechte.** Sie haben das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung und Datenübertragbarkeit sowie das Recht, der Verarbeitung auf Grundlage berechtigter Interessen zu widersprechen (Art. 15 bis 21 DSGVO). Eine erteilte Einwilligung können Sie jederzeit widerrufen. Sie können sich bei einer Datenschutzaufsichtsbehörde beschweren. Verantwortlich ist {{Firma}}, {{Anschrift}}, E-Mail {{E-Mail}}. + +## Fassung Du + +> ### Online-Terminbuchung +> +> Auf unserer Website kannst du Termine online buchen. Dafür verarbeiten wir die folgenden Daten: +> +> **Welche Daten?** Deinen Namen, deine E-Mail-Adresse, den gewählten Termin, die gewählte Leistung und die gewählte Person oder den gewählten Platz[[wenn Telefon-Feld aktiv: , auf Wunsch deine Telefonnummer]][[wenn Nachricht oder Zusatzfelder aktiv: und die Angaben, die du uns im Formular mitteilst]]. Außerdem speichern wir, wann du den Termin bestätigt hast, welche Fassung der Hinweise du bei der Buchung gesehen hast und welche Zustimmungen du erteilt hast[[wenn Erinnerung angeboten: , zum Beispiel ob du eine Erinnerung gewünscht hast]]. +> +> **Wofür und auf welcher Grundlage?** Wir brauchen die Daten, um deinen Termin zu verwalten, dir den Termin zu bestätigen und dich über Änderungen oder Absagen zu informieren. Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO (Vertrag über den gebuchten Termin, bei einer Terminanfrage vorvertragliche Maßnahmen auf deine Anfrage). [[wenn Erinnerung angeboten: Wenn du eine Erinnerung vor dem Termin wünschst, beruht diese auf deiner Einwilligung nach Art. 6 Abs. 1 lit. a DSGVO. Du kannst sie jederzeit mit Wirkung für die Zukunft widerrufen, indem du uns eine kurze Nachricht sendest oder den Termin über den Link in der E-Mail absagst.]] [[wenn Gäste Verbraucher sein können: Widerrufst du einen Vertrag über die Schaltfläche „Vertrag widerrufen“, speichern wir deine Erklärung mit Datum und Uhrzeit und senden dir eine Eingangsbestätigung (Art. 6 Abs. 1 lit. c DSGVO in Verbindung mit § 356a BGB).]] Zum Schutz vor Missbrauch (zum Beispiel automatisierten Buchungen) und um die Zahl der Anfragen zu begrenzen, merken wir uns deine IP-Adresse kurzzeitig nur im Arbeitsspeicher des Servers, nicht dauerhaft (Art. 6 Abs. 1 lit. f DSGVO; unser berechtigtes Interesse ist der sichere Betrieb der Terminbuchung). +> +> **E-Mails.** Du erhältst eine E-Mail, in der du deine E-Mail-Adresse und den Termin per Link bestätigst, danach eine Bestätigung[[wenn Erinnerung angeboten: und, wenn du das angekreuzt hast, eine Erinnerung]]. Sagen wir den Termin ab oder verschieben ihn, informieren wir dich ebenfalls per E-Mail. Die E-Mails enthalten keine Werbung und keine Tracking-Pixel. Absender ist {{Firma}}, deine Antworten gehen an uns. Bis zum Versand liegt die E-Mail in einer Warteschlange; nach dem Versand wird dort die Empfängeradresse samt Inhalt gelöscht, nicht zustellbare E-Mails nach 48 Stunden. +> +> **Wer verarbeitet die Daten in unserem Auftrag?** Den Terminkalender betreibt die WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler, als unser Auftragsverarbeiter nach Art. 28 DSGVO. Die Daten liegen auf Servern der Hetzner Online GmbH, Industriestraße 25, 91710 Gunzenhausen, in Deutschland (Rechenzentren Falkenstein und Nürnberg). Der E-Mail-Versand läuft ebenfalls über Hetzner. Eine Übermittlung in Drittländer findet nicht statt. +> +> **Beim Aufruf der Terminbuchung.** Beim Aufruf dieser Seite lädt dein Browser das Programm der Terminbuchung vom Server der WEBklar GbR (project.webklar.com), beim Öffnen der Terminbuchung zusätzlich die Termine und Texte. Dabei werden technisch bedingt deine IP-Adresse und Angaben zu deinem Browser übertragen. Der Server speichert sie kurzzeitig in Protokollen und löscht sie nach 14 Tagen (Art. 6 Abs. 1 lit. f DSGVO; berechtigtes Interesse ist der sichere und funktionsfähige Betrieb). Wir setzen dafür keine Cookies und speichern nichts auf deinem Gerät. +> +> **Wie lange speichern wir?** Nicht bestätigte Buchungen (der Bestätigungslink gilt 30 Minuten) löschen wir 48 Stunden nach Ablauf dieser Frist. Bestätigte Termine löschen wir {{Tage}} Tage nach dem Termin, abgesagte oder abgelehnte Termine 30 Tage nach der Absage oder Ablehnung. Terminanfragen, die wir bis zum Terminbeginn nicht beantworten, behandeln wir als abgelehnt. Wird die Online-Terminbuchung abgeschaltet, löschen wir alle Termindaten 30 Tage nach dem Abschalten. Gesetzliche Aufbewahrungspflichten für Geschäftsunterlagen (zum Beispiel Rechnungen) bleiben davon unberührt; sie erfüllen wir außerhalb des Terminkalenders. +> +> **Pflicht zur Angabe.** Ohne Name, E-Mail-Adresse und Terminwahl können wir keinen Termin online vergeben. [[wenn Telefon hinterlegt: Du kannst uns auch telefonisch erreichen: {{Telefon}}.]] Eine automatisierte Entscheidungsfindung findet nicht statt. Bitte mach bei der Buchung keine Angaben zu deiner Gesundheit. +> +> **Deine Rechte.** Du hast das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung und Datenübertragbarkeit sowie das Recht, der Verarbeitung auf Grundlage berechtigter Interessen zu widersprechen (Art. 15 bis 21 DSGVO). Eine erteilte Einwilligung kannst du jederzeit widerrufen. Du kannst dich bei einer Datenschutzaufsichtsbehörde beschweren. Verantwortlich ist {{Firma}}, {{Anschrift}}, E-Mail {{E-Mail}}. diff --git a/docs/rechtliches/termine-hinweise.md b/docs/rechtliches/termine-hinweise.md new file mode 100644 index 0000000..7aefe56 --- /dev/null +++ b/docs/rechtliches/termine-hinweise.md @@ -0,0 +1,83 @@ +ENTWURF – nur Pilot, vor Einsatz bei fremden Betrieben prüfen + +# Hinweise, Haken und Knopftexte für Gäste (Terminplaner) + +Fassung 2026-10-02. Grundlage: `recherche-recht.md` Abschnitt 11.2 (Hinweis unter dem Formular) und 11.3, `recherche-recht-zahlung.md` Abschnitt 7.3 C bis E (Haken, Widerrufshinweis, Widerrufsfunktion). Die Texte in den Abschnitten 1 bis 3 sind die Fassung aus `server/services/termine/rechtstexte.js` (Platzhalter in doppelten geschweiften Klammern); `test/termineRechtstexte.test.js` prüft, dass sie hier wörtlich stehen. Ändere sie deshalb nur in `rechtstexte.js` und übernimm sie hierher. Die Abschnitte 4 bis 6 sind reine Entwurfstexte für das Widget und die Mails; sie stehen nicht im Code. + +Platzhalter: `{{Firma}}` = Betriebsname, `{{Anschrift}}` = Anschrift des Betriebs, `{{n}}` = Stunden vor dem Termin (`erinnerung.stundenVorher`: 2, 12, 24 oder 48). Die Texte zeigen die Vorgabe von 90 Tagen Aufbewahrung nach dem Termin; der Wert kommt aus `aufbewahrungTage` (14 bis 365) der Einstellung. + +## 1. Datenschutz-Hinweis unter dem Formular (Kurzfassung nach Art. 13 DSGVO) + +Steht in der Zusammenfassung vor dem Absenden. Sie- oder Du-Fassung nach `anrede` des Betriebs. Neben dem Text zeigt das Widget einen Link auf `betrieb.datenschutzUrl` (die Datenschutzerklärung des Betriebs); die Betriebs-E-Mail steht bewusst nicht im Text. Es gibt keinen Haken dafür. + +Fassung Sie: + +```text +Datenschutz. Wir verwenden Ihre Angaben, um Ihren Termin zu verwalten und zu bestätigen (Art. 6 Abs. 1 lit. b DSGVO). Betrieben wird der Terminkalender in unserem Auftrag von der WEBklar GbR (Server: Hetzner, Deutschland). Wir speichern Ihre Daten bis 90 Tage nach dem Termin, abgesagte Termine 30 Tage, nicht bestätigte Buchungen 48 Stunden nach Ablauf der Bestätigungsfrist. Es werden keine Cookies gesetzt. Bitte machen Sie keine Angaben zu Ihrer Gesundheit. Mehr erfahren Sie in unserer Datenschutzerklärung. Verantwortlich: {{Firma}}, {{Anschrift}}. +``` + +Fassung Du: + +```text +Datenschutz. Wir verwenden deine Angaben, um deinen Termin zu verwalten und zu bestätigen (Art. 6 Abs. 1 lit. b DSGVO). Betrieben wird der Terminkalender in unserem Auftrag von der WEBklar GbR (Server: Hetzner, Deutschland). Wir speichern deine Daten bis 90 Tage nach dem Termin, abgesagte Termine 30 Tage, nicht bestätigte Buchungen 48 Stunden nach Ablauf der Bestätigungsfrist. Es werden keine Cookies gesetzt. Bitte mach keine Angaben zu deiner Gesundheit. Mehr erfährst du in unserer Datenschutzerklärung. Verantwortlich: {{Firma}}, {{Anschrift}}. +``` + +Gegenüber Abschnitt 11.2 der Recherche: Die Löschfristen sind die des Löschlaufs (nicht bestätigte Buchungen 48 Stunden nach Ablauf der Bestätigungsfrist, abgesagte Termine 30 Tage, vergangene Termine `aufbewahrungTage` nach dem Termin) und der Verantwortliche nennt zusätzlich die Anschrift. + +## 2. Haken (Zustimmungen) + +Wortlaut immer in der Sie-Form, auch bei Betrieben, die ihre Gäste duzen: Es sind Rechtstexte. Keiner der Haken ist vorangekreuzt. + +| Haken | Wann | Pflicht | Wortlaut | +|---|---|---|---| +| Vorzeitiger Beginn | verbindlich, mit Preis, Verbraucher, Terminbeginn vor Ablauf von 14 Tagen nach der Buchung | ja | Ich verlange ausdrücklich, dass {{Firma}} mit der Leistung vor Ablauf der Widerrufsfrist beginnt. Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung der Leistung erlischt (§ 356 Abs. 5 Nr. 2 BGB). Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB). | +| Stornobedingungen | der Betrieb hat Stornobedingungen hinterlegt | ja | Ich habe die Stornobedingungen gelesen. | +| Erinnerung | der Betrieb bietet Erinnerungen an | nein | Ich möchte {{n}} Stunden vor dem Termin eine Erinnerung per E-Mail erhalten. | + +## 3. Hinweis über dem Nachrichtenfeld + +Bitte keine Gesundheitsangaben. + +Der Terminplaner ist nicht für den Gesundheitsbereich bestimmt (Spezifikation, Vorgaben). Der Hinweis steht über jedem Freitextfeld. + +## 4. Knopftexte (exakt) + +| Fall | Knopf im Widget | Knopf auf der Bestätigungsseite | +|---|---|---| +| verbindlich, mit Preis | Zahlungspflichtig buchen | Zahlungspflichtig bestätigen | +| verbindlich, ohne Preis | Termin buchen | Termin bestätigen | +| Anfrage | Termin anfragen | Anfrage absenden | +| Widerruf (Funktion) | Vertrag widerrufen | Widerruf bestätigen | + +## 5. Widerrufshinweis im Widget und in der Mail (Entwurf, Rahmen) + +Er ersetzt nicht die Widerrufsbelehrung (`termine-widerrufsbelehrung-muster.md`), sondern führt zu ihr. Gilt nur für verbindliche Buchungen von Verbrauchern. + +**Mit Widerrufsrecht** (Zusammenfassung vor dem Knopf und G02 vor der Belehrung): + +> **Widerrufsrecht.** Wenn Sie Verbraucher sind, können Sie diesen Vertrag innerhalb von 14 Tagen ab Vertragsschluss ohne Angabe von Gründen widerrufen. Dafür nutzen Sie die Schaltfläche „Vertrag widerrufen“ im Terminbereich auf unserer Website oder den Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail. Die ausführliche Widerrufsbelehrung und das Muster-Widerrufsformular stehen in Ihrer Bestätigungs-E-Mail. + +**Freizeit-Dienstleistung mit festem Termin** (§ 312g Abs. 2 Nr. 9 BGB; `recht.freizeit`), kein Widerrufsrecht, keine Belehrung, kein Formular, keine Funktion: + +> **Für diese Buchung besteht kein Widerrufsrecht.** Ihre Buchung ist verbindlich; Absagen richten sich nach den Stornobedingungen. + +Gäste, die nur Unternehmen sind (`recht.gaesteSind = unternehmer`): kein Hinweis. + +## 6. Widerrufsfunktion (Entwurf der Texte) + +- Schaltfläche oder Link: **Vertrag widerrufen** (im Terminbereich der Website, auf der Verwaltungsseite hinter „Termin verwalten“). +- Formular ohne Anmeldung: Name · Buchungsnummer · E-Mail-Adresse für die Eingangsbestätigung. Schaltfläche: **Widerruf bestätigen**. +- Eingangsbestätigung per E-Mail (G08; die Mailvorlage ist maßgeblich, hier nur der Kern. Die Mail enthält keine Eingaben des Gastes, nur die Buchungsnummer und den Eingang): + +> Betreff: Eingangsbestätigung Ihres Widerrufs +> +> Wir bestätigen den Eingang Ihres Widerrufs. Inhalt Ihrer Erklärung: Widerruf des Vertrags zur Buchung {{nummer}}. Eingegangen am {{eingang}} (Datum und Uhrzeit). {{Nur im Block „zugeordnet“: Ihr Termin ist abgesagt.}} Absender: {{Firma}}. + +Der Eingang muss mit Datum und Uhrzeit bestätigt werden (§ 356a BGB); das ist auch der Text der Belehrung („Eingangsbestätigung mit Informationen zum Inhalt der Widerrufserklärung sowie dem Datum und der Uhrzeit ihres Eingangs“). + +## Für die Rechtsprüfung und das Widget offen + +1. **Belehrung vor dem Vertragsschluss**: Das Muster verlangt die Information, bevor der Gast seine Erklärung abgibt (Art. 246a § 4 Abs. 1 EGBGB). Hier steht der Hinweis (Abschnitt 5) in der Zusammenfassung, die vollständige Belehrung in der Bestätigungs-E-Mail. Prüfen, ob die vollständige Belehrung (`widerrufsbelehrung()`) schon in der Zusammenfassung stehen muss; der Text ist dafür geeignet (reiner Text mit Zeilenumbrüchen). +2. **Widerruf der Einwilligung zur Erinnerung**: Der feste Wortlaut des Hakens (Abschnitt 2) enthält den Hinweis auf den Widerruf nicht; er steht im Datenschutz-Absatz (`termine-datenschutz-absatz.md`). Prüfen, ob ein kurzer Zusatz unter dem Haken nötig ist („Sie können diese Einwilligung jederzeit widerrufen.“). +3. **Link zur Datenschutzerklärung** neben dem Hinweis: `betrieb.datenschutzUrl` ist in der Einstellung optional. Ohne Link verweist der Hinweis auf eine Datenschutzerklärung, die der Gast nicht findet; das Portal sollte die Adresse vor dem Einschalten verlangen. +4. **Bestätigungsfrist**: Der Hinweis nennt „48 Stunden nach Ablauf der Bestätigungsfrist“. Der Löschlauf läuft in Abständen; „48 Stunden“ ist die früheste Löschung, nicht eine Sekundengarantie. diff --git a/docs/rechtliches/termine-widerrufsbelehrung-muster.md b/docs/rechtliches/termine-widerrufsbelehrung-muster.md new file mode 100644 index 0000000..fc49219 --- /dev/null +++ b/docs/rechtliches/termine-widerrufsbelehrung-muster.md @@ -0,0 +1,73 @@ +ENTWURF – nur Pilot, vor Einsatz bei fremden Betrieben prüfen + +# Widerrufsbelehrung und Muster-Widerrufsformular (Terminplaner, Dienstleistung) + +Fassung 2026-10-02. Quelle des Wortlauts: Anlage 1 und Anlage 2 zu Art. 246a § 1 Abs. 2 Satz 2 EGBGB (Fundstelle BGBl. I 2013, S. 3663 bis 3665), abgerufen am 2026-10-02 von gesetze-im-internet.de; die Textdateien liegen unverändert in `docs/rechtliches/quellen/anlage-1.txt` und `anlage-2.txt`. + +Der Text unten ist die Fassung, die `widerrufsbelehrung(betrieb)` in `server/services/termine/rechtstexte.js` erzeugt, mit Platzhaltern statt der Betriebsdaten. `test/termineRechtstexte.test.js` prüft, dass (a) jeder Absatz des Musters wörtlich aus den Quelldateien im Ergebnis steht und (b) der Textblock unten genau der Ausgabe der Funktion entspricht. Ändere den Text deshalb nur in `rechtstexte.js` und übernimm ihn hierher. + +## Wie das Muster für einen Termin (Dienstleistung) ausgefüllt ist + +| Gestaltungshinweis in Anlage 1 | Umsetzung | +|---|---| +| Frist: Textbaustein a) für Dienstleistungsverträge | „des Vertragsabschlusses.“ (Fristbeginn ist der Vertragsschluss: bei einer sofort verbindlichen Buchung die Bestätigung per Link, bei einer Anfrage die Annahme durch den Betrieb) | +| „Fügen Sie Ihren Namen, Ihre Anschrift, Ihre Telefonnummer und Ihre E-Mail-Adresse ein.“ | Name, Anschrift, Telefon, E-Mail aus den Betriebsangaben der Einstellung (`betrieb.name`, `adresse`, `telefon`, `email`); fehlen Anschrift oder Telefon, entfallen sie aus dem Text | +| Widerrufsfunktion bei online geschlossenen Verträgen (§ 356a BGB) | „anderer geeigneter Hinweis“ statt Internetadresse: Schaltfläche „Vertrag widerrufen“ im Terminbereich der Website und Link „Termin verwalten“ in der Bestätigungs-E-Mail; danach der Gesetzeswortlaut zur Eingangsbestätigung unverändert | +| Absatz zum elektronisch ausfüllbaren Webformular | ausgelassen: Es gibt keine eigene Webformular-Funktion außer der Widerrufsfunktion | +| Textbausteine zu Waren (Besitz, Rückzahlungsverweigerung, Abholung oder Rücksendung, Rücksendekosten, Wertverlust) | ausgelassen: Ein Termin hat keine Waren | +| Dienstleistungen: Wertersatz („Haben Sie verlangt, dass die Dienstleistungen …“) | enthalten; „oder Lieferung von Wasser/Gas/Strom/Fernwärme [Unzutreffendes streichen]“ ist gestrichen, wie der Gestaltungshinweis es vorsieht | +| Muster-Widerrufsformular (Anlage 2) | enthalten; in der Zeile „An …“ stehen Name, Anschrift und E-Mail des Betriebs; die (*)-Alternativen bleiben unverändert | + +Anrede immer „Sie“, auch bei Betrieben, die ihre Gäste duzen: Es ist der gesetzliche Mustertext. + +## Text (Platzhalter in doppelten geschweiften Klammern) + +```text +Widerrufsbelehrung + +Widerrufsrecht + +Sie haben das Recht, binnen vierzehn Tagen ohne Angabe von Gründen diesen Vertrag zu widerrufen. + +Die Widerrufsfrist beträgt vierzehn Tage ab dem Tag des Vertragsabschlusses. + +Um Ihr Widerrufsrecht auszuüben, müssen Sie uns ({{Name des Betriebs}}, {{Anschrift}}, Telefon: {{Telefon}}, E-Mail: {{E-Mail}}) mittels einer eindeutigen Erklärung (z. B. ein mit der Post versandter Brief oder eine E-Mail) über Ihren Entschluss, diesen Vertrag zu widerrufen, informieren. Sie können dafür das beigefügte Muster-Widerrufsformular verwenden, das jedoch nicht vorgeschrieben ist. + +Sie können Ihr Widerrufsrecht auch online über die Schaltfläche „Vertrag widerrufen“ ausüben, die Sie im Terminbereich auf unserer Website und über den Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail finden. Wenn Sie diese Online-Funktion nutzen, übermitteln wir Ihnen auf einem dauerhaften Datenträger (z. B. durch eine E-Mail) unverzüglich eine Eingangsbestätigung mit Informationen zum Inhalt der Widerrufserklärung sowie dem Datum und der Uhrzeit ihres Eingangs. + +Zur Wahrung der Widerrufsfrist reicht es aus, dass Sie die Mitteilung über die Ausübung des Widerrufsrechts vor Ablauf der Widerrufsfrist absenden. + +Folgen des Widerrufs + +Wenn Sie diesen Vertrag widerrufen, haben wir Ihnen alle Zahlungen, die wir von Ihnen erhalten haben, einschließlich der Lieferkosten (mit Ausnahme der zusätzlichen Kosten, die sich daraus ergeben, dass Sie eine andere Art der Lieferung als die von uns angebotene, günstigste Standardlieferung gewählt haben), unverzüglich und spätestens binnen vierzehn Tagen ab dem Tag zurückzuzahlen, an dem die Mitteilung über Ihren Widerruf dieses Vertrags bei uns eingegangen ist. Für diese Rückzahlung verwenden wir dasselbe Zahlungsmittel, das Sie bei der ursprünglichen Transaktion eingesetzt haben, es sei denn, mit Ihnen wurde ausdrücklich etwas anderes vereinbart; in keinem Fall werden Ihnen wegen dieser Rückzahlung Entgelte berechnet. + +Haben Sie verlangt, dass die Dienstleistungen während der Widerrufsfrist beginnen soll, so haben Sie uns einen angemessenen Betrag zu zahlen, der dem Anteil der bis zu dem Zeitpunkt, zu dem Sie uns von der Ausübung des Widerrufsrechts hinsichtlich dieses Vertrags unterrichten, bereits erbrachten Dienstleistungen im Vergleich zum Gesamtumfang der im Vertrag vorgesehenen Dienstleistungen entspricht. + +Muster-Widerrufsformular + +(Wenn Sie den Vertrag widerrufen wollen, dann füllen Sie bitte dieses Formular aus und senden Sie es zurück.) + +– An {{Name des Betriebs}}, {{Anschrift}}, {{E-Mail}}: +– Hiermit widerrufe(n) ich/wir (*) den von mir/uns (*) abgeschlossenen Vertrag über den Kauf der folgenden Waren (*)/die Erbringung der folgenden Dienstleistung (*) +– Bestellt am (*)/erhalten am (*) +– Name des/der Verbraucher(s) +– Anschrift des/der Verbraucher(s) +– Unterschrift des/der Verbraucher(s) (nur bei Mitteilung auf Papier) +– Datum + +(*) Unzutreffendes streichen. +``` + +## Wo der Text erscheint + +- G02 (Bestätigung an den Gast), Block `widerruf`, als `{{widerrufsbelehrung}}` (die Mailvorlage setzt den Text HTML-maskiert ein); nur wenn die Buchung ein Widerrufsrecht hat (verbindlich, Verbraucher). +- Bei einer Freizeit-Dienstleistung mit festem Termin (§ 312g Abs. 2 Nr. 9 BGB) und bei Gästen, die nur Unternehmen sind, entfallen Belehrung, Formular und Widerrufsfunktion; der Hinweis steht dann in `termine-hinweise.md`. + +## Für die Rechtsprüfung offen + +1. **Lieferkosten-Klammer** im Absatz „Folgen des Widerrufs“ („einschließlich der Lieferkosten (mit Ausnahme …)“) steht unverändert im Text. Anlage 1 enthält keinen Gestaltungshinweis, der sie bei reinen Dienstleistungen zu streichen erlaubt; sie ist deshalb nicht gekürzt. Prüfen, ob sie für Dienstleistungen entfallen darf. +2. **„… beginnen soll“**: Nach dem Streichen von „oder Lieferung von Wasser/Gas/Strom/Fernwärme“ steht „die Dienstleistungen … beginnen soll“ (Singular im Gesetzestext). Der Wortlaut ist bewusst nicht geglättet, damit das Muster unverändert bleibt. Prüfen, ob „sollen“ zulässig ist. +3. **Anschrift und Telefon**: Das Muster verlangt Anschrift und Telefonnummer. Die Einstellung verlangt nur Name und E-Mail. Das Portal sollte die Anschrift (und besser auch die Telefonnummer) verlangen, bevor eine verbindliche Verbraucher-Buchung möglich ist; sonst fehlt sie in der Belehrung. +4. **Wertersatz-Absatz** gilt nur, wenn der Gast den vorzeitigen Beginn verlangt hat (Zustimmung `ZUSTIMMUNG_VORZEITIG`, nur bei Terminbeginn innerhalb von 14 Tagen). Der Absatz steht trotzdem immer in der Belehrung, wie das Muster es für Dienstleistungen vorsieht. +5. **„Terminbereich auf unserer Website“** ist als geeigneter Hinweis gewählt (Entscheidung R12). Prüfen, ob ein Hinweis ohne Internetadresse genügt oder die Adresse der Seite genannt werden muss. +6. **Gültigkeit der Gesetzlichkeitsfiktion**: Sie gilt nur, wenn das Muster inhaltlich und gestalterisch dem Gesetz entspricht. Jede Abweichung (auch Zeilenumbrüche, Überschriften, die „–“-Aufzählung im Formular) ist hier Darstellung, nicht Inhalt. Vor dem Einsatz bei fremden Betrieben durch eine Kanzlei prüfen lassen. diff --git a/server/services/termine/rechtstexte.js b/server/services/termine/rechtstexte.js new file mode 100644 index 0000000..b9675c4 --- /dev/null +++ b/server/services/termine/rechtstexte.js @@ -0,0 +1,157 @@ +/** + * ENTWURF – nur Pilot, vor Einsatz bei fremden Betrieben prüfen. + * + * Rechtstext-Bausteine des Terminplaners fuer Gaeste (Spezifikation 2026-10-02-terminplaner-design.md, Abschnitt 11). + * Reine Funktionen und Konstanten, ohne Appwrite und ohne config. Die Texte sind Rechtstexte und stehen deshalb immer + * in der Sie-Form (auch bei Du-Betrieben); nur der Datenschutz-Hinweis unter dem Formular folgt der Anrede des Betriebs. + * + * widerrufsbelehrung: Muster der Anlage 1 und Muster-Widerrufsformular der Anlage 2 zu Art. 246a § 1 Abs. 2 Satz 2 EGBGB + * (docs/rechtliches/quellen/, abgerufen 2026-10-02), Gestaltungshinweise fuer einen Dienstleistungsvertrag angewendet: + * Fristbeginn „des Vertragsabschlusses.“, Betriebsdaten, Hinweis auf die Widerrufsfunktion als „anderer geeigneter + * Hinweis“ (ohne Internetadresse), Wertersatz fuer Dienstleistungen. Alles Uebrige ist der Gesetzestext, unveraendert. + * Ausgelassen: Textbausteine zu Waren (Besitz, Rueckzahlungsverweigerung, Rueckgabe, Rueckkosten, Wertverlust) und der + * Absatz zum elektronischen Webformular (es gibt nur die Widerrufsfunktion). test/termineRechtstexte.test.js prueft + * den Wortlaut gegen die Quelldateien. + * + * Die Rueckgabe ist reiner Text mit Zeilenumbruechen und NICHT HTML-maskiert: Die Mailvorlage maskiert beim Einsetzen. + */ + +export const RECHTSTEXTE_VERSION = '2026-10-02' + +const ZEILENUMBRUCH = '\n' +const ABSATZ = '\n\n' + +/** Einzeiliger Text: jeder Leerraum (auch Umbrueche) wird zu einem Leerzeichen, Rand getrimmt; Nicht-Text = ''. */ +const einzeilig = (wert) => (typeof wert === 'string' ? wert.replace(/\s+/g, ' ').trim() : '') + +/** Betriebsdaten fuer die Texte. Name und E-Mail sind Pflicht (die Einstellung prueft sie), Anschrift und Telefon entfallen, wenn leer. */ +function betriebsdaten(betrieb) { + const name = einzeilig(betrieb?.name) + const email = einzeilig(betrieb?.email) + if (!name || !email) throw new TypeError('Betriebsname und E-Mail-Adresse sind für die Rechtstexte erforderlich.') + return { name, email, adresse: einzeilig(betrieb.adresse), telefon: einzeilig(betrieb.telefon) } +} + +const verbinde = (teile) => teile.filter(Boolean).join(', ') + +// ---------------------------------------------------------------- Widerrufsbelehrung (Anlage 1) und Muster-Widerrufsformular (Anlage 2) + +const FRIST = 'des Vertragsabschlusses.' // Gestaltungshinweis a): Dienstleistungsvertrag + +// Widerrufsfunktion: „anderer geeigneter Hinweis“ statt Internetadresse, danach der Gesetzeswortlaut. +const WIDERRUFSFUNKTION = + 'Sie können Ihr Widerrufsrecht auch online über die Schaltfläche „Vertrag widerrufen“ ausüben, ' + + 'die Sie im Terminbereich auf unserer Website und über den Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail finden. ' + + 'Wenn Sie diese Online-Funktion nutzen, übermitteln wir Ihnen auf einem dauerhaften Datenträger (z. B. durch eine E-Mail) ' + + 'unverzüglich eine Eingangsbestätigung mit Informationen zum Inhalt der Widerrufserklärung sowie dem Datum und der Uhrzeit ihres Eingangs.' + +// Wertersatz bei Dienstleistungen; „oder Lieferung von Wasser/Gas/Strom/Fernwärme [Unzutreffendes streichen]“ ist gestrichen. +const WERTERSATZ = + 'Haben Sie verlangt, dass die Dienstleistungen während der Widerrufsfrist beginnen soll, so haben Sie uns einen angemessenen Betrag zu zahlen, ' + + 'der dem Anteil der bis zu dem Zeitpunkt, zu dem Sie uns von der Ausübung des Widerrufsrechts hinsichtlich dieses Vertrags unterrichten, ' + + 'bereits erbrachten Dienstleistungen im Vergleich zum Gesamtumfang der im Vertrag vorgesehenen Dienstleistungen entspricht.' + +/** Kontaktangaben fuer „Um Ihr Widerrufsrecht auszuüben, müssen Sie uns (…)“: Name, Anschrift, Telefonnummer, E-Mail-Adresse. */ +function kontakt({ name, adresse, telefon, email }) { + return verbinde([name, adresse, telefon && `Telefon: ${telefon}`, `E-Mail: ${email}`]) +} + +function belehrungsAbsaetze(daten) { + return [ + 'Widerrufsbelehrung', + 'Widerrufsrecht', + 'Sie haben das Recht, binnen vierzehn Tagen ohne Angabe von Gründen diesen Vertrag zu widerrufen.', + `Die Widerrufsfrist beträgt vierzehn Tage ab dem Tag ${FRIST}`, + `Um Ihr Widerrufsrecht auszuüben, müssen Sie uns (${kontakt(daten)}) mittels einer eindeutigen Erklärung ` + + '(z. B. ein mit der Post versandter Brief oder eine E-Mail) über Ihren Entschluss, diesen Vertrag zu widerrufen, informieren. ' + + 'Sie können dafür das beigefügte Muster-Widerrufsformular verwenden, das jedoch nicht vorgeschrieben ist.', + WIDERRUFSFUNKTION, + 'Zur Wahrung der Widerrufsfrist reicht es aus, dass Sie die Mitteilung über die Ausübung des Widerrufsrechts vor Ablauf der Widerrufsfrist absenden.', + 'Folgen des Widerrufs', + 'Wenn Sie diesen Vertrag widerrufen, haben wir Ihnen alle Zahlungen, die wir von Ihnen erhalten haben, einschließlich der Lieferkosten ' + + '(mit Ausnahme der zusätzlichen Kosten, die sich daraus ergeben, dass Sie eine andere Art der Lieferung als die von uns angebotene, ' + + 'günstigste Standardlieferung gewählt haben), unverzüglich und spätestens binnen vierzehn Tagen ab dem Tag zurückzuzahlen, ' + + 'an dem die Mitteilung über Ihren Widerruf dieses Vertrags bei uns eingegangen ist. ' + + 'Für diese Rückzahlung verwenden wir dasselbe Zahlungsmittel, das Sie bei der ursprünglichen Transaktion eingesetzt haben, ' + + 'es sei denn, mit Ihnen wurde ausdrücklich etwas anderes vereinbart; in keinem Fall werden Ihnen wegen dieser Rückzahlung Entgelte berechnet.', + WERTERSATZ, + ] +} + +function formularAbsaetze({ name, adresse, email }) { + const zeilen = [ + `An ${verbinde([name, adresse, email])}:`, + 'Hiermit widerrufe(n) ich/wir (*) den von mir/uns (*) abgeschlossenen Vertrag über den Kauf der folgenden Waren (*)/die Erbringung der folgenden Dienstleistung (*)', + 'Bestellt am (*)/erhalten am (*)', + 'Name des/der Verbraucher(s)', + 'Anschrift des/der Verbraucher(s)', + 'Unterschrift des/der Verbraucher(s) (nur bei Mitteilung auf Papier)', + 'Datum', + ] + return [ + 'Muster-Widerrufsformular', + '(Wenn Sie den Vertrag widerrufen wollen, dann füllen Sie bitte dieses Formular aus und senden Sie es zurück.)', + zeilen.map((zeile) => `– ${zeile}`).join(ZEILENUMBRUCH), + '(*) Unzutreffendes streichen.', + ] +} + +/** + * Widerrufsbelehrung und Muster-Widerrufsformular fuer einen Dienstleistungs-Termin, Sie-Form, als Text mit Zeilenumbruechen. + * betrieb: { name, email, adresse?, telefon? } aus der Einstellung. Fehlen Name oder E-Mail: TypeError. Fehlende Anschrift + * oder Telefonnummer entfallen aus dem Text (die Einstellung verlangt sie nicht; das Portal sollte die Anschrift verlangen). + */ +export function widerrufsbelehrung(betrieb) { + const daten = betriebsdaten(betrieb) + return [...belehrungsAbsaetze(daten), ...formularAbsaetze(daten)].join(ABSATZ) +} + +// ---------------------------------------------------------------- Hinweis unter dem Buchungsformular (recherche-recht.md 11.2) + +const LOESCHFRIST_ABGESAGT_TAGE = 30 +const LOESCHFRIST_UNBESTAETIGT_STUNDEN = 48 +const AUFBEWAHRUNG_VORGABE_TAGE = 90 + +/** + * Kurzer Datenschutz-Hinweis (Art. 13 DSGVO gestuft) vor dem Absenden; Sie oder Du nach einstellung.anrede. + * Die Loeschfristen entsprechen dem Loeschlauf: nicht bestaetigt 48 Stunden nach Ablauf der Bestaetigungsfrist, abgesagt + * 30 Tage, vergangene Termine einstellung.aufbewahrungTage nach dem Termin. Der Link auf die Datenschutzerklaerung des + * Betriebs (betrieb.datenschutzUrl) steht nicht im Text; das Widget setzt ihn daneben. Die Betriebs-E-Mail steht nicht im Text. + */ +export function formularHinweis(einstellung) { + const daten = betriebsdaten(einstellung?.betrieb) + const du = einstellung.anrede === 'du' + const tage = Number.isInteger(einstellung.aufbewahrungTage) ? einstellung.aufbewahrungTage : AUFBEWAHRUNG_VORGABE_TAGE + const verantwortlich = verbinde([daten.name, daten.adresse]) + const frist = `bis ${tage} Tage nach dem Termin, abgesagte Termine ${LOESCHFRIST_ABGESAGT_TAGE} Tage, ` + + `nicht bestätigte Buchungen ${LOESCHFRIST_UNBESTAETIGT_STUNDEN} Stunden nach Ablauf der Bestätigungsfrist.` + const betrieben = 'Betrieben wird der Terminkalender in unserem Auftrag von der WEBklar GbR (Server: Hetzner, Deutschland).' + const cookies = 'Es werden keine Cookies gesetzt.' + return du + ? `Datenschutz. Wir verwenden deine Angaben, um deinen Termin zu verwalten und zu bestätigen (Art. 6 Abs. 1 lit. b DSGVO). ${betrieben} ` + + `Wir speichern deine Daten ${frist} ${cookies} Bitte mach keine Angaben zu deiner Gesundheit. ` + + `Mehr erfährst du in unserer Datenschutzerklärung. Verantwortlich: ${verantwortlich}.` + : `Datenschutz. Wir verwenden Ihre Angaben, um Ihren Termin zu verwalten und zu bestätigen (Art. 6 Abs. 1 lit. b DSGVO). ${betrieben} ` + + `Wir speichern Ihre Daten ${frist} ${cookies} Bitte machen Sie keine Angaben zu Ihrer Gesundheit. ` + + `Mehr erfahren Sie in unserer Datenschutzerklärung. Verantwortlich: ${verantwortlich}.` +} + +// ---------------------------------------------------------------- Zustimmungen (Haken) und Hinweis zu Gesundheitsangaben + +/** + * Pflicht-Zustimmung zum vorzeitigen Beginn (§ 356 Abs. 5 Nr. 2 BGB, § 357a Abs. 2 BGB): nur bei verbindlicher Leistung + * mit Preis, Verbrauchern und Terminbeginn vor Ablauf der Widerrufsfrist. Sie-Form auch bei Du-Betrieben. + */ +export const ZUSTIMMUNG_VORZEITIG = (name) => + `Ich verlange ausdrücklich, dass ${name} mit der Leistung vor Ablauf der Widerrufsfrist beginnt. ` + + 'Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung der Leistung erlischt (§ 356 Abs. 5 Nr. 2 BGB). ' + + 'Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB).' + +/** Pflicht-Haken, wenn der Betrieb Stornobedingungen hat. */ +export const ZUSTIMMUNG_STORNO = 'Ich habe die Stornobedingungen gelesen.' + +/** Freiwilliger Haken (nie vorangekreuzt), nur wenn der Betrieb Erinnerungen anbietet; stunden = einstellung.erinnerung.stundenVorher. */ +export const ZUSTIMMUNG_ERINNERUNG = (stunden) => `Ich möchte ${stunden} Stunden vor dem Termin eine Erinnerung per E-Mail erhalten.` + +/** Hinweis ueber dem Nachrichtenfeld (Gesundheitsbereich ist ausgeschlossen, Spezifikation Abschnitt 4). */ +export const GESUNDHEIT_HINWEIS = 'Bitte keine Gesundheitsangaben.' diff --git a/test/termineRechtstexte.test.js b/test/termineRechtstexte.test.js new file mode 100644 index 0000000..4c1efbd --- /dev/null +++ b/test/termineRechtstexte.test.js @@ -0,0 +1,272 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { + RECHTSTEXTE_VERSION, + widerrufsbelehrung, + formularHinweis, + ZUSTIMMUNG_VORZEITIG, + ZUSTIMMUNG_STORNO, + ZUSTIMMUNG_ERINNERUNG, + GESUNDHEIT_HINWEIS, +} from '../server/services/termine/rechtstexte.js' + +// ENTWURF – nur Pilot, vor Einsatz bei fremden Betrieben prüfen. +// Die Belehrung wird gegen den Wortlaut der Anlagen 1 und 2 zu Art. 246a EGBGB geprueft (docs/rechtliches/quellen/, +// abgerufen 2026-10-02): jeder Absatz des Musters muss unveraendert im Ergebnis stehen, nur die Luecken sind gefuellt. + +const KOPF = 'ENTWURF – nur Pilot, vor Einsatz bei fremden Betrieben prüfen' +const lies = (rel) => readFileSync(new URL(`../${rel}`, import.meta.url), 'utf8') +const zeilen = (text) => text.split('\n').map((z) => z.trim()) + +const BETRIEB = { name: 'Salon Müller & Söhne', adresse: 'Hauptstraße 1, 67722 Winnweiler', telefon: '06301 1234', email: 'info@salon-mueller.de' } +const KONTAKT = 'Salon Müller & Söhne, Hauptstraße 1, 67722 Winnweiler, Telefon: 06301 1234, E-Mail: info@salon-mueller.de' +// Platzhalter-Betrieb: so steht die Belehrung in docs/rechtliches/termine-widerrufsbelehrung-muster.md +const PLATZHALTER = { name: '{{Name des Betriebs}}', adresse: '{{Anschrift}}', telefon: '{{Telefon}}', email: '{{E-Mail}}' } + +const ANLAGE_1 = lies('docs/rechtliches/quellen/anlage-1.txt') +const ANLAGE_2 = lies('docs/rechtliches/quellen/anlage-2.txt') + +/** Muster-Absaetze der Anlage 1 (zwischen Titel und Gestaltungshinweisen) mit den Luecken des Gesetzestexts. */ +function musterZeilen() { + const z = zeilen(ANLAGE_1) + const von = z.indexOf('Widerrufsbelehrung') + const bis = z.findIndex((s) => s.startsWith('Gestaltungshinweise:')) + assert.ok(von >= 0 && bis > von, 'Aufbau von anlage-1.txt hat sich geaendert') + return z.slice(von, bis).filter(Boolean) +} + +/** Textbaustein a) fuer Dienstleistungsvertraege: „des Vertragsabschlusses.“ */ +function fristBaustein() { + const m = /Dienstleistungsvertrags[^„]*„([^“]+)“/.exec(ANLAGE_1) + assert.ok(m, 'Gestaltungshinweis a) fehlt in anlage-1.txt') + return m[1] +} + +const belehrung = (betrieb = BETRIEB) => widerrufsbelehrung(betrieb) + +test('Fassung der Rechtstexte', () => { + assert.equal(RECHTSTEXTE_VERSION, '2026-10-02') +}) + +// ---------------------------------------------------------------- Widerrufsbelehrung + +test('Belehrung: alle Muster-Absaetze der Anlage 1 stehen wörtlich darin, nur die Lücken sind gefüllt', () => { + const text = belehrung() + const muster = musterZeilen() + assert.equal(muster.length, 8, 'Anlage 1: erwartet Titel, Widerrufsrecht, 3 Absätze, Folgen des Widerrufs und 1 Absatz') + assert.equal(fristBaustein(), 'des Vertragsabschlusses.') + for (const zeile of muster) { + const erwartet = zeile + .replace('ab dem Tag .', `ab dem Tag ${fristBaustein()}`) + .replace('uns ( )', `uns (${KONTAKT})`) + assert.ok(text.includes(erwartet), `Absatz fehlt oder ist verändert: ${erwartet}`) + } +}) + +test('Belehrung: Widerrufsfunktion als „anderer geeigneter Hinweis“, Rest des Gesetzeswortlauts unverändert', () => { + const text = belehrung() + const m = /Wenn Sie diese Online-Funktion nutzen, ([^“]+?)\s*“/.exec(ANLAGE_1) + assert.ok(m, 'Gestaltungshinweis zur Widerrufsfunktion fehlt in anlage-1.txt') + assert.ok(m[1].includes('Eingangsbestätigung') && m[1].includes('Uhrzeit ihres Eingangs')) + const absatz = 'Sie können Ihr Widerrufsrecht auch online über die Schaltfläche „Vertrag widerrufen“ ausüben, ' + + 'die Sie im Terminbereich auf unserer Website und über den Link „Termin verwalten“ in Ihrer Bestätigungs-E-Mail finden. ' + + `Wenn Sie diese Online-Funktion nutzen, ${m[1]}` + assert.ok(text.includes(absatz), 'Absatz zur Widerrufsfunktion fehlt oder weicht ab') + assert.ok(text.includes('Vertrag widerrufen')) + assert.ok(!text.includes('online unter'), 'keine Internetadresse, kein Parameter') + assert.ok(!/https?:\/\//.test(text)) +}) + +test('Belehrung: Wertersatz für Dienstleistungen, Unzutreffendes (Wasser/Gas/Strom/Fernwärme) gestrichen', () => { + const text = belehrung() + const m = /Haben Sie verlangt, dass die Dienstleistungen oder Lieferung von Wasser\/Gas\/Strom\/Fernwärme \[Unzutreffendes streichen\] (während[^“]+?)\s*“/.exec(ANLAGE_1) + assert.ok(m, 'Wertersatz-Absatz fehlt in anlage-1.txt') + assert.ok(text.includes(`Haben Sie verlangt, dass die Dienstleistungen ${m[1]}`), 'Wertersatz-Absatz fehlt oder weicht ab') + assert.ok(!/Wasser|Strom|Fernwärme|\[Unzutreffendes/.test(text)) +}) + +test('Belehrung: Muster-Widerrufsformular der Anlage 2 steht wörtlich darin, Betrieb in der „An“-Zeile', () => { + const text = belehrung() + const z = zeilen(ANLAGE_2) + const start = z.indexOf('Muster-Widerrufsformular') + assert.ok(start >= 0, 'Aufbau von anlage-2.txt hat sich geaendert') + const inhalt = z.slice(start).filter((s) => s && s !== '–') + assert.ok(inhalt.length >= 9) + let anZeile = 0 + for (const zeile of inhalt) { + if (zeile.startsWith('An [hier ist')) { + anZeile += 1 + assert.ok(text.includes('An Salon Müller & Söhne, Hauptstraße 1, 67722 Winnweiler, info@salon-mueller.de:'), '„An“-Zeile mit Betrieb fehlt') + } else { + assert.ok(text.includes(zeile), `Formularzeile fehlt oder ist verändert: ${zeile}`) + } + } + assert.equal(anZeile, 1) + // die (*)-Alternativen bleiben unverändert stehen + assert.ok(text.includes('Hiermit widerrufe(n) ich/wir (*) den von mir/uns (*) abgeschlossenen Vertrag über den Kauf der folgenden Waren (*)/die Erbringung der folgenden Dienstleistung (*)')) + assert.ok(text.includes('(*) Unzutreffendes streichen.')) +}) + +test('Belehrung: Betriebsdaten, kein offener Platzhalter, nichts zu Waren-Rücksendung oder Webformular', () => { + const text = belehrung() + for (const wert of Object.values(BETRIEB)) assert.ok(text.includes(wert), `Betriebsangabe fehlt: ${wert}`) + assert.ok(text.includes('Vertrag widerrufen')) + assert.ok(text.includes('Termin verwalten')) + assert.ok(text.includes('vierzehn Tagen')) + assert.ok(!/\{\{|\}\}|\[|\]|…|\.\.\.|\(\s*\)/.test(text), 'offener Platzhalter') + assert.ok(!/Rücksendung|zurückzusenden|Besitz genommen|Wir holen die Waren|Sie haben die Waren|elektronisch ausfüllen|Wir können die Rückzahlung verweigern/.test(text)) + assert.ok(!/undefined|null|NaN/.test(text)) +}) + +test('Belehrung: reiner Text mit Zeilenumbrüchen, nicht HTML-maskiert (die Mail maskiert beim Einsetzen)', () => { + const text = belehrung() + assert.equal(typeof text, 'string') + assert.ok(text.includes('\n')) + assert.ok(text.startsWith('Widerrufsbelehrung\n')) + assert.ok(text.includes('Salon Müller & Söhne')) + assert.ok(!text.includes('&') && !/<[a-z/]/i.test(text)) + assert.ok(!text.includes('\r')) + assert.equal(belehrung(), belehrung(), 'deterministisch') +}) + +test('Belehrung: fehlende Telefonnummer oder Anschrift hinterlässt keine leeren Klammern oder Kommas', () => { + const ohneTel = belehrung({ ...BETRIEB, telefon: '' }) + assert.ok(ohneTel.includes('uns (Salon Müller & Söhne, Hauptstraße 1, 67722 Winnweiler, E-Mail: info@salon-mueller.de)')) + assert.ok(!ohneTel.includes('Telefon:')) + const ohneAdresse = belehrung({ ...BETRIEB, adresse: '', telefon: undefined }) + assert.ok(ohneAdresse.includes('uns (Salon Müller & Söhne, E-Mail: info@salon-mueller.de)')) + assert.ok(ohneAdresse.includes('An Salon Müller & Söhne, info@salon-mueller.de:')) + assert.ok(!/,\s*,|\(\s*,|,\s*\)|:\s*,/.test(ohneAdresse)) +}) + +test('Belehrung: Betriebsname und E-Mail sind Pflicht, Leerraum in Angaben wird zu einem Leerzeichen', () => { + assert.throws(() => widerrufsbelehrung({ ...BETRIEB, name: '' }), TypeError) + assert.throws(() => widerrufsbelehrung({ ...BETRIEB, email: undefined }), TypeError) + assert.throws(() => widerrufsbelehrung(undefined), TypeError) + assert.throws(() => widerrufsbelehrung(null), TypeError) + const text = belehrung({ ...BETRIEB, name: ' Salon \n Müller ', adresse: 'Hauptstraße 1,\n67722 Winnweiler' }) + assert.ok(text.includes('uns (Salon Müller, Hauptstraße 1, 67722 Winnweiler, Telefon:')) +}) + +test('Belehrung ist die Quelle von docs/rechtliches/termine-widerrufsbelehrung-muster.md (Platzhalter-Fassung steht dort wörtlich)', () => { + const md = lies('docs/rechtliches/termine-widerrufsbelehrung-muster.md') + assert.ok(md.replace(/\r/g, '').includes(widerrufsbelehrung(PLATZHALTER)), 'Textblock im Muster-Dokument weicht von widerrufsbelehrung() ab') +}) + +// ---------------------------------------------------------------- Zustimmungen und Hinweise + +test('Zustimmungstexte wortgleich zu den Vorgaben (Sie-Form)', () => { + assert.equal( + ZUSTIMMUNG_VORZEITIG('Salon Müller'), + 'Ich verlange ausdrücklich, dass Salon Müller mit der Leistung vor Ablauf der Widerrufsfrist beginnt. ' + + 'Mir ist bekannt, dass mein Widerrufsrecht mit der vollständigen Erbringung der Leistung erlischt (§ 356 Abs. 5 Nr. 2 BGB). ' + + 'Widerrufe ich vor der vollständigen Erbringung, schulde ich einen angemessenen Betrag für die bis dahin erbrachte Leistung (§ 357a Abs. 2 BGB).', + ) + assert.equal(ZUSTIMMUNG_STORNO, 'Ich habe die Stornobedingungen gelesen.') + assert.equal(ZUSTIMMUNG_ERINNERUNG(24), 'Ich möchte 24 Stunden vor dem Termin eine Erinnerung per E-Mail erhalten.') + assert.equal(ZUSTIMMUNG_ERINNERUNG(2), 'Ich möchte 2 Stunden vor dem Termin eine Erinnerung per E-Mail erhalten.') + assert.equal(GESUNDHEIT_HINWEIS, 'Bitte keine Gesundheitsangaben.') +}) + +test('Zustimmung zum vorzeitigen Beginn setzt den Betriebsnamen unverändert ein (kein Ersetzungsmuster)', () => { + const text = ZUSTIMMUNG_VORZEITIG("Frisör $& $1 'Anna'") + assert.ok(text.startsWith("Ich verlange ausdrücklich, dass Frisör $& $1 'Anna' mit der Leistung vor Ablauf")) +}) + +const EINSTELLUNG = { anrede: 'sie', aufbewahrungTage: 90, betrieb: BETRIEB } + +test('Formular-Hinweis (Sie): Zweck, Auftragsverarbeiter, Löschfristen, keine Cookies, Gesundheit, Verantwortlicher', () => { + const text = formularHinweis(EINSTELLUNG) + assert.ok(text.startsWith('Datenschutz. Wir verwenden Ihre Angaben, um Ihren Termin zu verwalten und zu bestätigen (Art. 6 Abs. 1 lit. b DSGVO).')) + assert.ok(text.includes('in unserem Auftrag von der WEBklar GbR (Server: Hetzner, Deutschland)')) + assert.ok(text.includes('Wir speichern Ihre Daten bis 90 Tage nach dem Termin, abgesagte Termine 30 Tage, nicht bestätigte Buchungen 48 Stunden nach Ablauf der Bestätigungsfrist.')) + assert.ok(text.includes('Es werden keine Cookies gesetzt.')) + assert.ok(text.includes('Bitte machen Sie keine Angaben zu Ihrer Gesundheit.')) + assert.ok(text.includes('Mehr erfahren Sie in unserer Datenschutzerklärung.')) + assert.ok(text.endsWith('Verantwortlich: Salon Müller & Söhne, Hauptstraße 1, 67722 Winnweiler.')) + assert.ok(!/\{\{|\}\}|\[|\]|undefined|null/.test(text)) + assert.ok(!text.includes('info@salon-mueller.de'), 'Betriebs-E-Mail gehört nicht in die öffentliche Konfiguration') + assert.ok(!/\bdein|\bdu\b/i.test(text)) +}) + +test('Formular-Hinweis (Du): gleiche Aussagen in Du-Form', () => { + const text = formularHinweis({ ...EINSTELLUNG, anrede: 'du', aufbewahrungTage: 30 }) + assert.ok(text.startsWith('Datenschutz. Wir verwenden deine Angaben, um deinen Termin zu verwalten und zu bestätigen (Art. 6 Abs. 1 lit. b DSGVO).')) + assert.ok(text.includes('Wir speichern deine Daten bis 30 Tage nach dem Termin, abgesagte Termine 30 Tage, nicht bestätigte Buchungen 48 Stunden nach Ablauf der Bestätigungsfrist.')) + assert.ok(text.includes('Bitte mach keine Angaben zu deiner Gesundheit.')) + assert.ok(text.includes('Mehr erfährst du in unserer Datenschutzerklärung.')) + assert.ok(text.includes('Es werden keine Cookies gesetzt.')) + assert.ok(!/\bSie\b|\bIhre|\bIhren/.test(text)) +}) + +test('Formular-Hinweis: Sie ist die Vorgabe, Aufbewahrung 90 Tage ohne gültigen Wert, Adresse optional', () => { + const text = formularHinweis({ betrieb: { name: 'Salon Müller', email: 'info@salon.de' } }) + assert.ok(text.includes('Wir speichern Ihre Daten bis 90 Tage nach dem Termin')) + assert.ok(text.endsWith('Verantwortlich: Salon Müller.')) + assert.ok(formularHinweis({ ...EINSTELLUNG, aufbewahrungTage: 'viel' }).includes('bis 90 Tage')) + assert.ok(formularHinweis({ ...EINSTELLUNG, aufbewahrungTage: 365 }).includes('bis 365 Tage')) + assert.ok(formularHinweis({ ...EINSTELLUNG, anrede: 'unbekannt' }).includes('Ihre Angaben')) + assert.throws(() => formularHinweis({}), TypeError) + assert.throws(() => formularHinweis(undefined), TypeError) +}) + +// ---------------------------------------------------------------- Dokumente + +const DOKUMENTE = [ + 'docs/rechtliches/termine-widerrufsbelehrung-muster.md', + 'docs/rechtliches/termine-datenschutz-absatz.md', + 'docs/rechtliches/termine-hinweise.md', + 'docs/rechtliches/termine-datenschutz-6c-entwurf.md', +] + +test('Jedes Rechtstext-Dokument beginnt mit dem Entwurfs-Kopf', () => { + for (const rel of DOKUMENTE) { + assert.equal(lies(rel).split('\n')[0].trim(), KOPF, rel) + } +}) + +test('Hinweise-Dokument: Formular-Hinweis (Sie/Du) und alle Zustimmungstexte stehen wörtlich darin', () => { + const md = lies('docs/rechtliches/termine-hinweise.md').replace(/\r/g, '') + const platzhalter = { betrieb: { name: '{{Firma}}', adresse: '{{Anschrift}}', email: '{{E-Mail}}' }, aufbewahrungTage: 90 } + assert.ok(md.includes(formularHinweis({ ...platzhalter, anrede: 'sie' })), 'Formular-Hinweis (Sie) weicht ab') + assert.ok(md.includes(formularHinweis({ ...platzhalter, anrede: 'du' })), 'Formular-Hinweis (Du) weicht ab') + assert.ok(md.includes(ZUSTIMMUNG_VORZEITIG('{{Firma}}'))) + assert.ok(md.includes(ZUSTIMMUNG_STORNO)) + assert.ok(md.includes(ZUSTIMMUNG_ERINNERUNG('{{n}}'))) + assert.ok(md.includes(GESUNDHEIT_HINWEIS)) + for (const knopf of ['Zahlungspflichtig buchen', 'Termin buchen', 'Termin anfragen', 'Vertrag widerrufen', 'Widerruf bestätigen']) { + assert.ok(md.includes(knopf), `Knopftext fehlt: ${knopf}`) + } +}) + +test('Datenschutz-Absatz: Löschfristen der Vorgaben, Sie- und Du-Fassung, Auftragsverarbeiter und Gesundheitshinweis', () => { + const md = lies('docs/rechtliches/termine-datenschutz-absatz.md').replace(/\r/g, '') + assert.ok(/Fassung Sie/i.test(md) && /Fassung Du/i.test(md)) + assert.ok(md.includes('Online-Terminbuchung')) + // jede Fassung nennt die vier Fristen: nicht bestätigt 48 Stunden, abgesagt 30 Tage, vergangen {{Tage}} Tage, Abschaltung 30 Tage + const fassungen = md.split(/^## Fassung /m).slice(1) + assert.equal(fassungen.length, 2) + for (const f of fassungen) { + assert.ok(f.includes('48 Stunden'), 'Frist nicht bestätigter Buchungen fehlt') + assert.ok(f.includes('{{Tage}} Tage nach dem Termin'), 'Aufbewahrung vergangener Termine fehlt') + assert.ok(/abgesagte[^.]*30 Tage/.test(f), 'Frist abgesagter Termine fehlt') + assert.ok(/abgeschaltet[^.]*30 Tage|30 Tage nach dem Abschalten/.test(f), 'Frist nach Abschaltung fehlt') + assert.ok(f.includes('WEBklar GbR') && f.includes('Art. 28 DSGVO') && f.includes('Hetzner')) + assert.ok(f.includes('Gesundheit')) + assert.ok(f.includes('Art. 6 Abs. 1 lit. b DSGVO') && f.includes('Art. 6 Abs. 1 lit. a DSGVO')) + } + assert.ok(fassungen[0].startsWith('Sie') && fassungen[1].startsWith('Du')) + assert.ok(!/\bdein|\bdu\b|\bdir\b|\bdich\b/i.test(fassungen[0]), 'Du-Form in der Sie-Fassung') + assert.ok(!/\bIhr|\bIhnen\b|\bSie (haben|können|erhalten|wünschen|machen|buchen|klicken|sind|wollen)\b/.test(fassungen[1]), 'Sie-Form in der Du-Fassung') +}) + +test('6c-Entwurf: nur als Entwurfsdatei, public/datenschutz.html bleibt unverändert (ohne Abschnitt 6c)', () => { + const md = lies('docs/rechtliches/termine-datenschutz-6c-entwurf.md') + assert.ok(md.includes('6c. Online-Terminbuchung')) + assert.ok(md.includes('Auftragsverarbeiter')) + assert.ok(/nicht (in|auf) .*(einbauen|veröffentlich)|nicht eingebaut|nicht veröffentlicht/i.test(md)) + // TP2 baut 6c ein und entfernt diese Absicherung + const html = lies('public/datenschutz.html') + assert.ok(!/6c\./.test(html) && !html.includes('id="termine"'), '6c darf vor TP2 nicht in public/datenschutz.html stehen') +})