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:
| Datei | Funktion | Aufgabe |
|---|---|---|
src/storage/leadRepository.ts | createStorageRepository(storage, workspaceId) | Speichert Leads als JSON in storage unter einem Schlüssel, der die workspaceId enthält |
src/domain/leadValidation.ts | validateLead(input) | Prüft Pflichtfelder, E-Mail, Status, Datumsformat; liefert Feldmeldungen |
src/domain/lead.ts | canTransition(from, to), transitionLead(lead, to) | Erlaubte Statuswechsel laut ALLOWED_TRANSITIONS; neues Objekt statt Mutation |
src/domain/leadFilters.ts | filterLeads(leads, filter) | Nach Status und überfälligem Follow-up filtern; neues Array |
src/components/LeadFilters.tsx | Komponente | select 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
| Kriterium | Was geprüft wird |
|---|---|
| C02-M3-01 | save dann list() liefert den Lead; list() gibt eine Kopie zurück (Verändern des Arrays ändert nichts im Repository). |
| C02-M3-02 | Nach createStorageRepository mit demselben storage sind die Daten noch da. |
| C02-M3-03 | Gleiche id überschreibt; get findet; remove entfernt. |
| C02-M3-04 | validateLead 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-05 | loadSeedLeads sortiert genau die Zeilen 7, 13, 21, 29, 36, 44 aus und behält 44 Leads. Das klappt automatisch, sobald validateLead stimmt. |
| C02-M3-06 | canTransition erlaubt new nach contacted, contacted nach qualified, qualified nach won, lost nach new. |
| C02-M3-07 | won nach new ist verboten; transitionLead wirft dann einen Fehler; bei erlaubtem Wechsel bleibt das Original unverändert. |
| C02-M3-08 | filterLeads nach Status liefert ein neues Array; ohne Filter alle Leads. |
| C02-M3-09 | filterLeads 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
- Beginne mit
validateLead. Schreib die Regeln zuerst als Liste in eigenen Worten, dann lass dir Code vorschlagen. Teste mitnpm run bic -- check 3und schau nur auf C02-M3-04 und C02-M3-05. - Dann
canTransitionundtransitionLead. Frage deinen Agenten: "Was ist der Unterschied zwischenlead.status = tound{ ...lead, status: to }, und warum verlangt der Test das zweite?" - Dann
createStorageRepository. Idee: eine kleinereadAll- und einewriteAll-Hilfsfunktion, der Rest baut darauf auf. Der Schlüssel muss dieworkspaceIdenthalten (Mission 5 prüft, dass zwei Arbeitsbereiche getrennt sind). - Dann
filterLeads. Für "überfällig" verwendest duisOverdueausfollowUpSchedule.ts. Sie ist vorgegeben; für die Testdaten dieser Mission funktioniert sie. Merke dir diesen Satz für Mission 5. - Baue
LeadFiltersund binde alles inApp.tsxein. Im Browser: Filter wechseln, Seite neu laden, Status eines Leads ändern. npm run bic -- check 3, Commit, KI-PROTOKOLL,npm run bic -- submit 3.
Typische Stolpersteine
JSON.parse(null)wirft. Prüfestorage.getItem(key) === nullzuerst.list()gibt dasselbe Array zurück, das du intern hältst. Gib[...leads]zurück.- Du prüfst die E-Mail mit einem riesigen regulären Ausdruck aus dem Internet. Ein "@" mit einem "." dahinter reicht in diesem Kurs; mehr macht es nur fehleranfälliger.
ALLOWED_TRANSITIONS[from].includes(to)ist die ganze Logik voncanTransition. Wenn dein Agent zwanzig Zeilen vorschlägt, frag nach.
Erklären
- Was speichert dein Repository genau in localStorage? Öffne die Browser-Entwicklerwerkzeuge (F12, Reiter Application oder Speicher) und zeig es.
- Warum darf ein gewonnener Lead nicht zurück auf "new"? Was wäre im echten Vertrieb die Folge?
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.