diff --git a/server/pruef-entwicklung.mjs b/server/pruef-entwicklung.mjs
index 440f7014..c438c92d 100644
--- a/server/pruef-entwicklung.mjs
+++ b/server/pruef-entwicklung.mjs
@@ -469,6 +469,55 @@ console.log("\n=== Punkte je Person aussuchen ===");
ok(vonHand.status === 200 && vonHand.daten?.zugeteilt === 5,
`die rechte Hand sucht ebenfalls aus (HTTP ${vonHand.status}, ${vonHand.daten?.zugeteilt})`);
+ /* ---- 4b. EINZELN ZUTRAGEN, DIREKT AM PUNKT (25.09.2026) --------
+
+ Filipe: „ich will sofort über diese aufgaben da spezifisch
+ zuteilen kann bitte. bei allen personnen."
+
+ Das Auswahlfenster setzt die GANZE Liste; dieser Weg setzt EINEN
+ Punkt. Der Unterschied ist nicht nur Bequemlichkeit: Wuerde der
+ Knopf am Punkt die ganze Liste schicken, loeschte er die
+ Zutragung, die jemand anderes eine Sekunde vorher gemacht hat.
+ Genau das wird hier gemessen -- erst zutragen, dann pruefen, dass
+ die vorherigen unberuehrt sind. */
+ {
+ const einer = alle[20].schluessel;
+ const vorZahl = (await karte(dogi.keks))?.gesamt || 0;
+ const weg = (an, s = einer, keks = dogi.keks) =>
+ hole(`/workspace/api/entwicklung/person/${idModi}/punkt/${s}`,
+ { host: CREW, keks, methode: "PUT", rumpf: JSON.stringify({ an }) });
+
+ const a1 = await weg(true);
+ ok(a1.status === 200 && a1.daten?.zugetragen === true,
+ `ein einzelner Punkt laesst sich direkt am Punkt zutragen (HTTP ${a1.status})`);
+ ok(a1.daten?.gesamt === vorZahl + 1,
+ `und die neue Zahl kommt vom Server zurueck (${vorZahl} -> ${a1.daten?.gesamt})`);
+
+ const k1 = await karte(dogi.keks);
+ const markiert1 = (k1?.bloecke || []).flatMap((b) => b.punkte)
+ .filter((pt) => pt.zugetragen).map((pt) => pt.schluessel);
+ ok(markiert1.includes(einer) && markiert1.length === vorZahl + 1,
+ `die vorher zugetragenen bleiben unberuehrt (${markiert1.length})`);
+
+ /* ZWEIMAL DASSELBE IST KEIN FEHLER. Wer zweimal tippt oder zwei
+ Fenster offen hat, bekommt denselben Zustand -- nicht eine
+ Absage und nicht einen doppelten Eintrag. */
+ const a2 = await weg(true);
+ ok(a2.status === 200 && a2.daten?.gesamt === a1.daten?.gesamt,
+ `zweimal zutragen aendert nichts (${a2.daten?.gesamt})`);
+
+ const a3 = await weg(false);
+ ok(a3.status === 200 && a3.daten?.gesamt === vorZahl,
+ `und einzeln wieder wegnehmen ebenso (${a3.daten?.gesamt})`);
+
+ const quatsch = await weg(true, "gibt_es_nicht");
+ ok(quatsch.status === 400,
+ `Gegenprobe: ein erfundener Punkt wird abgewiesen (${quatsch.status})`);
+ const vonModi2 = await weg(true, einer, ayla.keks);
+ ok(vonModi2.status >= 400,
+ `Gegenprobe: ein Modi traegt nichts zu (${vonModi2.status})`);
+ }
+
/* ---- Gegenproben ------------------------------------------------- */
/* EIN ERFUNDENER SCHLUESSEL WIRD VERWORFEN. Ohne diesen Abgleich
liesse sich beliebiger Text in die Tabelle schreiben -- er stuende
diff --git a/server/workspace-entwicklung.js b/server/workspace-entwicklung.js
index b108fac5..1ecbd90a 100644
--- a/server/workspace-entwicklung.js
+++ b/server/workspace-entwicklung.js
@@ -951,6 +951,72 @@ entwicklungRouter.get("/workspace/api/entwicklung/person/:id/punkte",
}
});
+/* ==== EINEN EINZELNEN PUNKT ZUTRAGEN (25.09.2026) ==================
+
+ Filipe: „ich will sofort über diese aufgaben da spezifisch zuteilen
+ kann bitte. bei allen personnen."
+
+ DAS AUSWAHLFENSTER BLEIBT -- es ist der Weg, wenn man jemanden neu
+ einrichtet und zwoelf Punkte auf einmal vergibt. Dieser Weg hier ist
+ der andere Fall, und er ist der haeufigere: Man liest eine
+ Beobachtung, denkt „das soll er machen", und will es in dem Moment
+ erledigen -- nicht ein Fenster aufmachen, in einer Liste von 68
+ denselben Punkt suchen und wieder zumachen.
+
+ EIN PUNKT, EIN AUFRUF. Anders als beim Fenster, das die ganze Liste
+ setzt: Hier geht es um EINE Entscheidung, und die soll auch dann
+ ankommen, wenn zwei Leute gleichzeitig an derselben Karte sitzen.
+ Wuerde dieser Weg die ganze Liste schicken, loeschte er die
+ Zutragung, die der andere eine Sekunde vorher gemacht hat.
+
+ `INSERT OR IGNORE` / `DELETE`: Zweimal zutragen ist kein Fehler,
+ zweimal wegnehmen auch nicht. Wer zweimal tippt (oder zwei Fenster
+ offen hat), bekommt denselben Zustand, nicht einen Fehler.
+ ================================================================== */
+entwicklungRouter.put("/workspace/api/entwicklung/person/:id/punkt/:schluessel",
+ gleicheHerkunft, nurLeitung, express.json({ limit: "4kb" }), (req, res) => {
+ try {
+ const id = nummer(req.params.id);
+ const p = id && db().prepare("SELECT id, name, rolle FROM personen WHERE id = ?").get(id);
+ if (!p || !KARTEN_ROLLEN.includes(p.rolle)) {
+ return res.status(404).json({ fehler: "nicht_gefunden" });
+ }
+ const schluessel = String(req.params.schluessel || "");
+ /* NUR EIN SCHLUESSEL, DEN ES WIRKLICH GIBT -- sonst liesse sich
+ ueber diesen Weg beliebiger Text in die Tabelle schreiben, der
+ danach in keiner Liste steht und auch nicht wegzubekommen
+ ist. Dieselbe Pruefung wie beim Fenster. */
+ if (!ENTWICKLUNG_FREMD.some((pt) => pt.schluessel === schluessel)) {
+ return res.status(400).json({ fehler: "unbekannter_punkt" });
+ }
+ const an = req.body?.an === true;
+ if (an) {
+ db().prepare(`INSERT INTO entwicklung_zuteilung
+ (person_id, schluessel, seit, von_id) VALUES (?,?,?,?)
+ ON CONFLICT(person_id, schluessel) DO NOTHING`)
+ .run(id, schluessel, jetzt(), req.person.id);
+ } else {
+ db().prepare(
+ "DELETE FROM entwicklung_zuteilung WHERE person_id = ? AND schluessel = ?")
+ .run(id, schluessel);
+ }
+
+ protokolliere(an ? "entwicklung_punkt_zugetragen" : "entwicklung_punkt_zurueck", {
+ personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
+ detail: `${p.name}: ${schluessel}`.slice(0, 120),
+ });
+ /* DIE NEUE ZAHL KOMMT MIT. Die Oberflaeche zeigt „3 von 12
+ erledigt" -- rechnete sie selbst weiter, waere sie bei zwei
+ offenen Fenstern falsch, und niemand saehe, warum. */
+ const gesamt = db().prepare(
+ "SELECT COUNT(*) AS n FROM entwicklung_zuteilung WHERE person_id = ?").get(id).n;
+ res.json({ ok: true, zugetragen: an, gesamt });
+ } catch (f) {
+ console.error("[entwicklung] Punkt zutragen:", f?.message);
+ res.status(503).json({ fehler: "nicht_verfuegbar" });
+ }
+ });
+
entwicklungRouter.put("/workspace/api/entwicklung/person/:id/punkte",
gleicheHerkunft, nurLeitung, express.json({ limit: "32kb" }), (req, res) => {
try {
diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html
index 932bf84f..cb0886cc 100644
--- a/workspace/anruf-probe.html
+++ b/workspace/anruf-probe.html
@@ -16,15 +16,15 @@
verdeckt. Gefunden hat es pruef-struktur.
Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. -->
-
-
+
+
-
+