Mission 3: Daten und Regeln beherrschen

Planzeit: 5 Stunden. Achievements: A04 Daten im Griff (100 XP) bei 5 von 5 Datentests, A05 Regeln funktionieren (100 XP) bei 4 von 4 Regeltests.

Ziel

Die App hält Daten und Zustände konsistent: Speichern überlebt das Neuladen, ungültige Eingaben werden abgelehnt, Statuswechsel folgen festen Regeln, Filter funktionieren. Das ist der Kern jeder Geschäftsanwendung.

Was du baust

Vier Funktionen, die im Starter mit TODO Mission 3 markiert sind, plus die Filterleiste:

DateiFunktionAufgabe
src/storage/leadRepository.tscreateStorageRepository(storage, workspaceId)Speichert Leads als JSON in storage unter einem Schlüssel, der die workspaceId enthält
src/domain/leadValidation.tsvalidateLead(input)Prüft Pflichtfelder, E-Mail, Status, Datumsformat; liefert Feldmeldungen
src/domain/lead.tscanTransition(from, to), transitionLead(lead, to)Erlaubte Statuswechsel laut ALLOWED_TRANSITIONS; neues Objekt statt Mutation
src/domain/leadFilters.tsfilterLeads(leads, filter)Nach Status und überfälligem Follow-up filtern; neues Array
src/components/LeadFilters.tsxKomponenteselect mit data-testid="filter-status", Checkbox filter-overdue

Danach stellst du App.tsx um: Repository aus createStorageRepository(window.localStorage, "demo") statt aus dem Arbeitsspeicher, Beispieldaten nur beim ersten Start einspielen, Filterleiste über der Liste.

Der Vertrag mit den Tests

KriteriumWas geprüft wird
C02-M3-01save dann list() liefert den Lead; list() gibt eine Kopie zurück (Verändern des Arrays ändert nichts im Repository).
C02-M3-02Nach createStorageRepository mit demselben storage sind die Daten noch da.
C02-M3-03Gleiche id überschreibt; get findet; remove entfernt.
C02-M3-04validateLead liefert ok: false mit errors.company und errors.email bei leerer Firma und E-Mail ohne "@"; ein gültiger Lead liefert ok: true und keine Fehler.
C02-M3-05loadSeedLeads sortiert genau die Zeilen 7, 13, 21, 29, 36, 44 aus und behält 44 Leads. Das klappt automatisch, sobald validateLead stimmt.
C02-M3-06canTransition erlaubt new nach contacted, contacted nach qualified, qualified nach won, lost nach new.
C02-M3-07won nach new ist verboten; transitionLead wirft dann einen Fehler; bei erlaubtem Wechsel bleibt das Original unverändert.
C02-M3-08filterLeads nach Status liefert ein neues Array; ohne Filter alle Leads.
C02-M3-09filterLeads mit overdueOn: "2026-03-15" liefert die Leads mit Follow-up im Januar 2026 und Dezember 2025, nicht den im April und nicht den ohne Datum.

Die Validierungsregeln stehen als Kommentar in leadValidation.ts. Die sechs fehlerhaften Beispielzeilen haben: leere E-Mail (7), E-Mail ohne "@" (13), nur Leerzeichen als Firma (21), Datum im Format 15.03.2026 (29), unbekannter Status (36), leere E-Mail und leerer Kontaktname (44).

Schritte

  1. Beginne mit validateLead. Schreib die Regeln zuerst als Liste in eigenen Worten, dann lass dir Code vorschlagen. Teste mit npm run bic -- check 3 und schau nur auf C02-M3-04 und C02-M3-05.
  2. Dann canTransition und transitionLead. Frage deinen Agenten: "Was ist der Unterschied zwischen lead.status = to und { ...lead, status: to }, und warum verlangt der Test das zweite?"
  3. Dann createStorageRepository. Idee: eine kleine readAll- und eine writeAll-Hilfsfunktion, der Rest baut darauf auf. Der Schlüssel muss die workspaceId enthalten (Mission 5 prüft, dass zwei Arbeitsbereiche getrennt sind).
  4. Dann filterLeads. Für "überfällig" verwendest du isOverdue aus followUpSchedule.ts. Sie ist vorgegeben; für die Testdaten dieser Mission funktioniert sie. Merke dir diesen Satz für Mission 5.
  5. Baue LeadFilters und binde alles in App.tsx ein. Im Browser: Filter wechseln, Seite neu laden, Status eines Leads ändern.
  6. npm run bic -- check 3, Commit, KI-PROTOKOLL, npm run bic -- submit 3.

Typische Stolpersteine

Erklären

Hinweise

missions/hints/03-1.md (Validierung), missions/hints/03-2.md (Repository), missions/hints/03-3.md (Statuswechsel und Filter).

Weiter mit missions/04-schnittstelle.md.