Files
betreuungs-cockpit/README.md
T
Florian Wilde 42eb242767 feat: Update-Skript, das die vergessenen Schritte mitnimmt
Bisher war Aktualisieren eine Folge von Befehlen aus der Anleitung, und
zwei davon vergisst man zuverlaessig: die Sicherung vorher und die beiden
SQL-Skripte hinterher. Genau die hat es hier schon gekostet.

./scripts/update.sh macht das in einem Durchgang - mit zwei
Eigenschaften, auf die es ankommt:

Bis zur Rueckfrage wird nichts veraendert. Der erste Entwurf hat die
eigenen Aenderungen beiseite gelegt und danach am Serverzugriff
abgebrochen - Halbzustand, aus dem man erst wieder herausfinden muss.
Jetzt laufen Pruefung, Abgleich und Changelog-Anzeige vollstaendig
durch, bevor irgendetwas angefasst wird.

Eigene Aenderungen an versionierten Dateien blockieren nicht. Im Betrieb
passt man die docker-compose.yml an, bis etwas laeuft; genau diese
Anpassungen sind spaeter oft schon eingearbeitet. Das Skript legt sie mit
git stash beiseite - rueckholbar, nicht weggeworfen - und sagt, wie man
sie zurueckbekommt. Wer sie wirklich loswerden will, nimmt --verwerfen.
Die .env bleibt immer unangetastet.

Schlaegt die Sicherung fehl, wird nicht aktualisiert. Schlaegt der Bau
fehl, nennt das Skript den Weg zurueck.

Nebenbei behoben: Der Port der MinIO-Konsole stand fest auf 9001. Lief
dort schon etwas, startete der ganze Stapel nicht. Jetzt ueber
MINIO_KONSOLE_PORT einstellbar - dieselbe Moeglichkeit, die APP_PORT
schon hatte. Aufgefallen beim Aufsetzen einer Testumgebung.

Etappe: 8
Phase: Haertung
2026-08-22 15:09:01 +02:00

574 lines
22 KiB
Markdown

# Betreuungs-Cockpit
Arbeits- und Dokumentationswerkzeug für die ehrenamtliche rechtliche Betreuung
eines Angehörigen: Fristen, Vorgänge, Belege, Vermögen, Sozialhilfe, Berichte.
> **Keine Rechtsberatung.** Die Anwendung rechnet Fristen aus, sortiert
> Unterlagen und erzeugt Berichte. Sie kennt den Einzelfall nicht. Jeder
> Hinweis mit Paragraphen-Bezug trägt den sichtbaren Vermerk, dass er die
> Rückfrage bei Betreuungsgericht, Sozialhilfeträger oder Anwalt **nicht**
> ersetzt. Wer sich darauf verlässt, statt beim Rechtspfleger nachzufragen,
> kann Geld und Ansprüche verlieren.
---
## Inhalt
- [Was die Anwendung kann](#was-die-anwendung-kann)
- [Die drei Dokumente, die du wirklich brauchst](#die-drei-dokumente-die-du-wirklich-brauchst)
- [Schnellstart zum Ausprobieren](#schnellstart-zum-ausprobieren)
- [Produktivbetrieb mit Docker Compose](#produktivbetrieb-mit-docker-compose)
- [Reverse Proxy](#reverse-proxy)
- [Alternative: Vercel und Neon](#alternative-vercel-und-neon)
- [Sicherheit und Datenschutz](#sicherheit-und-datenschutz)
- [Was rechtlich zu tun ist](#was-rechtlich-zu-tun-ist)
- [Sicherung und Wiederherstellung](#sicherung-und-wiederherstellung)
- [Schlüsselverwaltung](#schlüsselverwaltung)
- [Entwicklung](#entwicklung)
- [Bauprotokoll](#bauprotokoll)
---
## Was die Anwendung kann
| Bereich | Kurz gesagt |
|---|---|
| **Heute** | Eine Seite, die morgens die Frage beantwortet, was ansteht. Fristen-Ampel, wer am Zug ist, offene Nachforderungen des Bezirks, ungeprüfter Posteingang, Kennzahlen. |
| **Fristen** | Die gesetzlichen Pflichtfristen entstehen von selbst aus dem Bestellungsdatum. Liste, Kalender, ICS-Feed zum Abonnieren. |
| **Vorgänge** | Fallakte je Angelegenheit, mit Vorlagen (Heimeinzug, Hausverkauf, Widerspruch …) und der festen Ablaufkette für genehmigungspflichtige Geschäfte. |
| **Dokumente** | Foto oder Scan hochladen, Texterkennung, Volltextsuche über alle Schreiben, Posteingangsbuch, Belegmappe als eine PDF. |
| **Vermögen & Kasse** | Kassenbuch mit Belegzwang, Konten, Vermögensposten mit Wert-Historie, Vermögensverzeichnis und Rechnungslegung als PDF. |
| **Sozialhilfe** | Antragsakte, Nachforderungs-Tracker, Bescheid-Prüfung mit Rechtsbehelfsfrist, Heimkosten-Gegenüberstellung, Schonvermögens-Bewertung. |
| **Immobilie** | Stammdaten, Belastungen, Wert-Historie, Status im Sozialhilfeverfahren, Pflichten-Checkliste bei Leerstand. |
| **Verträge** | Versicherungen und laufende Verträge mit Kündigungsfristen, Rückkaufswert-Anfrage, Kündigungs-Assistent. |
| **Heim & Pflege** | Heimvertrag, Leistungszuschläge, Pflegegrad-Historie, Gesundheitsverlauf mit COPD-Bezug. |
| **Kontakte** | Verzeichnis aller Stellen, Ein-Klick-Kontaktprotokoll, Auswertung für den Jahresbericht. |
| **Berichte** | Vermögensverzeichnis, Jahresbericht, Rechnungslegung, Belegmappe, Unterlagenpaket für den Bezirk, Vollexport. |
---
## Die drei Dokumente, die du wirklich brauchst
| Wofür | Wo |
|---|---|
| **Die Anwendung aufsetzen** — auf einem Server, der Docker und einen Proxy schon hat, mit Kontrolle nach jedem Schritt | [`docs/INSTALLATION.md`](docs/INSTALLATION.md) |
| **Täglich damit arbeiten** — die zwölf Handgriffe, der Jahresrhythmus, was tun wenn etwas schiefgeht | [`docs/ANLEITUNG.md`](docs/ANLEITUNG.md), in der Anwendung unter `/anleitung` |
| **Wissen, was sich ändert** — was neu ist, was du vor einem Update beachten musst | [`CHANGELOG.md`](CHANGELOG.md) |
Dazu: [`docs/ROADMAP.md`](docs/ROADMAP.md) für das, was als Nächstes
kommt, und [`docs/BAUPROTOKOLL.md`](docs/BAUPROTOKOLL.md) für die Frage,
wie das hier entstanden ist.
Der Rest dieser Datei ist die Kurzfassung für alle, die schon wissen, was
sie tun.
---
## Schnellstart zum Ausprobieren
Voraussetzungen: **Node.js ≥ 20.11**, **Docker** und **Git**.
```bash
git clone https://git.wilde-server.de/florian.wilde/betreuungs-cockpit.git
cd betreuungs-cockpit
npm install
```
**1. Datenbank und Objektspeicher starten.** Beide horchen nur auf
`127.0.0.1`, gehen also nicht ins Netz hinaus:
```bash
docker compose -f docker-compose.dev.yml up -d
```
**2. Konfiguration anlegen.** Die Geheimnisse erzeugst du dir:
```bash
cp .env.example .env
npm run schluessel # gibt AUTH_SECRET, ENC_KEY_V1, INDEX_KEY aus
```
Trage die ausgegebenen Werte in die `.env` ein und setze:
```
DATABASE_URL="postgresql://cockpit:entwicklung@localhost:5433/betreuungs_cockpit?schema=public"
```
**3. Datenbank einrichten:**
```bash
npx prisma migrate dev --name fundament
npm run db:sql # Volltextsuche und Audit-Schutz
npm run db:seed
```
**4. Starten:**
```bash
npm run dev
```
Dann [http://localhost:3000](http://localhost:3000) öffnen. Beim ersten Aufruf
führt dich ein Assistent in drei Schritten durch Zugang, zweiten Faktor und
die Stammdaten der betreuten Person.
### Mit Beispieldaten ansehen
Um zu sehen, wie die Anwendung mit Inhalt aussieht:
```bash
echo "SEED_DEMO=true" >> .env
npm run db:seed
```
Das legt den Fall „Erika Musterfrau" an — COPD GOLD III, Pflegegrad 3,
Heimeinzug Januar 2026, leerstehendes Haus in Bindlach, zwei Versicherungen,
laufender Antrag beim Bezirk Oberfranken mit acht Nachforderungen.
**Der Demofall wird in der Produktion nicht angelegt** und lässt sich später
über den Papierkorb vollständig entfernen.
---
## Produktivbetrieb mit Docker Compose
> **Zum ersten Mal?** Nimm die [Schritt-für-Schritt-Anleitung](docs/INSTALLATION.md).
> Sie fängt beim leeren Server an, hat nach jedem Abschnitt eine Kontrolle
> und endet mit einer Abnahmeliste. Was hier steht, ist die Kurzfassung.
Das Compose-Setup bringt drei Dienste mit: die Anwendung, PostgreSQL und
MinIO als S3-kompatiblen Objektspeicher, dazu einen Sicherungsdienst.
**Einen Reverse Proxy enthält es bewusst nicht** — er ist auf dem Zielserver
bereits vorhanden. Die Anwendung horcht nur auf `127.0.0.1:3000`.
**Die Datenbank hat keine Portfreigabe** und ist ausschließlich über das
interne Docker-Netz erreichbar.
```bash
# 1. Konfiguration
cp .env.example .env
npm run schluessel
# In der .env mindestens setzen:
# POSTGRES_PASSWORD, AUTH_SECRET, AUTH_URL (die echte öffentliche Adresse),
# ENC_KEY_V1, INDEX_KEY,
# S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, SICHERUNG_PASSWORT
# 2. Bauen und starten
docker compose up -d --build
# 3. Datenbank vorbereiten
# (die Migrationen laufen beim Start der Anwendung automatisch;
# die beiden SQL-Skripte müssen einmalig dazu)
docker compose exec app node node_modules/prisma/build/index.js db execute \
--schema prisma/schema.prisma --file prisma/sql/volltextsuche.sql
docker compose exec app node node_modules/prisma/build/index.js db execute \
--schema prisma/schema.prisma --file prisma/sql/audit-schutz.sql
# 4. Bucket im Objektspeicher anlegen
docker compose exec minio mc alias set lokal http://localhost:9000 \
"$S3_ACCESS_KEY_ID" "$S3_SECRET_ACCESS_KEY"
docker compose exec minio mc mb lokal/betreuungs-cockpit
```
Danach im Browser über die eigene Domain auf `/ersteinrichtung` gehen und den
ersten Zugang anlegen. Die Seite sperrt sich selbst, sobald ein Zugang
existiert.
### Aktualisieren
```bash
./scripts/update.sh
```
Das Skript sichert zuerst, zeigt den Changelog zwischen altem und neuem
Stand, fragt nach, schaltet um, baut neu und führt danach die beiden
SQL-Skripte aus. Schlägt die Sicherung fehl, wird nicht aktualisiert.
`--pruefen` sieht nur nach, ohne etwas zu ändern. Eigene Änderungen an
versionierten Dateien werden vor dem Umschalten beiseite gelegt
(`git stash`), die `.env` bleibt unangetastet.
Einzelheiten und der Weg von Hand: [`docs/INSTALLATION.md`](docs/INSTALLATION.md).
---
## Reverse Proxy
Die Anwendung **beendet selbst kein TLS** und bringt keinen Proxy mit. Sie
horcht standardmäßig auf `127.0.0.1:3000`.
**Läuft der Proxy auf demselben Server**, ist damit alles gesagt: Er zeigt
auf `127.0.0.1:3000`, fertig.
**Läuft er auf einem anderen Server**, setz in der `.env` die Adresse, über
die er herankommt — am besten die eines privaten Netzes oder eines
WireGuard-Tunnels:
```bash
APP_BIND=10.0.0.5
APP_PORT=3000
```
`0.0.0.0` nur, wenn eine Firewall den Port ausschließlich für die IP des
Proxy-Servers öffnet. Zwischen Proxy und Anwendung läuft unverschlüsseltes
HTTP — dieser Weg darf nie durchs offene Netz gehen.
### Was der Proxy leisten muss
| Anforderung | Was ohne sie passiert |
|---|---|
| TLS beenden | Der Browser lehnt Cookies mit `Secure`-Kennzeichen ab, die Anmeldung schlägt stumm fehl |
| `Host` durchreichen | Auth.js vergleicht ihn mit `AUTH_URL` und weist die Anmeldung ab |
| `X-Forwarded-Proto: https` | Die Anwendung baut Adressen mit `http` |
| `X-Forwarded-For` | Die Login-Sperre zählt alle Zugriffe als denselben Anschluss |
| Uploads ≥ 30 MB | Fotos mehrseitiger Bescheide werden abgeschnitten |
| Zeitgrenze ≥ 10 Minuten | Belegmappe, Rechnungslegung und Vollexport brechen ab |
| Antwort nicht puffern | Der Vollexport kommt als Strom |
**Keine eigenen Sicherheitskopfzeilen setzen.** CSP, `X-Frame-Options`,
`nosniff` und HSTS kommen aus der Anwendung. Die CSP enthält eine Nonce,
die pro Anfrage neu ist — eine feste CSP aus dem Proxy blockiert die
eigenen Skripte.
**Fertige Konfigurationen** für Caddy, nginx und Traefik, mit Zeitgrenzen
und den Gegenproben, stehen in
[`docs/INSTALLATION.md`](docs/INSTALLATION.md), Schritt 7.
---
## Alternative: Vercel und Neon
Technisch möglich, hier aber **nicht empfohlen**. Der Grund ist nicht
technischer Natur:
> Vercel Inc. und Neon Inc. sind US-Unternehmen. Auch bei Wahl einer
> EU-Region unterliegen sie potenziell dem US-CLOUD-Act. Für
> Gesundheitsdaten einer nicht selbst entscheidungsfähigen Person — also
> besondere Kategorien nach Art. 9 DSGVO — ist das eine Abwägung, die man
> bewusst treffen und dokumentieren muss. Ein eigener Server in Deutschland
> nimmt einem diese Abwägung ab.
Wenn du es dennoch willst:
1. **Neon**-Projekt in der Region `eu-central-1` (Frankfurt) anlegen,
Verbindungszeichenfolge als `DATABASE_URL` hinterlegen.
2. In **Vercel** unter *Settings → Functions* die Region `fra1` festlegen.
3. Alle Umgebungsvariablen aus `.env.example` als Vercel-Secrets eintragen.
`ENC_KEY_V1` und `INDEX_KEY` gehören zwingend dazu.
4. **Objektspeicher**: nicht Vercel Blob, sondern ein EU-Anbieter, z. B.
Hetzner Object Storage (Falkenstein oder Nürnberg). `SPEICHER_TREIBER=s3`.
5. **OCR und PDF laufen nicht auf Vercel.** Tesseract braucht ein System-
Binary, das es in der Serverless-Umgebung nicht gibt. Setze `OCR_AKTIV=false`
oder betreibe einen kleinen Worker daneben. PDF-Erzeugung mit
`@react-pdf/renderer` funktioniert, kann bei großen Belegmappen aber ins
Zeitlimit laufen.
6. **Migrationen** laufen nicht automatisch: `npx prisma migrate deploy` als
Build-Schritt oder von Hand.
7. Mit **beiden** Anbietern ist ein Auftragsverarbeitungsvertrag nach
Art. 28 DSGVO abzuschließen.
---
## Sicherheit und Datenschutz
Hier liegen Gesundheits- und Finanzdaten einer Person, die nicht selbst
entscheiden kann. Was dafür umgesetzt ist:
**Verschlüsselung**
- Transport über TLS (durch den Reverse Proxy).
- Zusätzlich **anwendungsseitige Feldverschlüsselung** mit AES-256-GCM für
Gesundheitsdaten, Diagnosen, den ermittelten Willen, IBANs,
Versicherungsnummern, Renten- und Steuernummern sowie alle Freitext-Notizen.
Der Schlüssel liegt in der Umgebung, nicht in der Datenbank — ein
Datenbank-Dump allein nützt niemandem etwas.
- Der Feldname wird mitauthentifiziert: ein Geheimtext lässt sich nicht von
„Diagnosen" in „Notizen" umhängen.
- **Blindindex** (HMAC-SHA256 mit eigenem Schlüssel) für die IBAN, damit die
Frage „kenne ich dieses Konto schon?" beantwortbar bleibt, ohne sie im
Klartext zu speichern.
**Zugang**
- Passwörter mit **Argon2id** (19 MiB, t=2, p=1 — OWASP-Empfehlung).
- **Zweiter Faktor ist Pflicht** und lässt sich nicht abschalten. TOTP nach
RFC 6238, kompatibel mit Aegis, 2FAS, Bitwarden, 1Password.
- **Rate-Limiting** am Login, nach E-Mail *und* nach Anschluss. Die Sperre
wird geprüft, bevor Argon2 überhaupt rechnet.
- Die Antwortzeit ist auf eine Mindestdauer gestreckt, damit sie nicht
verrät, welche E-Mail-Adressen existieren.
- **Sitzungsablauf** nach 30 Minuten Leerlauf. Für Export, endgültiges
Löschen und die Benutzerverwaltung ist eine **erneute Anmeldung** nötig.
- Passwortwechsel beendet **alle** offenen Sitzungen, auch auf fremden
Geräten.
**Rollen**
- `betreuer` — voller Zugriff auf die zugeordneten Betreuten.
- `mitleser` — nur lesend, und nur die ausdrücklich je Modul freigegebenen
Bereiche (z. B. Geschwister, die den Kontaktverlauf sehen dürfen, aber
nicht die Finanzen).
- `admin` — Benutzerverwaltung.
Geprüft wird **immer serverseitig gegen die Betreuten-ID**, nie nur im UI
ausgeblendet.
**Audit-Log**
Jede schreibende Aktion und jeder Dokumentzugriff wird protokolliert: wer,
wann, was, alter Wert → neuer Wert. Die Änderungen liegen verschlüsselt.
Das Log ist **unveränderlich**: ein Datenbank-Trigger weist UPDATE und DELETE
ab — auch bei direktem Zugriff mit `psql`. Damit ist es der Nachweis
gegenüber Betreuungsgericht und möglichen Miterben. Nachprüfen lässt sich das
so:
```bash
docker compose exec db psql -U cockpit betreuungs_cockpit \
-c "UPDATE audit_log SET entitaet='x';"
# ERROR: Das Audit-Log ist unveraenderlich. UPDATE auf audit_log ist nicht zulaessig.
```
**Content-Security-Policy**
Streng gesetzt, mit einer Nonce pro Anfrage. Keine externen Schriften, kein
CDN, keine Analytics, keine Tracker. Der QR-Code für den zweiten Faktor wird
serverseitig erzeugt und als Data-URI eingebettet, nicht von einem fremden
Dienst geholt.
**Uploads**
Der Dateityp wird an den **Magic Bytes** geprüft, nicht an der Endung und
nicht am gemeldeten MIME-Typ. Erlaubt sind PDF, JPEG, PNG, WEBP, TIFF, HEIC.
Ausgeliefert wird nur über kurzlebige signierte URLs, immer mit
`Content-Disposition: attachment` und `X-Content-Type-Options: nosniff` —
ein hochgeladenes HTML-Dokument kann also nie als Seite ausgeführt werden.
**Löschen**
Papierkorb mit 30 Tagen, danach echtes Löschen. Endgültiges Löschen erfordert
eine frische Anmeldung.
---
## Was rechtlich zu tun ist
Das erledigt keine Software für dich:
1. **Auftragsverarbeitungsvertrag nach Art. 28 DSGVO** mit dem Hoster und mit
dem Betreiber des Objektspeichers. Bei Hetzner findest du ihn im
Kundenkonto unter *Verträge → AV-Vertrag*, bei Netcup und IONOS ähnlich.
Ohne AV-Vertrag ist der Betrieb formal rechtswidrig.
2. **Verarbeitungsverzeichnis** nach Art. 30 DSGVO. Ein Textbaustein liegt in
[`docs/verarbeitungsverzeichnis.md`](docs/verarbeitungsverzeichnis.md) und
muss nur ergänzt werden.
3. **Datenschutzhinweise** für die betreute Person und für Mitleser:
[`docs/datenschutzhinweise.md`](docs/datenschutzhinweise.md).
4. **Server in Deutschland oder mindestens in der EU.** Keine US-Dienste im
Datenpfad.
5. Bei einer Datenpanne: **Meldepflicht binnen 72 Stunden** (Art. 33 DSGVO)
an die zuständige Aufsichtsbehörde — in Bayern das BayLDA.
---
## Sicherung und Wiederherstellung
### Was automatisch läuft
Der Dienst `sicherung` legt **täglich um 03:15 Uhr** einen Stand nach
`./backups`:
- `pg_dump` im eigenen Format, damit auch einzelne Tabellen zurückholbar sind
- verschlüsselt mit AES-256 und PBKDF2 aus `SICHERUNG_PASSWORT`
- SHA-256-Prüfsumme daneben
- **Selbstprüfung**: direkt nach dem Schreiben wird geprüft, ob sich die
Datei wieder entschlüsseln lässt. Schlägt das fehl, endet der Lauf mit
Fehler statt mit einem stillen Scheinerfolg.
- Aufbewahrung 30 Tage, der jeweils erste Stand eines Monats bleibt dauerhaft
Von Hand anstoßen:
```bash
docker compose exec sicherung /bin/sh /sicherung.sh
```
### Zurückspielen
```bash
docker compose exec sicherung /bin/sh /wiederherstellung.sh \
/backups/cockpit_2026-08-22_0315.dump.enc
```
Das Skript prüft die Prüfsumme, fragt nach und spielt zurück. Danach:
```bash
docker compose exec app npm run db:sql # Trigger und Indizes erneut anlegen
```
### Wiederherstellung üben
**Ein Backup, das nie zurückgespielt wurde, ist kein Backup.** Nimm dir
einmal im Jahr zwanzig Minuten:
```bash
# 1. Testdatenbank neben der echten anlegen
docker compose exec db createdb -U cockpit probe
# 2. Sicherung dorthin zurückspielen
docker compose exec -e PGDATABASE=probe -e SICHERUNG_OHNE_RUECKFRAGE=ja \
sicherung /bin/sh /wiederherstellung.sh /backups/cockpit_<datum>.dump.enc
# 3. Stichprobe: sind die Daten da?
docker compose exec db psql -U cockpit probe \
-c "SELECT vorname, nachname FROM betreute;" \
-c "SELECT count(*) FROM audit_log;"
# 4. Aufräumen
docker compose exec db dropdb -U cockpit probe
```
Wichtig ist der letzte Schritt in der Anwendung selbst: eine betreute Person
öffnen und nachsehen, ob die **Diagnosen im Klartext** erscheinen. Erst dann
weißt du, dass nicht nur die Datenbank, sondern auch der Schlüssel stimmt.
### Was das Backup NICHT enthält
**Den Verschlüsselungsschlüssel.** `ENC_KEY_V1` liegt in der `.env`, nicht in
der Datenbank. Das ist Absicht: ein gestohlenes Backup ist damit wertlos.
Für dich bedeutet es aber: **ohne diesen Schlüssel ist auch dein eigenes
Backup halb blind.** Diagnosen, IBANs, Versicherungsnummern und Notizen
bleiben unlesbar.
Ebenfalls nicht enthalten: die **Dateien im Objektspeicher**. Die sichere
gesondert, zum Beispiel:
```bash
docker compose exec minio mc mirror lokal/betreuungs-cockpit /backups/dokumente
```
Oder du benutzt regelmäßig den **Vollexport** in der Anwendung — der packt
Dateien, Daten und Berichte in ein ZIP.
---
## Schlüsselverwaltung
`ENC_KEY_V1` ist der wichtigste Wert der ganzen Installation.
**Sichere ihn getrennt vom Backup.** Passwortmanager *und* ausgedruckt bei
den Betreuungsunterlagen. Wenn er verloren geht, sind die verschlüsselten
Felder unwiederbringlich weg — es gibt keine Hintertür.
### Schlüssel wechseln
Die Anwendung kann mehrere Schlüsselversionen gleichzeitig lesen:
```bash
# 1. Neuen Schlüssel erzeugen und als ENC_KEY_V2 in die .env eintragen
npm run schluessel
# 2. Umschalten - ab jetzt wird mit V2 verschlüsselt
ENC_KEY_AKTIV=2
# 3. Neu starten
docker compose up -d
```
**`ENC_KEY_V1` bleibt in der `.env` stehen**, solange noch Daten damit
verschlüsselt sind — sonst sind ältere Datensätze nicht mehr lesbar. Erst
wenn alle Felder auf die neue Version umgeschrieben wurden, darf der alte
Schlüssel weg.
---
## Entwicklung
```bash
npm run dev # Entwicklungsserver
npm run typecheck # TypeScript ohne Ausgabe prüfen
npm run lint
npm test # Vitest: Fachlogik
npm run test:e2e # Playwright: kritische Abläufe
npm run db:studio # Prisma Studio
npm run protokoll # Bauprotokoll aus der Git-Historie
npm run version -- patch # Version anheben, Changelog vorbereiten
```
Konventionen — verbindlich für alle Beiträge:
[`docs/KONVENTIONEN.md`](docs/KONVENTIONEN.md).
Schnittstellen zwischen den Modulen:
[`docs/SCHNITTSTELLEN.md`](docs/SCHNITTSTELLEN.md).
### Bildschirmfotos erzeugen
```bash
node scripts/screenshots.mjs /heute /fristen /vorgaenge
```
Meldet sich mit dem Entwicklungszugang an und fotografiert die Seiten am
Schreibtisch und am Handy, hell und dunkel. Setzt voraus, dass
`SEED_DEV_TOTP_SECRET` in der `.env` steht — das gilt nur außerhalb der
Produktion.
### Betriebswissen, das man einmal gebraucht hat
- **Nicht gleichzeitig `npm run dev` und `npm run build` laufen lassen.**
Beide schreiben nach `.next`; das Ergebnis ist ein Build, dem einzelne
Chunks fehlen, und der Fehler (`Cannot find module './638.js'`) zeigt
nicht auf die Ursache. Im Zweifel `rm -rf .next` und neu bauen.
- **`AUTH_URL` muss exakt der Adresse entsprechen, unter der du die
Anwendung aufrufst** — bis auf Port und Schreibweise. Steht dort
`localhost` und du rufst `127.0.0.1` auf, wird das Sitzungs-Cookie für
die falsche Herkunft gesetzt und die Anmeldung landet stumm wieder auf
dem Formular.
- Der Standalone-Server (`node server.js`, so startet der Container) liest
**keine `.env`**. Alle Variablen kommen von außen — im Compose-Setup
über den Abschnitt `environment:`.
- Beim ersten Start eines Containers laufen die Migrationen automatisch
mit. Die beiden SQL-Skripte (Volltextsuche, Audit-Schutz) sind
**einmalig von Hand** anzustoßen, siehe oben.
### Technische Grundlage
Next.js 15 (App Router) · TypeScript · Tailwind CSS · shadcn/ui-Bausteine ·
PostgreSQL 17 mit Prisma · Auth.js · Zod · Server Actions ·
`@react-pdf/renderer` · Tesseract (deutsch) · Vitest · Playwright
Bewusste Abweichungen von shadcn/ui im Original: Bedienelemente sind 44 px
hoch statt 36 px, Auswahlfelder benutzen das native `<select>`, und
Eingabefelder haben mindestens 16 px Schrift. Alles drei, damit die Anwendung
am Handy zuverlässig zu bedienen ist — im Heim und auf dem Amt.
---
## Bauprotokoll
Jeder Commit trägt die Trailer `Etappe:` und `Phase:`. Daraus lässt sich
nachvollziehen, wie lange welcher Bauabschnitt gedauert hat:
```bash
npm run protokoll # Tabelle auf der Konsole
npm run protokoll:md # Markdown für docs/BAUPROTOKOLL.md
```
Der aktuelle Stand liegt in [`docs/BAUPROTOKOLL.md`](docs/BAUPROTOKOLL.md) —
dort stehen auch die Laufzeiten der parallel gebauten Etappen und die
Fehler, die während des Baus aufgefallen sind.
---
## Stand
| | |
|---|---:|
| Zeilen TypeScript/TSX | 71.051 |
| Prisma-Modelle | 24 |
| Seiten | 46 |
| Unit-Tests (Vitest) | 458 |
| End-to-End-Tests (Playwright) | 34 |
| TypeScript-Fehler | 0 |
Alle acht Etappen sind umgesetzt. Der Produktionsbuild ist gegen den
fertigen Standalone-Server geprüft: strenge CSP aktiv, keine
Konsolenfehler, keine Anfrage an einen fremden Host.