<!-- Generated by scripts/build-prompts.mjs — do not edit by hand. -->
# Bau mir dieses Werkzeug: Verpackungsregister

*Alles Folgende ist die fachliche Anforderung. Halte dich daran; wo sie schweigt, entscheide selbst und sage, was du angenommen hast.*

## Ausgangspunkt

Nutze die Vorlage **openToolbox**: https://github.com/m-dohmen/openToolbox. Lies dort zuerst `AGENTS.md` — sie ist maßgeblich für Schemaform, Feldtypen und die Regeln, an denen ein Einzeldatei-Build zerbricht. Alles Fachliche kommt in eine einzige Datei, `src/domain.js`.

> Ist der openToolbox-Skill installiert (`claude plugin install opentoolbox@opentoolbox`), genügt es, diese Datei einzufügen — er holt sich die Vorlage selbst.

## Das Problem dahinter

Ab dem 12. August 2026 verlangt die EU-Verpackungsverordnung (PPWR) eine Konformitätserklärung und technische Dokumentation **für jede in Verkehr gebrachte Verpackung**. Ein Konzern hat dafür eine Abteilung. Die Manufaktur mit vierzig Artikeln hat die Inhaberin — die dann feststellt, dass die entscheidenden Angaben gar nicht bei ihr liegen, sondern beim Kartonlieferanten, der Etikettendruckerei und dem Folienhersteller. Das ist ein Erhebungsproblem, kein Softwareproblem. Das Werkzeug muss sichtbar machen, was noch fehlt und woher es kommen muss.

## Was am Ende dastehen muss

Eine einzelne, in sich geschlossene HTML-Datei, per Doppelklick zu öffnen, ohne Server und ohne Installation. Die Datei ist zugleich die Datenbank: Speichern schreibt eine neue HTML-Datei mit den eingebetteten Datensätzen.

## Datenarten

Entitätsschlüssel `packagings` — ein Datensatz ist **Verpackung**, mehrere sind **Verpackungen**.

**Felder**

| Schlüssel | Beschriftung | Typ | Näheres |
| --- | --- | --- | --- |
| `name` | Verpackung | text | Pflicht |
| `category` | Art | enum | einer von: Verkaufsverpackung · Umverpackung · Transportverpackung · Serviceverpackung |
| `articles` | Betroffene Artikel | text | Tabellenkopf `Artikel` |
| `docStatus` | Stand der Unterlagen | enum | einer von: nicht begonnen · beim Lieferanten angefragt · Angaben liegen vor · Erklärung erstellt, Tabellenkopf `Stand` |
| `deadline` | Eigene Frist | date | Tabellenkopf `Frist` |
| `responsible` | Zuständig | text | — |
| `doc` | Konformitätserklärung | attachment | hochgeladene Datei, im Datensatz abgelegt, Tabellenkopf `KE` |
| `note` | Notiz | text | mehrzeilig |
| `weight` | Gewicht gesamt (g) | computed | berechnet, nie gespeichert, Tabellenkopf `g` |
| `recyclate` | Rezyklatanteil | computed | berechnet, nie gespeichert, Tabellenkopf `Rez.` |
| `worst` | Schlechteste Klasse | computed | berechnet, nie gespeichert, Tabellenkopf `Klasse` |

**Darstellung**

- Führende Spalte: `name`
- Zweite Zeile darunter: `category`
- Tabellenspalten, in dieser Reihenfolge: `name`, `category`, `articles`, `weight`, `recyclate`, `docStatus`, `deadline`
- Filter in der Seitenleiste: `category`, `docStatus`
- Zählt nicht mehr als offen, wenn: `r.docStatus === 'Erklärung erstellt'`
- Rot markiert, wenn: `r.docStatus !== 'Erklärung erstellt' && r.deadline && r.deadline < today()`

**Berechnete Felder**

Sie werden bei jeder Anzeige gerechnet und nie in den Datensatz geschrieben — eine gespeicherte Ableitung ist in dem Moment falsch, in dem sich eine ihrer Quellen ändert.

- `weight` (Gewicht gesamt (g)) — `componentsOf(r.id).reduce((sum, c) => sum + (Number(c.weight) || 0), 0)`
- `recyclate` (Rezyklatanteil):

  ```js
  const parts = componentsOf(r.id)
  const total = parts.reduce((s, c) => s + (Number(c.weight) || 0), 0)
  if (!total) return ''
  const share = parts.reduce(
    (s, c) => s + ((Number(c.weight) || 0) * (Number(c.recycled) || 0)) / 100,
    0,
  )
  return Math.round((share / total) * 100) + ' %'
  ```
- `worst` (Schlechteste Klasse):

  ```js
  const grades = componentsOf(r.id)
    .map((c) => c.grade)
    .filter((g) => g && g !== 'noch offen')
  if (!grades.length) return 'noch offen'
  return grades.sort().at(-1)
  ```

**Prüfregeln**

Bedingungen zwischen Feldern. Sie müssen an einer Stelle greifen, damit Formular, CSV-Import und die Vorschläge der KI durch dieselbe Prüfung laufen.

- **Wenn** `r.docStatus === 'Erklärung erstellt'` → **Dann** `responsible`
  **Meldung:** „Eine ausgestellte Erklärung braucht eine verantwortliche Person.“
- **Wenn** `r.docStatus === 'Erklärung erstellt'` → **Dann** `Boolean(r.doc?.data)`
  **Meldung:** „Die erstellte Erklärung gehört als Datei an den Datensatz.“

---

Entitätsschlüssel `components` — ein Datensatz ist **Bestandteil**, mehrere sind **Bestandteile**.

**Felder**

| Schlüssel | Beschriftung | Typ | Näheres |
| --- | --- | --- | --- |
| `name` | Bestandteil | text | Pflicht |
| `packagingId` | Gehört zu | reference | Pflicht, Referenz auf `packagings`, Tabellenkopf `Verpackung` |
| `material` | Material | enum | einer von: Papier/Karton · Kunststoff PE · Kunststoff PET · Kunststoff PP · Glas · Aluminium · Weißblech · Verbund |
| `weight` | Gewicht (g) | number | Tabellenkopf `g` |
| `recycled` | Rezyklatanteil (%) | number | Tabellenkopf `% Rez.` |
| `grade` | Recyclingklasse | enum | einer von: A · B · C · D · E · noch offen, Tabellenkopf `Klasse` |
| `source` | Angabe von | enum | einer von: Lieferant schriftlich · Datenblatt · selbst geschätzt · fehlt noch, Tabellenkopf `Quelle` |
| `supplier` | Lieferant | text | — |
| `note` | Notiz | text | mehrzeilig |

**Darstellung**

- Führende Spalte: `name`
- Tabellenspalten, in dieser Reihenfolge: `name`, `packagingId`, `material`, `weight`, `recycled`, `grade`, `source`
- Filter in der Seitenleiste: `material`, `grade`
- In der Übersicht summiert: `weight`
- Zählt nicht mehr als offen, wenn: `() => false`
- Rot markiert, wenn: `r.source === 'fehlt noch'`

**Prüfregeln**

Bedingungen zwischen Feldern. Sie müssen an einer Stelle greifen, damit Formular, CSV-Import und die Vorschläge der KI durch dieselbe Prüfung laufen.

- **Wenn** `Number(r.recycled) > 0` → **Dann** `source`
  **Meldung:** „Zu einem Rezyklatanteil gehört, woher die Zahl stammt.“
- **Wenn** `r.source === 'Lieferant schriftlich'` → **Dann** `supplier`
  **Meldung:** „Wenn der Lieferant es schriftlich bestätigt hat, gehört sein Name dazu.“

---

## Dashboard

Kacheln über den gesamten Bestand, nicht über die gefilterte Ansicht.

- Eine Zahl: **Verpackungen** — der Anzahl · „im Register“
- Eine Zahl: **Noch offen** — der Anzahl (nur Datensätze, die einem Filter entsprechen): `r.docStatus !== 'Erklärung erstellt'` · „ohne fertige Erklärung“
- Eine Zahl: **Angaben fehlen** — der Anzahl (nur Datensätze, die einem Filter entsprechen): `r.source === 'fehlt noch'` · „Bestandteile ohne Quelle“
- Ein Ring je Ausprägung von `docStatus`
- Balken je Ausprägung von `material`, gemessen an `weight` (Gewicht (g)) — **Gewicht je Material (g)**
- Balken je Ausprägung von `grade`, gemessen an der Anzahl — **Bestandteile je Recyclingklasse**

## Geführte Erfassung

Eine kurze Schrittfolge für jemanden, der eine Sache melden soll und das Werkzeug nicht kennt. Geschrieben wird nichts, bevor der letzte Schritt bestätigt ist — ein Abbruch darf nichts hinterlassen.

Titel: **Verpackung aufnehmen**

> Erst die Verpackung, dann ihre Bestandteile. Geschrieben wird nichts, bevor der letzte Schritt bestätigt ist — Abbrechen hinterlässt nichts.

1. **Schritt 1 `packagings`** — Verpackung: Felder: `name`, `category`, `articles`, `responsible`, `deadline`
2. **Schritt 2 `components`** — Erster Bestandteil: Felder: `name`, `packagingId`, `material`, `weight`, `recycled`, `grade`, `source`, `supplier`
   nur wenn: `Boolean(drafts.packagings?.name)`
3. **Schritt 3 `components`** — Liste vom Lieferanten: CSV-Upload, zahlt in denselben Durchlauf ein
   nur wenn: `Boolean(drafts.packagings?.name)`
4. **Schritt 4** — Prüfen: Zusammenfassung, aus dem Schema erzeugt

Abschluss: „Aufgenommen. Was noch fehlt, steht in der Liste unter „Angabe von: fehlt noch".“

## Vorgaben

In `DEFAULT_SETTINGS`, `DEFAULT_COLORS` und `DEFAULT_HOME` in `src/app.jsx` setzen.

- Titel: **Verpackungsregister**
- Untertitel: Konformität nach PPWR, Bestandteil für Bestandteil
- Dateiname: `verpackungsregister`
- Version: `1.0`
- Oberflächensprache: `de`
- Öffnet als: vollständiges Werkzeug
- Farben: `accent` #2f6b3a · `band` #17241a · `flag` #b4531c · `ok` #2f6b3a · `pending` #c08a12

## Startseite

Die Anwendung öffnet auf diesem Text. Er ist ein kleiner Markdown-Teilsatz — Überschriften, Listen, Zitate, fett, kursiv, Inline-Code und Verweise. Wörtlich übernehmen:

```markdown
# Verpackungsregister nach PPWR

Ab dem **12. August 2026** darf in der EU nur noch in Verkehr gebracht werden, was der neuen
Verpackungsverordnung entspricht — mit Konformitätserklärung und technischer Dokumentation
**je Verpackung**.

Für einen Konzern macht das eine Fachabteilung. Für die Manufaktur mit vierzig Artikeln macht das
die Inhaberin — und stellt fest, dass die entscheidenden Angaben gar nicht bei ihr liegen, sondern
bei Kartonlieferant, Etikettendruckerei und Folienhersteller.

## Was diese Demo zeigt

- **Zwei Datenarten**, weil die Recyclingfähigkeit nicht an der Verpackung hängt, sondern an ihren
  Bestandteilen. Ein Sichtfenster aus der falschen Folie kippt die ganze Einheit.
- **Gerechnet statt gepflegt**: Gesamtgewicht, Rezyklatanteil und die schlechteste Klasse ergeben
  sich aus den Bestandteilen.
- **Regeln, die Schätzungen sichtbar machen** — eine Zahl ohne Quelle lässt sich nicht speichern.
- **Anhänge** für die Erklärung selbst, mit hartem Speicherbudget.

> Erfundene Daten, Veranschaulichung der Struktur. Keine Rechtsberatung und kein Nachweis von
> Konformität.
```

## Beispieldaten

Lege Verpackungen: 8, Bestandteile: 15 realistische Beispieldatensätze an, damit die Datei beim ersten Öffnen nicht leer ist. Erfinde sie im Stil der Felder oben; sie sind Anschauung, nicht die Daten des Nutzers. Sag ihm, dass seine eigenen Daten über **Import CSV → alle ersetzen** hineinkommen.

## Fertig, wenn

- `npm run build` eine einzelne, geschlossene `dist/index.html` erzeugt
- `npm test` durchläuft
- die Datei per Doppelklick öffnet und die Beispieldatensätze zeigt
- die berechneten Felder Werte zeigen und die Regeln einen verstoßenden Datensatz abweisen
- Einstellungen, Farben und Startseite der Vorgabe oben entsprechen

## Vor der Übergabe

Entscheide das selbst, statt es dem Empfänger zu überlassen: `copyright` auf den Eigentümer des Werkzeugs setzen, den Kopfzeilen-Verweis auf das openToolbox-Repository ersetzen und `examplePrompts` abschalten, wenn der Empfänger nur Daten pflegt. Den Aufrufzähler (Einstellungen → Sicherheit) bei der Übergabe ansprechen, statt ihn später in einem Netzwerkprotokoll entdecken zu lassen.

---

*Erzeugt aus `examples/ppwr-packaging.domain.js` — dem laufenden Quelltext der [Live-Demo](https://m-dohmen.github.io/openToolbox/demos/ppwr-packaging/). Neu erzeugen mit `npm run prompts`.*

*Alle Daten der Demo sind erfunden. Sie veranschaulicht die Struktur eines solchen Werkzeugs — sie ist keine Rechtsberatung und kein Nachweis von Konformität.*
