<!-- Generated by scripts/build-prompts.mjs — do not edit by hand. -->
# Construa esta ferramenta para mim: Verpackungsregister

*Tudo o que segue é a especificação funcional. Siga-a como está; onde ela se calar, decida e diga o que assumiu.*

## Ponto de partida

Use o modelo **openToolbox**: https://github.com/m-dohmen/openToolbox. Leia primeiro o `AGENTS.md` desse repositório — ele é a autoridade sobre a forma do esquema, os tipos de campo e as regras que quebram uma compilação de arquivo único. Tudo o que é do domínio vai em um único arquivo, `src/domain.js`.

> Se o skill do openToolbox estiver instalado (`claude plugin install opentoolbox@opentoolbox`), basta colar este arquivo — ele mesmo busca o modelo.

## O problema por trás disso

A partir de 12 de agosto de 2026 o regulamento europeu de embalagens (PPWR) exige uma declaração de conformidade e documentação técnica **para cada embalagem colocada no mercado**. Uma corporação tem um departamento para isso. Um pequeno fabricante com quarenta artigos tem a própria dona — que então descobre que os dados decisivos não são dela: estão com o fornecedor de papelão, a gráfica de etiquetas e o fabricante do filme. É um problema de coleta, não de software. A ferramenta precisa tornar visível o que ainda falta e de onde tem de vir.

## O que precisa existir no fim

Um único arquivo HTML autocontido, aberto com duplo clique, sem servidor e sem instalação. O arquivo também é o banco de dados: salvar escreve um novo HTML com os registros embutidos.

## Tipos de registro

Chave de entidade `packagings` — um registro é **Verpackung**, vários são **Verpackungen**.

**Campos**

| Chave | Rótulo | Tipo | Detalhe |
| --- | --- | --- | --- |
| `name` | Verpackung | text | obrigatório |
| `category` | Art | enum | um de: Verkaufsverpackung · Umverpackung · Transportverpackung · Serviceverpackung |
| `articles` | Betroffene Artikel | text | cabeçalho de tabela `Artikel` |
| `docStatus` | Stand der Unterlagen | enum | um de: nicht begonnen · beim Lieferanten angefragt · Angaben liegen vor · Erklärung erstellt, cabeçalho de tabela `Stand` |
| `deadline` | Eigene Frist | date | cabeçalho de tabela `Frist` |
| `responsible` | Zuständig | text | — |
| `doc` | Konformitätserklärung | attachment | arquivo anexado, guardado no registro, cabeçalho de tabela `KE` |
| `note` | Notiz | text | multilinha |
| `weight` | Gewicht gesamt (g) | computed | calculado, nunca armazenado, cabeçalho de tabela `g` |
| `recyclate` | Rezyklatanteil | computed | calculado, nunca armazenado, cabeçalho de tabela `Rez.` |
| `worst` | Schlechteste Klasse | computed | calculado, nunca armazenado, cabeçalho de tabela `Klasse` |

**Apresentação**

- Coluna principal: `name`
- Segunda linha abaixo: `category`
- Colunas da tabela, nesta ordem: `name`, `category`, `articles`, `weight`, `recyclate`, `docStatus`, `deadline`
- Filtros da barra lateral: `category`, `docStatus`
- Deixa de contar como aberto quando: `r.docStatus === 'Erklärung erstellt'`
- Marcado em vermelho quando: `r.docStatus !== 'Erklärung erstellt' && r.deadline && r.deadline < today()`

**Campos calculados**

São derivados a cada renderização e nunca gravados no registro — uma derivação armazenada fica desatualizada assim que uma de suas entradas muda.

- `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)
  ```

**Regras de validação**

Condições entre campos. Precisam valer em um único lugar, para que o formulário, a importação CSV e o que a IA propuser passem pela mesma verificação.

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

---

Chave de entidade `components` — um registro é **Bestandteil**, vários são **Bestandteile**.

**Campos**

| Chave | Rótulo | Tipo | Detalhe |
| --- | --- | --- | --- |
| `name` | Bestandteil | text | obrigatório |
| `packagingId` | Gehört zu | reference | obrigatório, referência para `packagings`, cabeçalho de tabela `Verpackung` |
| `material` | Material | enum | um de: Papier/Karton · Kunststoff PE · Kunststoff PET · Kunststoff PP · Glas · Aluminium · Weißblech · Verbund |
| `weight` | Gewicht (g) | number | cabeçalho de tabela `g` |
| `recycled` | Rezyklatanteil (%) | number | cabeçalho de tabela `% Rez.` |
| `grade` | Recyclingklasse | enum | um de: A · B · C · D · E · noch offen, cabeçalho de tabela `Klasse` |
| `source` | Angabe von | enum | um de: Lieferant schriftlich · Datenblatt · selbst geschätzt · fehlt noch, cabeçalho de tabela `Quelle` |
| `supplier` | Lieferant | text | — |
| `note` | Notiz | text | multilinha |

**Apresentação**

- Coluna principal: `name`
- Colunas da tabela, nesta ordem: `name`, `packagingId`, `material`, `weight`, `recycled`, `grade`, `source`
- Filtros da barra lateral: `material`, `grade`
- Somado no resumo: `weight`
- Deixa de contar como aberto quando: `() => false`
- Marcado em vermelho quando: `r.source === 'fehlt noch'`

**Regras de validação**

Condições entre campos. Precisam valer em um único lugar, para que o formulário, a importação CSV e o que a IA propuser passem pela mesma verificação.

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

---

## Painel

Blocos sobre todo o conjunto de registros, não sobre a visão filtrada.

- Um número: **Verpackungen** — a quantidade de registros · „im Register“
- Um número: **Noch offen** — a quantidade de registros (apenas registros que atendem a um filtro): `r.docStatus !== 'Erklärung erstellt'` · „ohne fertige Erklärung“
- Um número: **Angaben fehlen** — a quantidade de registros (apenas registros que atendem a um filtro): `r.source === 'fehlt noch'` · „Bestandteile ohne Quelle“
- Um anel por valor de `docStatus`
- Barras por valor de `material`, medindo `weight` (Gewicht (g)) — **Gewicht je Material (g)**
- Barras por valor de `grade`, medindo a quantidade de registros — **Bestandteile je Recyclingklasse**

## Captura guiada

Uma sequência curta de passos para quem precisa relatar uma coisa e não conhece a ferramenta. Nada é gravado antes da confirmação do último passo — abandonar não pode deixar rastro.

Título: **Verpackung aufnehmen**

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

1. **Passo 1 `packagings`** — Verpackung: campos: `name`, `category`, `articles`, `responsible`, `deadline`
2. **Passo 2 `components`** — Erster Bestandteil: campos: `name`, `packagingId`, `material`, `weight`, `recycled`, `grade`, `source`, `supplier`
   somente quando: `Boolean(drafts.packagings?.name)`
3. **Passo 3 `components`** — Liste vom Lieferanten: upload de CSV, alimentando a mesma sessão
   somente quando: `Boolean(drafts.packagings?.name)`
4. **Passo 4** — Prüfen: resumo gerado a partir do esquema

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

## Padrões

Defina em `DEFAULT_SETTINGS`, `DEFAULT_COLORS` e `DEFAULT_HOME` em `src/app.jsx`.

- Título: **Verpackungsregister**
- Subtítulo: Konformität nach PPWR, Bestandteil für Bestandteil
- Nome do arquivo: `verpackungsregister`
- Versão: `1.0`
- Idioma da interface: `de`
- Abre como: ferramenta completa
- Cores: `accent` #2f6b3a · `band` #17241a · `flag` #b4531c · `ok` #2f6b3a · `pending` #c08a12

## Página inicial

O aplicativo abre com este texto. É um subconjunto pequeno de Markdown: títulos, listas, citações, negrito, itálico, código embutido e links. Use-o literalmente:

```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.
```

## Dados de demonstração

Acrescente Verpackungen: 8, Bestandteile: 15 registros de demonstração realistas para que o arquivo não esteja vazio ao abrir. Invente-os no estilo dos campos acima; são ilustração, não os dados do usuário. Diga a ele que os próprios dados entram por **Import CSV → replace all**.

## Pronto quando

- `npm run build` produz um único `dist/index.html` autocontido
- `npm test` passa
- o arquivo abre com duplo clique e mostra os registros de demonstração
- os campos calculados mostram valores e as regras recusam um registro que as viole
- as configurações, cores e página inicial correspondem à especificação acima

## Antes de entregar

Decida você, em vez de deixar para o destinatário: defina `copyright` para quem é dono da ferramenta, substitua o link do cabeçalho que aponta para o repositório do openToolbox e desligue `examplePrompts` se o destinatário apenas registra dados. Mencione o contador de aberturas (Configurações → Segurança) na entrega.

---

*Gerado a partir de `examples/ppwr-packaging.domain.js`, o código real da [demo ao vivo](https://m-dohmen.github.io/openToolbox/demos/ppwr-packaging/). Gerar de novo com `npm run prompts`.*

*Todos os dados da demo são inventados. Ela ilustra a estrutura de uma ferramenta assim — não é aconselhamento jurídico nem prova de conformidade.*
