# 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 | |---|---| | **Einen Server aufsetzen** — von der leeren Maschine bis zur laufenden Anwendung, 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 git pull docker compose up -d --build ``` Die Migrationen laufen beim Start des Anwendungsbehälters mit. **Vorher ein Backup anstoßen** (siehe unten) — eine Migration lässt sich nicht zurückrollen. --- ## Reverse Proxy Die Anwendung erwartet, dass der Proxy TLS beendet und die echte Client-IP weiterreicht. Sie liest `X-Forwarded-For` bzw. `X-Real-IP` für das Rate-Limiting am Login und für das Audit-Log (dort nur als Hash). **Caddy** ```caddyfile cockpit.example.de { encode zstd gzip reverse_proxy 127.0.0.1:3000 # Uploads: Fotos von Behördenschreiben können groß sein. request_body { max_size 30MB } } ``` **nginx** ```nginx server { listen 443 ssl http2; server_name cockpit.example.de; ssl_certificate /etc/letsencrypt/live/cockpit.example.de/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/cockpit.example.de/privkey.pem; client_max_body_size 30M; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` **Traefik** (Labels am `app`-Dienst) ```yaml labels: - traefik.enable=true - traefik.http.routers.cockpit.rule=Host(`cockpit.example.de`) - traefik.http.routers.cockpit.tls.certresolver=letsencrypt - traefik.http.services.cockpit.loadbalancer.server.port=3000 ``` In allen Fällen muss `AUTH_URL` in der `.env` auf die öffentliche Adresse zeigen (`https://cockpit.example.de`), sonst schlägt die Anmeldung fehl. --- ## 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_.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 `