# Schnittstellen zwischen den Modulen Verträge, auf die sich mehrere Etappen stützen. Wer eine dieser Schnittstellen implementiert, hält sich exakt an die hier beschriebene Signatur; wer sie benutzt, darf sich darauf verlassen. --- ## 1. Dateiablage — `src/lib/speicher/index.ts` Zwei Treiber hinter einer Schnittstelle: `datei` (lokales Verzeichnis, für Entwicklung und kleine Installationen) und `s3` (S3-kompatibler Speicher in der EU). Gesteuert über `SPEICHER_TREIBER` bzw. das Vorhandensein von `S3_ACCESS_KEY_ID`. ```ts export type Ablageziel = { /** Schlüssel im Speicher, z.B. "betreute//dokumente/.pdf" */ schluessel: string groesseBytes: number /** SHA-256 hex — Nachweis der Unverändertheit gegenüber Gericht. */ pruefsumme: string } /** Legt eine Datei ab. Der Schlüssel wird erzeugt, nie vom Nutzer bestimmt. */ export function ablegen( betreuteId: string, bereich: 'dokumente' | 'berichte' | 'exporte' | 'vorschau', dateiname: string, inhalt: Buffer, mimeTyp: string, ): Promise /** Holt eine Datei zurück in den Speicher des Servers. */ export function lesen(schluessel: string): Promise /** * Kurzlebige, signierte URL. Gültigkeit in Sekunden, Standard 300. * Beim Dateitreiber zeigt sie auf eine eigene, ebenfalls signierte Route. */ export function signierteUrl(schluessel: string, sekunden?: number): Promise export function entfernen(schluessel: string): Promise export function existiert(schluessel: string): Promise ``` **Regeln** - Der Schlüssel enthält immer die `betreuteId` als zweites Segment. Jede Leseoperation prüft vorher, dass der angemeldete Benutzer Zugriff auf genau diese `betreuteId` hat. - Uploads werden nie als HTML ausgeliefert. Ausgehende Antworten tragen `Content-Disposition: attachment` und `X-Content-Type-Options: nosniff`. - Erlaubte Typen: `application/pdf`, `image/jpeg`, `image/png`, `image/heic`, `image/webp`, `image/tiff`. Geprüft wird an den **Magic Bytes**, nicht am gemeldeten MIME-Typ und nicht an der Endung. - Größenlimit aus `env.uploadMaxBytes` (Standard 25 MB). --- ## 2. PDF-Erzeugung — `src/lib/pdf/` Grundlage ist `@react-pdf/renderer` (kein Chromium im Container). ### `src/lib/pdf/rahmen.tsx` ```tsx export type Berichtskopf = { titel: string betreute: { name: string; geburtsdatum: Date | null } /** z.B. "Amtsgericht Bayreuth · Az. XVII 123/25" */ behoerde?: string aktenzeichen?: string zeitraumVon?: Date | null zeitraumBis?: Date | null stichtag?: Date | null rechtsgrundlage?: string erstelltAm: Date erstelltVon: string } /** Seitenrahmen mit Kopf, Fußzeile (Seite x von y, Erstelldatum) und * optionalem Unterschriftenfeld. Jeder Bericht benutzt ihn. */ export function Berichtsseite(props: { kopf: Berichtskopf mitUnterschrift?: boolean children: React.ReactNode }): JSX.Element ``` ### `src/lib/pdf/erzeugen.ts` ```ts /** Rendert ein react-pdf-Dokument zu einem Buffer. */ export function pdfErzeugen(dokument: React.ReactElement): Promise /** * Fügt mehrere PDFs zu einem zusammen (Belegmappe, Unterlagenpaket). * Bilder werden vorher in einseitige PDFs gewandelt. * Nutzt pdf-lib. */ export function pdfsZusammenfuehren(teile: Buffer[]): Promise /** Seitenzahlen und Fußzeile über ein zusammengeführtes PDF legen. */ export function paginieren(pdf: Buffer, fusszeile: string): Promise ``` **Regeln** - Schrift: die in `@react-pdf/renderer` eingebauten Standardschriften (Helvetica). Kein Nachladen von Google Fonts. - Jeder Bericht trägt Kopf (Betreute, Aktenzeichen, Gericht/Behörde, Zeitraum), Fußzeile mit Seitenzahl und Erstellungsdatum sowie ein Unterschriftenfeld. - Beträge über `formatCent()`, Daten über `formatDatum()`. --- ## 3. OCR — `src/lib/ocr/index.ts` ```ts export type OcrErgebnis = { text: string status: 'fertig' | 'fehlgeschlagen' | 'nicht_verfuegbar' seitenzahl?: number meldung?: string } /** Ruft Tesseract (deutsch) auf. Nie werfen — Status zurückgeben. */ export function ocrLesen(inhalt: Buffer, mimeTyp: string): Promise /** Ist Tesseract auf diesem System vorhanden? */ export function ocrVerfuegbar(): Promise ``` **Regeln** - Aufruf über das System-Binary `tesseract` (`env.tesseractBefehl`), Sprache `deu`. Im Docker-Image ist es installiert, auf einem Windows- Entwicklungsrechner in der Regel nicht — dann liefert die Funktion `nicht_verfuegbar`, und der Upload funktioniert trotzdem. - PDFs werden vorher mit `pdftoppm` in Bilder gewandelt; fehlt auch das, wird der eingebettete Text des PDFs mit `pdf-lib` gelesen. - Der OCR-Lauf blockiert den Upload nicht: Dokument wird mit `ocrStatus = "ausstehend"` gespeichert, die Erkennung läuft danach. --- ## 4. Fristenableitung — `src/lib/fristen/pflichtfristen.ts` ```ts export type Pflichtfrist = { /** Stabiler Schlüssel, verhindert Dubletten: "anfangsbericht", * "jahresbericht-2027", "aufwandspauschale-2026", … */ systemSchluessel: string titel: string beschreibung: string datum: Date rechtsgrundlage: string typ: 'GESETZLICH' | 'BEHOERDLICH' | 'SELBST_GESETZT' | 'WIEDERVORLAGE' ausschlussfrist: boolean vorwarnungTage: number[] wiederholungRrule?: string } /** Leitet alle Pflichtfristen aus den Stammdaten ab. Rein, ohne Datenbank. */ export function pflichtfristenAbleiten(stammdaten: { bestellungAm: Date | null befreiterBetreuer: boolean ehrenamtlich: boolean pflegegrad: number | null jahreVoraus?: number }): Pflichtfrist[] /** Schreibt sie in die Datenbank, ohne Dubletten (upsert auf * betreuteId + systemSchluessel). */ export function pflichtfristenSchreiben(betreuteId: string): Promise ``` Abzuleiten sind mindestens: | Schlüssel | Frist | Grundlage | |---|---|---| | `vermoegensverzeichnis` | 3 Monate nach Bestellung | § 1835 BGB | | `anfangsbericht` | 3 Monate nach Bestellung | § 1863 Abs. 1 BGB | | `jahresbericht-` | jährlich zum Betreuungsjahr | § 1863 Abs. 3 BGB | | `vermoegensuebersicht-` | jährlich, mit dem Jahresbericht | § 1859, § 1865 BGB | | `aufwandspauschale-` | 6 Monate nach Ende des Betreuungsjahres, **Ausschlussfrist** | § 1878 BGB | | `landespflegegeld-` | 31.12. | Bayerisches Landespflegegeldgesetz | | `zuzahlungsbefreiung-` | jährlich | § 62 SGB V | | `rueckkaufswerte-` | jährlich | selbst gesetzt | | `pflegegrad-pruefung` | konfigurierbar | § 18 SGB XI | **Betreuungsjahr**: läuft ab dem Tag der Bestellung, nicht ab dem Kalenderjahr. Die Aufwandspauschale verfällt sechs Monate nach dessen Ende — diese Frist wird als `ausschlussfrist: true` markiert und im Dashboard rot mit ausdrücklicher Verfallwarnung gezeigt. --- ## 5. Hinweis-Wissensbasis — `daten/hinweise.json` Pflegbare JSON-Datei, kein Hardcoding. ```jsonc { "version": 1, "karten": [ { "schluessel": "wohnraum-aufgeben", "titel": "Wohnung kündigen? Erst das Gericht fragen.", "text": "Die Aufgabe von Wohnraum der betreuten Person ist dem Gericht mitzuteilen und bedarf der Genehmigung.", "rechtsgrundlage": "§ 1833 BGB", "link": "https://www.gesetze-im-internet.de/bgb/__1833.html", "kontext": ["IMMOBILIE", "VORGANG:IMMOBILIE", "VORLAGE:wohnungsaufloesung"], "gewicht": 10 } ] } ``` `kontext` ist eine Liste von Auslösern: ein `Modul`, `VORGANG:`, `VORLAGE:` oder `FRIST:`. Die Oberfläche zeigt passende Karten, sortiert nach `gewicht` absteigend, jede mit ``. --- ## 6. Vorgangs-Vorlagen — `src/lib/vorgaenge/vorlagen.ts` ```ts export type Vorgangsvorlage = { schluessel: string titel: string kategorie: VorgangKategorie beschreibung: string genehmigungspflichtig: boolean rechtsgrundlage?: string /** Vorbefüllte Aufgabenliste. */ aufgaben: { titel: string; beschreibung?: string; tageBisFaellig?: number }[] /** Fristen, die mit angelegt werden. */ fristen?: { titel: string; tageBisFaellig: number; rechtsgrundlage?: string }[] } export const VORLAGEN: Vorgangsvorlage[] ``` Mindestens: `heimeinzug`, `antrag-hilfe-zur-pflege`, `wohnungsaufloesung`, `hausverkauf`, `darlehen-91-sgb-xii`, `versicherungsverwertung`, `pflegegrad-hoeherstufung`, `steuererklaerung`, `widerspruch-bescheid`. --- ## 7. E-Mail — `src/lib/mail.ts` ```ts export function mailSenden(nachricht: { an: string betreff: string text: string }): Promise<{ gesendet: boolean; meldung?: string }> ``` Ist kein SMTP konfiguriert, wird nichts gesendet und `gesendet: false` zurückgegeben — ohne Fehler. Erinnerungen sind eine Zugabe, kein Kernstück; die Fristen stehen auch so im Dashboard.