<!-- Generated by scripts/build-prompts.mjs — do not edit by hand. -->
# Constrúyeme esta herramienta: Prüfbuch Betriebsmittel

*Todo lo que sigue es la especificación funcional. Síguela tal como está; donde calle, decide tú y di qué supusiste.*

## Punto de partida

Usa la plantilla **openToolbox**: https://github.com/m-dohmen/openToolbox. Lee primero `AGENTS.md` en ese repositorio — es la autoridad sobre la forma del esquema, los tipos de campo y las reglas que rompen una compilación de archivo único. Todo lo específico del dominio va en un solo archivo, `src/domain.js`.

> Si tienes instalado el skill de openToolbox (`claude plugin install opentoolbox@opentoolbox`), basta con pegar este archivo — él mismo se trae la plantilla.

## El problema que resuelve

Todo taller, toda empresa artesanal y toda guardería tiene equipos que deben revisarse a intervalos fijos: el taladro, el alargador, la cafetera, la pistola de calor. La revisión dura minutos. La administración se come la tarde, porque el acta está en una carpeta, los plazos en la cabeza del encargado, y tras un accidente la mutua hace exactamente una pregunta: **¿cuándo se revisó este aparato por última vez?**

## Qué debe quedar al final

Un único archivo HTML autocontenido, que se abre con doble clic, sin servidor y sin instalación. El archivo es también la base de datos: guardar escribe un nuevo HTML con los registros incrustados.

## El registro

Un registro es **Betriebsmittel**, varios son **Betriebsmittel**.

**Campos**

| Clave | Etiqueta | Tipo | Detalle |
| --- | --- | --- | --- |
| `name` | Gerät | text | obligatorio |
| `kind` | Art | enum | uno de: Handmaschine · Verlängerung/Leitung · Küchengerät · Messgerät · Ladegerät · Ortsfest |
| `serial` | Inventar-/Seriennummer | text | cabecera de tabla `Nr.` |
| `location` | Standort | enum | uno de: Werkstatt · Baustellenwagen · Lager · Büro · Küche · ausgeliehen, cabecera de tabla `Ort` |
| `holder` | In der Hand von | text | cabecera de tabla `Bei` |
| `interval` | Prüfintervall (Monate) | enum | uno de: 6 · 12 · 24, cabecera de tabla `Int.` |
| `lastTest` | Letzte Prüfung | date | cabecera de tabla `Geprüft` |
| `tester` | Prüfer | text | — |
| `result` | Ergebnis | enum | uno de: bestanden · bestanden mit Mangel · nicht bestanden · noch nicht geprüft |
| `defect` | Festgestellter Mangel | text | varias líneas, cabecera de tabla `Mangel` |
| `protocol` | Prüfprotokoll | attachment | archivo adjunto, guardado en el registro, cabecera de tabla `Protokoll` |
| `note` | Notiz | text | varias líneas |
| `due` | Nächste Prüfung | computed | calculado, nunca almacenado, cabecera de tabla `Fällig` |
| `daysLeft` | Tage bis zur Prüfung | computed | calculado, nunca almacenado, cabecera de tabla `Tage` |

**Presentación**

- Columna principal: `name`
- Segunda línea debajo: `location`
- Columnas de la tabla, en este orden: `name`, `kind`, `location`, `lastTest`, `due`, `daysLeft`, `result`
- Filtros de la barra lateral: `kind`, `location`, `result`
- Deja de contar como abierto cuando: `() => false`
- Se marca en rojo cuando: `{ if (r.result === 'nicht bestanden') return true const d = dueDate(r) return !d || d < iso(0) }`

**Campos calculados**

Se derivan en cada renderizado y nunca se escriben en el registro: una derivación almacenada queda obsoleta en cuanto cambia una de sus entradas.

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

**Reglas de validación**

Condiciones entre campos. Deben aplicarse en un único lugar para que el formulario, la importación CSV y lo que proponga la IA pasen por la misma comprobación.

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

## Panel

Fichas sobre todo el conjunto de registros, no sobre la vista filtrada.

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

## Captura guiada

Una secuencia breve de pasos para quien tiene que reportar una cosa y no conoce la herramienta. No se escribe nada hasta confirmar el último paso: abandonarla no debe dejar rastro.

Título: **Prüfung eintragen**

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

1. **Paso 1** — Gerät: campos: `name`, `kind`, `serial`, `location`, `holder`, `interval`
2. **Paso 2** — Prüfung: campos: `lastTest`, `tester`, `result`, `defect`, `note`
3. **Paso 3** — Liste einlesen: subida de CSV, que alimenta la misma sesión
   solo cuando: `Boolean(drafts.records?.name)`
4. **Paso 4** — Prüfen: resumen generado a partir del esquema

Pantalla final: «Eingetragen. Die nächste Fälligkeit rechnet sich von selbst.»

## Valores por defecto

Ponlos en `DEFAULT_SETTINGS`, `DEFAULT_COLORS` y `DEFAULT_HOME` en `src/app.jsx`.

- Título: **Prüfbuch Betriebsmittel**
- Subtítulo: Wiederkehrende Prüfungen nach DGUV Vorschrift 3
- Nombre de archivo: `pruefbuch`
- Versión: `1.0`
- Idioma de la interfaz: `de`
- Se abre como: herramienta completa
- Colores: `accent` #a8541f · `band` #2a1d14 · `flag` #b3341f · `ok` #3f7a3f · `pending` #c99411

## Página de inicio

La aplicación abre con este texto. Es un subconjunto de Markdown: títulos, listas, citas, negrita, cursiva, código en línea y enlaces. Úsalo 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.
```

## Datos de ejemplo

Añade 13 registros de ejemplo realistas para que el archivo no esté vacío al abrirlo. Invéntalos al estilo de los campos anteriores; son ilustración, no los datos del usuario. Dile que sus datos entran por **Import CSV → replace all**.

## Terminado cuando

- `npm run build` produce un único `dist/index.html` autocontenido
- `npm test` pasa
- el archivo se abre con doble clic y muestra los registros de ejemplo
- los campos calculados muestran valores y las reglas rechazan un registro que las incumpla
- los ajustes, colores y página de inicio coinciden con lo especificado arriba

## Antes de entregarla

Decide esto tú, no lo dejes al destinatario: pon `copyright` a quien sea dueño de la herramienta, sustituye el enlace de cabecera que apunta al repositorio de openToolbox y desactiva `examplePrompts` si el destinatario solo introduce datos. Menciona el contador de aperturas (Ajustes → Seguridad) al entregarla.

---

*Generado a partir de `examples/equipment-testing.domain.js`, el código real de la [demo en vivo](https://m-dohmen.github.io/openToolbox/demos/equipment-testing/). Regenerar con `npm run prompts`.*

*Todos los datos de la demo son inventados. Ilustra la estructura de una herramienta así — no es asesoramiento legal ni prueba de conformidad.*
