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

*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

Según el art. 30 del RGPD casi toda empresa debe llevar un registro de actividades de tratamiento. Casi ninguna lleva uno que merezca ese nombre, y el motivo rara vez es mala voluntad: es la recopilación. Las respuestas están en doce personas distintas: las candidaturas en RR. HH., la herramienta de newsletter en marketing, las cámaras con el conserje. Quien intente rellenarlo desde el centro escribe doce correos y recibe nueve. Por eso la herramienta tiene que funcionar como algo que se envía: cada área declara su propio tratamiento, guarda y lo devuelve.

## 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 **Verarbeitung**, varios son **Verarbeitungen**.

**Campos**

| Clave | Etiqueta | Tipo | Detalle |
| --- | --- | --- | --- |
| `name` | Verarbeitungstätigkeit | text | obligatorio |
| `area` | Bereich | enum | uno de: Personal · Vertrieb · Marketing · IT · Buchhaltung · Empfang |
| `contact` | Ansprechperson | text | cabecera de tabla `Kontakt` |
| `purpose` | Zweck der Verarbeitung | text | varias líneas, cabecera de tabla `Zweck` |
| `subjects` | Betroffene | enum | uno de: Beschäftigte · Bewerberinnen und Bewerber · Kundinnen und Kunden · Interessenten · Lieferanten · Besucher |
| `categories` | Datenkategorien | text | varias líneas, cabecera de tabla `Daten` |
| `basis` | Rechtsgrundlage | enum | uno de: Vertrag (Art. 6 I b) · Rechtliche Pflicht (Art. 6 I c) · Berechtigtes Interesse (Art. 6 I f) · Einwilligung (Art. 6 I a) · noch zu klären, cabecera de tabla `Grundlage` |
| `system` | System / Ablage | text | cabecera de tabla `System` |
| `processor` | Auftragsverarbeiter | text | cabecera de tabla `AV` |
| `thirdCountry` | Drittlandtransfer | enum | uno de: nein · ja · unklar, cabecera de tabla `Drittland` |
| `retention` | Löschfrist | text | cabecera de tabla `Frist` |
| `risk` | Risiko | enum | uno de: gering · mittel · hoch |
| `status` | Status | enum | uno de: gemeldet · in Prüfung · freigegeben · nachzubessern |
| `reviewed` | Zuletzt geprüft | date | cabecera de tabla `Geprüft` |
| `note` | Notiz | text | varias líneas |
| `age` | Tage seit Prüfung | computed | calculado, nunca almacenado, cabecera de tabla `Alter` |

**Presentación**

- Columna principal: `name`
- Segunda línea debajo: `area`
- Columnas de la tabla, en este orden: `name`, `area`, `basis`, `subjects`, `retention`, `age`, `status`
- Filtros de la barra lateral: `area`, `status`, `risk`
- Deja de contar como abierto cuando: `r.status === 'freigegeben'`
- Se marca en rojo cuando: `!r.reviewed || r.reviewed < iso(-365)`

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

- `age` (Tage seit Prüfung):

  ```js
  const reviewed = localDateFromIso(r.reviewed)
  if (!reviewed) return ''
  /* Ganze lokale Tage über die Date.UTC-Differenz der Tageskompo-
     nenten - exakt ganzzahlig, ohne Rundungsrettung und ohne den
     Mix aus UTC-Mitternacht und Lokal-Mitternacht. */
  const now = new Date()
  return (
    (Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) -
      Date.UTC(reviewed.getFullYear(), reviewed.getMonth(), reviewed.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.basis === 'Einwilligung (Art. 6 I a)'` → **Entonces** `note`
  **Mensaje:** „Bei einer Einwilligung gehört in die Notiz, wie und wo sie eingeholt wird.“
- **Cuando** `r.thirdCountry === 'ja'` → **Entonces** `processor`
  **Mensaje:** „Bei einem Drittlandtransfer muss stehen, wer dort verarbeitet.“
- **Cuando** `r.status === 'freigegeben'` → **Entonces** `purpose`, `categories`, `retention`, `reviewed`
  **Mensaje:** „Freigeben lässt sich nur, was Zweck, Datenkategorien, Löschfrist und Prüfdatum hat.“
- **Cuando** `r.risk === 'hoch'` → **Entonces** `note`
  **Mensaje:** „Ein hohes Risiko braucht eine Begründung — es kann eine Folgenabschätzung auslösen.“

## Panel

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

- Un número: **Verarbeitungen** — el número de registros · „im Verzeichnis“
- Un número: **Prüfung überfällig** — el número de registros (solo registros que cumplen un filtro): `isOverdue(r)` · „nie oder vor über einem Jahr“
- Un número: **Offene Fragen** — el número de registros (solo registros que cumplen un filtro): `r.basis === 'noch zu klären' || r.thirdCountry === 'unklar'` · „Rechtsgrundlage oder Drittland unklar“
- Un anillo por valor de `status`
- Barras por valor de `area`, midiendo el número de registros — **Verarbeitungen je Bereich**
- Barras por valor de `risk`, midiendo el número de registros — **Verteilung nach Risiko**

## 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: **Eine Verarbeitung melden**

> Fünf kurze Schritte. Wenn Sie etwas nicht wissen, lassen Sie es leer — ein unvollständiger Eintrag ist besser als keiner. Gespeichert wird erst am Ende.

1. **Paso 1** — Was und wer: campos: `name`, `area`, `contact`
2. **Paso 2** — Zweck und Daten: campos: `purpose`, `subjects`, `categories`
3. **Paso 3** — Rechtsgrundlage: campos: `basis`, `note`
4. **Paso 4** — Wo liegt es: campos: `system`, `processor`, `thirdCountry`, `retention`
5. **Paso 5** — Risiko: campos: `risk`
   solo cuando: `drafts.records?.subjects === 'Beschäftigte' || drafts.records?.subjects === 'Bewerberinnen und Bewerber' || drafts.records?.thirdCountry === 'ja'`
6. **Paso 6** — Prüfen: resumen generado a partir del esquema

Pantalla final: «Danke — das ist aufgenommen. Bitte die Datei speichern und zurücksenden.»

## Valores por defecto

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

- Título: **Verarbeitungsverzeichnis**
- Subtítulo: Art. 30 DSGVO — gemeldet von den Fachbereichen
- Nombre de archivo: `verarbeitungsverzeichnis`
- Versión: `1.0`
- Idioma de la interfaz: `de`
- Se abre como: captura guiada (la lista queda oculta)
- Colores: `accent` #3a5a8c · `band` #1b2333 · `flag` #b0472e · `ok` #3a7a5e · `pending` #c08a12

## 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
# Verzeichnis von Verarbeitungstätigkeiten

Nach **Art. 30 DSGVO** muss fast jedes Unternehmen eines führen. Fast keines führt eines, das den
Namen verdient — und der Grund ist selten Unwille.

Es ist die Erhebung. Die Angaben liegen bei zwölf verschiedenen Leuten: die Bewerbungsmappen bei
der Personalstelle, das Newsletter-Werkzeug im Marketing, die Videoanlage beim Hausmeister. Wer das
zentral ausfüllen will, schreibt zwölf Mails und bekommt neun Antworten.

## Was diese Demo zeigt

Diese Datei ist im **Erfassungsmodus** ausgeliefert: sie öffnet direkt im Meldeformular und zeigt
die Liste gar nicht. Gedacht ist sie zum Weiterschicken — jeder Fachbereich meldet seine eigene
Verarbeitung, speichert und schickt zurück. Die Rückläufer führt man mit *Datei abgleichen*
zusammen.

Umschalten auf das vollständige Werkzeug: **Einstellungen → Anwendung → Öffnet als**.

> Erfundene Einträge, Veranschaulichung der Struktur. Keine Rechtsberatung.
```

## Datos de ejemplo

Añade 10 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/gdpr-processing.domain.js`, el código real de la [demo en vivo](https://m-dohmen.github.io/openToolbox/demos/gdpr-processing/). 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.*
