Datenschutz: neue Fassung vom 29.10.2026 zum Umschalten der Kundenansicht (R14)

Prüfpunkte D1 bis D8 der Rechtsrunde umgesetzt: Kundenart im Kundenkonto
(D3), Nachweis beim Einschalten einer Funktion ohne Zahlung (D1, D4),
Wortlaut und Kundenart im Nachweis bei Abo, Wechsel, Zusatzspeicher (D2)
und Übernahmegutschrift, Website-Analyse im Hosting- und Wartungs-Abo
(D5), Ende und Löschfristen der Funktionen im Abo-Modell (D6), Vorschuss
im Stundenkonto (D7). Neu Abschnitt 6e Öffnungszeiten live, auf den die
Leistungsbeschreibung Fassung 6 verweist. Der Bestand einzeln gekaufter
Funktionen bleibt beschrieben. PDF neu gedruckt, Pins nachgezogen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-29 13:27:32 +00:00
1 parent 756f7e6153
commit b1a582cb20
7 files changed
+77 -19

No files matched your search

+3 -1
View File
@@ -39,5 +39,7 @@ test('Datenschutz-Prüfpunkte D1 bis D8 mit gültiger Fundstelle in public/daten
const zeilen = lies('public/datenschutz.html').split('\n')
const refs = [...dsg.matchAll(/public\/datenschutz\.html:(\d+)/g)].map((m) => Number(m[1]))
assert.ok(refs.length >= 8 && refs.every((n) => n >= 1 && n <= zeilen.length))
assert.ok(zeilen[127].includes('Zahlungspflichtig bestellen') && zeilen[126].includes('Leistungsbeginn') && zeilen[102].includes('Wartungs-Abo'), 'D1, D2, D5 zitieren die richtigen Zeilen')
// Die Zeilen der Prüfpunkte gelten für die Fassung vom 22.10.2026; seit der Fassung vom 29.10.2026 (Umschalten, R14) sind Absätze dazugekommen, darum sucht der Test die Stellen nach Inhalt
const zeile = (text) => zeilen.find((z) => z.includes(text)) ?? ''
assert.ok(zeile('Zahlungspflichtig bestellen').includes('Stripe-Sitzung') && zeile('Leistungsbeginn').includes('Browserkennung') && zeile('Sobald diese Funktion freigeschaltet').includes('Wartungs-Abo'), 'D1, D2, D5 zitieren die richtigen Stellen')
})
+46 -6
View File
@@ -384,7 +384,7 @@ test('Rechtsrunde: Zusatzspeicher ganz oder teilweise kündbar, auch über „Ve
test('Rechtsrunde Datenschutz: Aufbewahrung nach dem Ende des letzten Abos, Zahl der Pakete, kurzer UptimeRobot-Eintrag', () => {
const html = read(DS), ds = strip(html)
assert.equal(ds.split('drei Jahre nach Ablauf des Jahres, in dem Ihr letztes Abo endet').length - 1, 3, 'Abschnitt 7 (Bestellungen, Funktionsbestellung) und Kündigungs-/Widerrufsseite')
assert.equal(ds.split('drei Jahre nach Ablauf des Jahres, in dem Ihr letztes Abo endet').length - 1, 5, 'Abschnitt 7 (Bestellungen, Funktionsbestellung, Einschalten einer Funktion, Nachweis der Übernahmegutschrift) und Kündigungs-/Widerrufsseite')
for (const s of ['drei Jahre nach Vertragsende', 'nach dem Ende Ihres letzten Abos']) assert.ok(!ds.includes(s), `noch da: ${s}`)
// Abschnitt 6 (Kundenkonto) verweist für Abos und Bestellungen auf die Fristen in Abschnitt 7
assert.ok(between(ds, '6. Kundenportal', '7. Abos').includes('Für Ihre Abos und Bestellungen gelten die Fristen in Abschnitt 7.'))
@@ -613,8 +613,8 @@ test('legal_datenschutz_webshop: Abschnitt 6d steht zwischen 6c und 7 und nennt
const html = read(DS)
const termine = html.indexOf('id="termine"')
const webshop = html.indexOf('id="webshop"')
const sieben = html.indexOf('<h2 id="buchungskalender">') // 6f folgt auf 6d (Buchungskalender)
assert.ok(termine > 0 && webshop > termine && sieben > webshop && html.indexOf('<h2>7. ') > sieben, 'Reihenfolge 6c < 6d < 6f < 7')
const sieben = html.indexOf('<h2 id="zeiten">') // 6e (Öffnungszeiten live) folgt auf 6d, danach 6f (Buchungskalender)
assert.ok(termine > 0 && webshop > termine && sieben > webshop && html.indexOf('<h2 id="buchungskalender">') > sieben && html.indexOf('<h2>7. ') > sieben, 'Reihenfolge 6c < 6d < 6e < 6f < 7')
assert.equal(html.split('id="webshop"').length - 1, 1)
assert.ok(html.includes('<h2 id="webshop">6d. Webshop auf Websites unserer Kunden</h2>'))
const roh = html.slice(webshop, sieben)
@@ -631,9 +631,11 @@ test('legal_datenschutz_webshop: Abschnitt 6d steht zwischen 6c und 7 und nennt
// Orte und Fristen wie im AVV Webshop
for (const s of ['Falkenstein', 'Nürnberg', 'nach 30 Tagen', 'drei Jahre nach dem Ende des Jahres der Bestellung', '90 Tage', 'bis zu sieben Tage', 'Sitzungsspeicher', 'nicht dauerhaft']) assert.ok(text.includes(s), `6d: ${s}`)
assert.ok(text.includes('Eine Ausnahme ist das Einrichtungs-Ticket'))
// kein Abo-Bezug: der Webshop endet mit dem Vertrag über den Betrieb der Website (Entscheidung W5)
// Bestand der einzeln gekauften Webshops (Entscheidung W5): der Satz über den Vertrag über den Betrieb der Website bleibt unverändert
assert.ok(text.includes('endet der Vertrag über den Betrieb seiner Website'))
assert.ok(!text.includes('Wartungs-Abo') && !text.includes('Hosting-Abo'), '6d nennt kein bestimmtes Abo')
// Fassung 6 (R14, D6): im Wartungs-Abo eingeschaltet oder beim Bau eingebaut endet er mit dem Wartungs-Abo (auch beim Wechsel ins Hosting-Abo), spätestens am Folgetag; ein neues Wartungs-Abo in den 90 Tagen holt ihn von selbst zurück
assert.ok(text.includes('Abweichend davon gilt für einen Webshop, den der Kunde im Wartungs-Abo eingeschaltet hat oder den wir auf Wunsch des Kunden schon beim Bau der Website eingebaut haben: Er endet mit dem Wartungs-Abo, auch bei einem Wechsel ins Hosting-Abo; wir schalten ihn dann spätestens am folgenden Tag ab.'))
assert.ok(text.includes('Beginnt in dieser Frist wieder ein Wartungs-Abo, schalten wir ihn von selbst wieder ein; ein Hosting-Abo genügt dafür nicht.'))
// Empfängerliste nennt den Webshop bei Stripe
assert.ok(strip(html).includes('Eröffnung des Stripe-Kontos für den Webshop und den Buchungskalender unserer Kunden (Abschnitte 6d, 6f und 7)'))
})
@@ -739,7 +741,7 @@ test('legal_datenschutz_4a: eigene Website-Analyse auf webklar.com mit Rolle, Re
test('legal_datenschutz_anfragen: Abschnitt 11 beschreibt nur den Anfrage-Weg (KI bewertet, Mitarbeiter sendet von Hand), mit Anker, Quelle, Daten, Rechtsgrundlage, Fristen und Widerspruch', () => {
const html = read(DS)
// Stand-Pin: der Tag der Fassung (das endgültige Datum setzt der Push, dann auch hier)
assert.ok(html.includes('<p class="meta">Stand 22.10.2026</p>'), 'Stand 22.10.2026')
assert.ok(html.includes('<p class="meta">Stand 29.10.2026</p>'), 'Stand 29.10.2026')
assert.match(html, /<a id="interessenten"><\/a>\s*<h2>11\. Interessenten: Antworten auf öffentliche Anfragen<\/h2>/)
assert.equal(html.split('id="interessenten"').length - 1, 1, 'der Anker steht genau einmal da')
const s = between(strip(html), '11. Interessenten: Antworten auf öffentliche Anfragen', '12. Empfänger im Überblick')
@@ -883,3 +885,41 @@ test('legal_datenschutz_anfragen: Abschnitt 2, 12 und 14 passen zu Abschnitt 11
// Abschnitt 8 (E-Mail über Hetzner) bleibt unverändert gültig, Abschnitt 11 verweist darauf, statt davon abzuweichen
assert.ok(between(ds, '8. E-Mail', '9. Buchhaltung').includes('versenden und empfangen wir über den E-Mail-Dienst der Hetzner Online GmbH'))
})
// ---------------------------------------------------------------- Fassung vom 29.10.2026: Umschalten der Kundenansicht (R14, Prüfpunkte D1 bis D8)
test('legal_datenschutz_umschalten: Kundenart, Einschalten ohne Zahlung, Abo-Ende und Fristen wie die Leistungsbeschreibungen der Fassung 6', () => {
const html = read(DS), ds = strip(html)
// D3: Kundenart im Kundenkonto
const konto = between(ds, '6. Kundenportal', '6a. News')
assert.ok(konto.includes('Zu Ihrem Kundenkonto gehört außerdem Ihre Kundenart: „Betrieb“ oder „privat“.') && konto.includes('ist keine hinterlegt, gilt Ihr Konto als privat'))
// D7: Vorschuss als Teil des Stundenkontingents
assert.ok(konto.includes('Stundenkontingent (verfügbare Zeit, Verbrauch, Ablaufdaten und, wenn die Einrichtung einer Funktion mehr Zeit braucht, als verfügbar ist, ein Vorschuss)'))
// D1, D2, D4: Nachweis beim Einschalten ohne Zahlung, Wortlaut und Kundenart, alle sechs Funktionen
const sieben = between(ds, '7. Abos und Zahlung über Stripe', '8. E-Mail')
assert.ok(sieben.includes('Im Kundenportal speichern wir außerdem den Wortlaut der Zeilen und Häkchen, die Ihnen beim Klick angezeigt wurden, und Ihre Kundenart zu diesem Zeitpunkt (Abschnitt 6).'))
assert.ok(sieben.includes('Schalten Sie eine Funktion im Kundenportal ein (News-Pinnwand, Terminplaner, Webshop, Buchungskalender, Öffnungszeiten live oder Website-Analyse), gehört das zu Ihrem Abo. Es löst keine Zahlung aus, und es gibt keine Stripe-Sitzung.'))
assert.ok(sieben.includes('Bestellen Sie eine Funktion (News-Pinnwand, Terminplaner), speichern wir als Nachweis'), 'der Bestand der einzeln gekauften Funktionen bleibt beschrieben')
// D5: Website-Analyse in beiden Abos, 60 Tage (AVV Website-Analyse Nr. 9)
const analyse = section(html, 'analyse', 'termine')
assert.ok(analyse.includes('Kunden mit Hosting-Abo oder Wartungs-Abo') && analyse.includes('wechselt der Kunde vom Wartungs- ins Hosting-Abo, bleibt sie') && analyse.includes('60 Tage nach dem Abschalten'))
// D6: Funktionen im Abo enden mit dem Wartungs-Abo, die Fristen wie in den Leistungsbeschreibungen
const news = section(html, 'news', 'analyse')
assert.ok(news.includes('im Wartungs-Abo eingeschaltet oder haben wir sie auf Ihren Wunsch schon beim Bau Ihrer Website eingebaut, gilt stattdessen: Sie endet mit dem Wartungs-Abo'))
assert.ok(news.includes('spätestens am folgenden Tag ab und löschen alle Beiträge und Fotos 30 Tage nach dem Abschalten'))
const shop = section(html, 'webshop', 'zeiten'), buchung = strip(html.slice(html.indexOf('id="buchungskalender"'), html.indexOf('<h2>7. ')))
for (const [text, name] of [[shop, 'Webshop'], [buchung, 'Buchungskalender']]) {
assert.ok(text.includes(`im Wartungs-Abo eingeschaltet hat oder den wir auf Wunsch des Kunden schon beim Bau der Website eingebaut haben: Er endet mit dem Wartungs-Abo, auch bei einem Wechsel ins Hosting-Abo; wir schalten ihn dann spätestens am folgenden Tag ab.`), `${name}: Ende mit dem Abo`)
assert.ok(text.includes('Beginnt in dieser Frist wieder ein Wartungs-Abo, schalten wir ihn von selbst wieder ein; ein Hosting-Abo genügt dafür nicht.'), `${name}: Rückkehr von selbst`)
assert.ok(text.includes('90 Tage'), `${name}: 90 Tage`)
}
// Öffnungszeiten live (§ 6e): 30 Tage Weiterlauf, danach 30 Tage Eingaben, kein Behalten
const zeiten = section(html, 'zeiten', 'buchungskalender')
assert.ok(zeiten.includes('läuft die Funktion noch 30 Tage unverändert weiter; danach schalten wir sie ab. Die Eingaben des Kunden bleiben nach dem Abschalten 30 Tage gespeichert und werden danach gelöscht'))
assert.ok(!/behalten/i.test(zeiten))
// D8: die Seiten Kündigen und Widerrufen bleiben unverändert (kein neues Datum)
assert.ok(ds.includes('Über die Seiten project.webklar.com/kuendigen.html und project.webklar.com/widerrufen.html können Sie Ihren Vertrag ohne Anmeldung kündigen oder widerrufen'))
// Stand und Du-Form: die öffentliche Erklärung bleibt in der Sie-Form
assert.ok(html.includes('<p class="meta">Stand 29.10.2026</p>'))
assert.ok(!/\b(du|dein\w*|dir|dich)\b/i.test(ds), 'keine Du-Form')
})
+8 -3
View File
@@ -222,7 +222,7 @@ test('unterlagenBereit für zeiten ist erfüllt', async () => {
})
// ---------------------------------------------------------------------------------------------- Datenschutz § 6e
test('Datenschutz-Entwurf § 6e und Ergänzung § 6 liegen als Entwurf vor, datenschutz.html bleibt unberührt', () => {
test('Datenschutz-Entwurf § 6e und Ergänzung § 6 liegen als Vorlage vor, § 6e steht seit dem 29.10.2026 nach Fassung 6 in datenschutz.html', () => {
const md = lies('docs/rechtliches/zeiten-datenschutz-6e-entwurf.md')
assert.ok(md.includes('<h2 id="zeiten">6e. Öffnungszeiten live auf Websites unserer Kunden</h2>'))
for (const satz of [
@@ -257,8 +257,13 @@ test('Datenschutz-Entwurf § 6e und Ergänzung § 6 liegen als Entwurf vor, date
assert.ok(md.includes(heute7), 'der heutige § 7-Satz ist wörtlich zitiert')
assert.match(md, /Bestellen Sie eine Funktion \(News-Pinnwand, Terminplaner, Öffnungszeiten live\) oder schalten Sie Öffnungszeiten live im Wartungs-Abo ein/)
assert.ok(md.includes('legal_datenschutz_kundenansicht'), 'nennt den Test, der den § 6-Satz pinnt')
// Wächter: die Entwurfsdatei ist nicht in die veröffentlichte Erklärung gerutscht
assert.doesNotMatch(html, /6e\. Öffnungszeiten live/)
// Seit der Fassung vom 29.10.2026 (Umschalten der Kundenansicht, R14) steht § 6e in der Erklärung, nach Fassung 6 (kein Behalten gegen 30 €): der Entwurf bleibt als Vorlage liegen
assert.equal(html.split('<h2 id="zeiten">6e. Öffnungszeiten live auf Websites unserer Kunden</h2>').length - 1, 1, '§ 6e steht genau einmal in der Erklärung')
const e6 = html.slice(html.indexOf('<h2 id="zeiten">'), html.indexOf('<h2 id="buchungskalender">'))
assert.doesNotMatch(e6, /behalten|30 €/i, '§ 6e kennt kein Behalten gegen Zahlung')
assert.match(e6, /läuft die Funktion noch 30 Tage unverändert weiter; danach schalten wir sie ab/)
assert.match(e6, /bleiben nach dem Abschalten 30 Tage gespeichert und werden danach gelöscht/)
assert.match(e6, /spätestens mit der Löschung seines Kundenkontos/)
})
// ---------------------------------------------------------------------------------------------- K02/K03