# Beraterkontakt (Simulation) ## Testseite starten Wie bei der Buchung in drei Terminals starten: ```sh npm run dev:mail npm run dev:server npm run dev ``` Öffne . Der Button öffnet ein modales Fenster. Gib eine E-Mail aus einer vorhandenen Buchung in `src/server/data/bookings.csv` ein, wähle einen Kontaktgrund und bestätige den Simulationshinweis. Beim Klick auf „Anfrage senden & Termin wählen“ prüft der Server die E-Mail und versendet nur bei vorhandener Buchung. Bei Bedarf zuerst über die bestehende Angebotsseite eine Testbuchung anlegen. Nach dem Absenden müssen zwei getrennte Nachrichten in erscheinen: an die Kundenadresse und beispielsweise `anna.mueller@tri-hub.de`. Der Browser wechselt anschließend zur vom Auftrag vorgegebenen Microsoft-Bookings-Adresse. Es wird dort kein Termin automatisch gebucht. Die Checkliste spricht von Microsoft Forms; umgesetzt ist der konkret angegebene Bookings-Link. ## Checkliste - [x] Kontaktgrund auswählbar: Coaching, Ernährungsplan, Wettkampfverpflegung, Regeneration, Paketfragen, Termin und Sonstiges. - [x] Getrennte Eingangsbestätigungen an Kunde und Berater, ausschließlich simuliert über Mailpit, mit Kontaktgrund und Simulationshinweis. - [x] Weiterleitung zur vorhandenen Microsoft-Bookings-Adresse nach Annahme beider Nachrichten durch Mailpit; zusätzlicher Link als Rückfalloption. - [x] Kontakt nur nach erfolgreichem E-Mail-Abgleich mit `bookings.csv`; der Server prüft beim tatsächlichen Absenden erneut. ## Einbindung in Beraterprofile Die Profilseite importiert `openContactDialog` aus `src/features/contact/contact-dialog.js` und übergibt ausschließlich die ID: ```js import { openContactDialog } from "./src/features/contact/contact-dialog.js"; button.addEventListener("click", () => { openContactDialog({ advisorId: profile.id }); }); ``` Den Importpfad relativ zur einbindenden Datei anpassen. Das Modul bringt die Popup-Styles und die lokalen DM-Sans-Schriftdateien mit. Die vollständige Einbauanleitung für die Profilseite steht in [contact-integration.md](contact-integration.md). Die gemeinsame Datenquelle `src/shared/berater-daten.json` enthält ein Array mit eindeutigen String-IDs und den Feldern `id`, `name`, `email`. Die beiden vorhandenen Einträge sind Demo-Daten und können durch die tatsächlichen Profildaten ersetzt werden. Zusätzliche Profilfelder sind möglich. Die Datei wird im Frontend eingebunden: ausschließlich öffentliche Profildaten eintragen. `src/shared/berater.js` stellt `findAdvisor(advisorId)` bereit. Popup und Server verwenden dieselbe Zuordnung. Der Server holt die Empfängeradresse ausschließlich aus dieser JSON; vom Browser gesendete Namen oder Empfängeradressen werden nicht übernommen. E-Mail-Adressen müssen gültig sein und auf `@tri-hub.de` enden. Eine unbekannte ID wird abgelehnt und sperrt das Absenden im Popup. Die Testseite liest den URL-Parameter `berater-id`, zum Beispiel `contact-test.html?berater-id=max-mustermann`. Ohne Parameter verwendet sie `anna-mueller`. Nach Änderungen an der JSON den API-Server neu starten und für das Deployment das Frontend neu bauen. Das native `dialog` hält den Tastaturfokus im Popup. Escape und Schließen bringen ihn zum auslösenden Button zurück. Während einer Anfrage ist Schließen kurz gesperrt, damit der Versand nicht unbemerkt weiterläuft. Das Fenster ist auf schmalen Bildschirmen scrollbar. Die E-Mail wird bei jedem Absenden geprüft; ein separater Prüfbutton ist nicht erforderlich. Es gibt keine zusätzlichen Laufzeitabhängigkeiten. ## API Der vollständige OpenAPI-Vertrag steht in `src/server/swagger.json`. Beide Endpunkte erwarten POST mit `Content-Type: application/json`, maximal 8 KiB. Fehler enthalten `error.code` und `error.message`. | Endpunkt | Eingabe | Erfolg | | ----------------------- | --------------------------------------------------- | ---------------------------------------------------------- | | `/api/contact/validate` | `{ "email": "kunde@example.test" }` | 200: `{ "valid": true, "email": "kunde@example.test" }` | | `/api/contact` | E-Mail, Berater-ID, Grund, Zustimmung (siehe unten) | 201: `simulated`, `emailStatus: "accepted"`, `redirectUrl` | ```json { "email": "kunde@example.test", "advisorId": "anna-mueller", "reason": "coaching", "simulationAccepted": true } ``` 400 bedeutet ungültige Eingaben, 403 keine passende Buchung, 405 falsche Methode, 413 zu große Anfrage, 415 falscher Medientyp, 503 nicht lesbarer CSV-Speicher oder ungültige Beraterkontaktdaten. 502 bedeutet, dass mindestens eine Mailannahme fehlgeschlagen oder unklar ist; der Browser leitet dann nicht weiter. 500 bezeichnet einen unerwarteten Fehler. Die CSV wird ausschließlich gelesen und bleibt unverändert. Der separate Validierungsendpunkt bleibt für API-Nutzer verfügbar; das Popup verwendet ausschließlich `/api/contact` mit integrierter Kundenprüfung. ## Grenzen der Simulation Der E-Mail-Abgleich ignoriert Groß-/Kleinschreibung und äußere Leerzeichen. Er bestätigt einen Eintrag in der Buchungsdatei, nicht den Besitz des Postfachs. Die Prüfantwort ist kein Anmeldetoken und gibt keine Buchungsdetails zurück. Vor einem öffentlichen Produktiveinsatz wären eine Anmeldung oder Bestätigung per E-Mail sowie ein Schutz vor automatisierten Adressabfragen erforderlich. Kontaktmails verwenden fest `127.0.0.1:1025`, unabhängig von einer eventuell externen SMTP-Konfiguration des Buchungsablaufs. Mailpit muss deshalb auf demselben Host laufen. Kunden- und Berateradresse sind simulierte Empfänger; es erfolgt kein externer Versand. SMTP-Annahme ist keine reale Zustellbestätigung. Kontaktanfragen werden nicht dauerhaft gespeichert und haben keinen Idempotenzschlüssel. Doppelklicks sind während des Versands gesperrt. Bei Verbindungsabbrüchen oder Teilfehlern kann bereits eine Nachricht vorliegen: vor manuellem Wiederholen Mailpit prüfen. Es gibt keinen automatischen Neuversand. ## Prüfungen - `npm test`: API, CSV-Abgleich, erneute Validierung beim Absenden, ungültige Eingaben, SMTP-Empfänger und Teilfehler; isolierte temporäre CSV-Dateien. - `npm run test:browser`: vollständiger Ablauf einschließlich abgefangener externer Weiterleitung, unbekannter Adresse, geänderter E-Mail, Versandfehler, mobiler Darstellung, Fokus und Escape; zusätzlich bestehende Buchungstests. - `npm run build` und `git diff --check`. - Lokaler SMTP-Smoke-Test mit Mailpit: beide Empfänger und Simulationshinweis in den empfangenen Nachrichten geprüft. Chromium muss für Playwright installiert sein (`npx playwright install chromium`). In dieser Arbeitsumgebung liegt der Testbrowser unter `/tmp/trihub-playwright`; hier mit `PLAYWRIGHT_BROWSERS_PATH=/tmp/trihub-playwright npm run test:browser` starten. Der globale Formatcheck hat bereits bestehende Abweichungen in `src/main.js` und `src/components/navigationsbar/README.md`; die Kontaktdateien sind formatiert. Der bestehende Gesamt-Build meldet außerdem fehlende ältere Schriftdateien aus dem globalen Stylesheet. Das Kontakt-Popup verwendet eigene lokale Schriftdateien. ### Ergebnis dieser Umsetzung 31 API-/Servicetests und alle vier Kontakt-Browsertests bestanden. Build, lokale Mailpit-Prüfung und Formatierung der geänderten Dateien bestanden. Die bestehenden Buchungs-Browsertests sind teilweise nicht mehr an das schon vorhandene Buchungs-Popup angepasst: Sie suchen Formularfelder, ohne vorher „Jetzt buchen“ zu klicken. Weitere Bestandsfälle erwarten einen nicht mehr vorhandenen Startseiten-Link oder eine Seitennavigation, wo bereits ein Popup geöffnet wird. Beim ursprünglichen Gesamtlauf bestanden 7 von 15 Browsertests; die 8 Fehler betrafen ausschließlich die unveränderten Buchungstests. Die gezielten Kontakttests werden nach Anpassungen separat ausgeführt. `git diff --check` ist für die eigenen Änderungen sauber; die zuvor bereits geänderte `bookings.csv` erzeugt separat CRLF-Whitespace-Hinweise und wurde von dieser Umsetzung nicht verändert.