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

*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

Toda oficina, toda empresa de ofício e toda creche tem equipamentos que precisam ser testados em intervalos fixos: a furadeira, a extensão, a cafeteira, o soprador térmico. O teste em si leva minutos. A administração consome a tarde, porque o laudo está numa pasta, os prazos na cabeça do encarregado, e depois de um acidente o seguro faz exatamente uma pergunta: **quando este aparelho foi testado pela última vez?**

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

## O registro

Um registro é **Betriebsmittel**, vários são **Betriebsmittel**.

**Campos**

| Chave | Rótulo | Tipo | Detalhe |
| --- | --- | --- | --- |
| `name` | Gerät | text | obrigatório |
| `kind` | Art | enum | um de: Handmaschine · Verlängerung/Leitung · Küchengerät · Messgerät · Ladegerät · Ortsfest |
| `serial` | Inventar-/Seriennummer | text | cabeçalho de tabela `Nr.` |
| `location` | Standort | enum | um de: Werkstatt · Baustellenwagen · Lager · Büro · Küche · ausgeliehen, cabeçalho de tabela `Ort` |
| `holder` | In der Hand von | text | cabeçalho de tabela `Bei` |
| `interval` | Prüfintervall (Monate) | enum | um de: 6 · 12 · 24, cabeçalho de tabela `Int.` |
| `lastTest` | Letzte Prüfung | date | cabeçalho de tabela `Geprüft` |
| `tester` | Prüfer | text | — |
| `result` | Ergebnis | enum | um de: bestanden · bestanden mit Mangel · nicht bestanden · noch nicht geprüft |
| `defect` | Festgestellter Mangel | text | multilinha, cabeçalho de tabela `Mangel` |
| `protocol` | Prüfprotokoll | attachment | arquivo anexado, guardado no registro, cabeçalho de tabela `Protokoll` |
| `note` | Notiz | text | multilinha |
| `due` | Nächste Prüfung | computed | calculado, nunca armazenado, cabeçalho de tabela `Fällig` |
| `daysLeft` | Tage bis zur Prüfung | computed | calculado, nunca armazenado, cabeçalho de tabela `Tage` |

**Apresentação**

- Coluna principal: `name`
- Segunda linha abaixo: `location`
- Colunas da tabela, nesta ordem: `name`, `kind`, `location`, `lastTest`, `due`, `daysLeft`, `result`
- Filtros da barra lateral: `kind`, `location`, `result`
- Deixa de contar como aberto quando: `() => false`
- Marcado em vermelho quando: `{ if (r.result === 'nicht bestanden') return true const d = dueDate(r) return !d || d < iso(0) }`

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

- `due` (Nächste Prüfung):

  ```js
  const d = dueDate(r)
  if (!d) return ''
  const [y, m, day] = d.split('-')
  return `${day}.${m}.${y}`
  ```
- `daysLeft` (Tage bis zur Prüfung):

  ```js
  const d = localDateFromIso(dueDate(r))
  if (!d) return ''
  // Beide Seiten als ganze lokale Tage: Date.UTC über die Tages-
  // komponenten hält die Differenz exakt ganzzahlig - ohne die
  // Rundungsrettung, die ab UTC+12 kippt, und ohne den Mix aus
  // UTC-Mitternacht und Lokal-Mitternacht.
  const now = new Date()
  return (
    (Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) -
      Date.UTC(now.getFullYear(), now.getMonth(), now.getDate())) /
    86400000
  )
  ```

**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.result !== 'noch nicht geprüft'` → **Então** `lastTest`, `tester`
  **Mensagem:** „Ein Prüfergebnis ohne Datum und Prüfer ist im Ernstfall wertlos.“
- **Quando** `r.result === 'bestanden mit Mangel' || r.result === 'nicht bestanden'` → **Então** `defect`
  **Mensagem:** „Zu einem Mangel gehört, worin er besteht.“
- **Quando** `r.result === 'nicht bestanden'` → **Então** `note`
  **Mensagem:** „Bei „nicht bestanden" gehört in die Notiz, wo das Gerät jetzt ist — es darf nicht weiterlaufen.“

## Painel

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

- Um número: **Betriebsmittel** — a quantidade de registros · „im Bestand“
- Um número: **Fällig oder gesperrt** — a quantidade de registros (apenas registros que atendem a um filtro): `isOverdue(r)` · „sofort anfassen“
- Um número: **In den nächsten 60 Tagen** — a quantidade de registros (apenas registros que atendem a um filtro): `{ const d = dueDate(r) return d && d >= iso(0) && d <= iso(60) }` · „Prüftermin planen“
- Um anel por valor de `result`
- Barras por valor de `location`, medindo a quantidade de registros — **Geräte je Standort**
- Barras por valor de `kind`, medindo a quantidade de registros — **Geräte je Art**

## 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: **Prüfung eintragen**

> Drei Schritte. Für den ersten Aufbau eines Bestands hilft der Schritt „Liste einlesen".

1. **Passo 1** — Gerät: campos: `name`, `kind`, `serial`, `location`, `holder`, `interval`
2. **Passo 2** — Prüfung: campos: `lastTest`, `tester`, `result`, `defect`, `note`
3. **Passo 3** — Liste einlesen: upload de CSV, alimentando a mesma sessão
   somente quando: `Boolean(drafts.records?.name)`
4. **Passo 4** — Prüfen: resumo gerado a partir do esquema

Tela final: “Eingetragen. Die nächste Fälligkeit rechnet sich von selbst.”

## Padrões

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

- Título: **Prüfbuch Betriebsmittel**
- Subtítulo: Wiederkehrende Prüfungen nach DGUV Vorschrift 3
- Nome do arquivo: `pruefbuch`
- Versão: `1.0`
- Idioma da interface: `de`
- Abre como: ferramenta completa
- Cores: `accent` #a8541f · `band` #2a1d14 · `flag` #b3341f · `ok` #3f7a3f · `pending` #c99411

## 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
# Prüfbuch für ortsveränderliche Betriebsmittel

Bohrmaschine, Verlängerung, Kaffeemaschine, Heißluftgebläse: alles muss regelmäßig geprüft werden.
Die Prüfung selbst dauert Minuten. Die Verwaltung frisst den Nachmittag — weil das Protokoll im
Ordner liegt, die Fristen im Kopf des Meisters, und die Berufsgenossenschaft nach einem Unfall
genau eine Frage stellt: **wann wurde dieses Gerät zuletzt geprüft?**

## Was diese Demo zeigt

- **Sortierbare Spalten** — ein Klick auf den Spaltenkopf ordnet: Zahlen nach Größe („Tage"
  reicht von deutlich überfällig bis weit in die Zukunft), Daten chronologisch („Geprüft" spannt
  zwei Jahre), Text alphabetisch; der dritte Klick stellt die Reihenfolge des Datenblocks wieder
  her, leere Werte bleiben unten.
- **Nichts wird summiert, alles gerechnet**: die Fälligkeit ergibt sich aus letzter Prüfung und
  Intervall, die Restzeit daraus, die rote Markierung wieder daraus.
- **Regeln, die dem Ernstfall standhalten** — ein Ergebnis ohne Datum und Prüfer lässt sich nicht
  speichern, ein „nicht bestanden" nicht ohne die Angabe, wo das Gerät jetzt ist.
- **Anhänge** für das Prüfprotokoll am Gerät selbst.

> Erfundene Daten. Veranschaulichung der Struktur, keine Aussage über den Umfang Ihrer Pflichten.
```

## Dados de demonstração

Acrescente 13 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/equipment-testing.domain.js`, o código real da [demo ao vivo](https://m-dohmen.github.io/openToolbox/demos/equipment-testing/). 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.*
