diff --git a/.claude/commands/init-proyecto.md b/.claude/commands/init-proyecto.md
index 279054a..13a6c14 100644
--- a/.claude/commands/init-proyecto.md
+++ b/.claude/commands/init-proyecto.md
@@ -40,7 +40,8 @@ Ejecuta el checklist de "Inicialización del proyecto" de `CLAUDE.md`:
de cada sesión se pare a preguntar por él. `docs/features/` se queda vacía, solo con su
`README.md`.
7. `mejoras/backlog.md` — borra el ejemplo comentado.
-8. `.template/` — bórrala (`rm -rf .template`).
+8. `.template/` — bórrala (`rm -rf .template`). Quita después las referencias a
+ `.template/assets/` que queden en otros archivos: hay una en `docs/features/README.md`.
9. `changelog/` — crea la primera entrada real del proyecto (tipo: Configuración) con `/changelog`
y limpia de `changelog/README.md` la referencia a la plantilla.
10. Verifica que no queden restos:
diff --git a/.template/README.md b/.template/README.md
index 4b7af1e..585b448 100644
--- a/.template/README.md
+++ b/.template/README.md
@@ -6,6 +6,7 @@ Su contenido:
- `changelog/` — historial de cambios de la propia plantilla (cómo ha ido evolucionando este
andamiaje). No es el changelog de tu proyecto: ese vive en `changelog/`, en la raíz.
+- `assets/` — los GIF del `README.md` de la plantilla y los scripts que los generan.
## Si estás usando la plantilla para un proyecto
diff --git a/.template/assets/README.md b/.template/assets/README.md
new file mode 100644
index 0000000..78c7277
--- /dev/null
+++ b/.template/assets/README.md
@@ -0,0 +1,31 @@
+# Imágenes del README
+
+Los GIF que ilustran el `README.md` de la plantilla, y los scripts que los generan.
+
+Viven aquí, dentro de `.template/`, por el mismo motivo que el changelog de la plantilla: son
+material del andamiaje, no del producto. Al inicializar un proyecto esta carpeta se borra entera y
+el README se reescribe, así que las imágenes y sus referencias desaparecen juntas.
+
+| Archivo | Qué muestra |
+|---------|-------------|
+| `comparativa.gif` | Sin plantilla frente a con plantilla |
+| `flujo.gif` | El repositorio montándose fase a fase |
+| `cobertura.gif` | El script bloqueando un test prometido que no existe |
+| `excepcion.gif` | El script rechazando una excepción sin justificar |
+
+## Regenerarlos
+
+Hace falta Python con Pillow, `rsvg-convert` y `ffmpeg`. Los dos diagramas se dibujan en SVG y se
+rasterizan; los de terminal se componen frame a frame a partir de **salida real** del script.
+
+```bash
+python3 gen-comparativa.py salida.svg # diagrama de carriles
+python3 gen-flujo.py salida.svg [fase] # repositorio por fases (0-6)
+```
+
+`gen-terminal.py` es una librería: se importa y se le pasa un guion de comandos y su salida.
+
+**Regla al tocarlos:** el texto que aparece en los GIF de terminal se copia de una ejecución de
+verdad. Si cambias el script y cambian sus mensajes, hay que volver a ejecutarlo y regenerar el
+GIF, no editar el texto a mano. Un GIF que enseña una salida que el programa ya no produce miente
+igual que una casilla marcada sin comprobar.
diff --git a/.template/assets/cobertura.gif b/.template/assets/cobertura.gif
new file mode 100644
index 0000000..cd48457
Binary files /dev/null and b/.template/assets/cobertura.gif differ
diff --git a/.template/assets/comparativa.gif b/.template/assets/comparativa.gif
new file mode 100644
index 0000000..5207d11
Binary files /dev/null and b/.template/assets/comparativa.gif differ
diff --git a/.template/assets/excepcion.gif b/.template/assets/excepcion.gif
new file mode 100644
index 0000000..d5db5ef
Binary files /dev/null and b/.template/assets/excepcion.gif differ
diff --git a/.template/assets/flujo.gif b/.template/assets/flujo.gif
new file mode 100644
index 0000000..d5877c5
Binary files /dev/null and b/.template/assets/flujo.gif differ
diff --git a/.template/assets/gen-comparativa.py b/.template/assets/gen-comparativa.py
new file mode 100644
index 0000000..15757f9
--- /dev/null
+++ b/.template/assets/gen-comparativa.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+"""Genera el diagrama 'sin plantilla / con plantilla' en SVG."""
+
+W, H = 1200, 560
+BG = "#faf8f5"
+CARRIL_MAL = "#f3efe9"; BORDE_MAL = "#e7e0d5"
+CARRIL_BIEN = "#eef1f8"; BORDE_BIEN = "#dde3f2"
+TARJETA = "#ffffff"; BORDE_T = "#e8e3da"
+TINTA = "#2c2c33"; TENUE = "#938d80"
+ROJO = "#c8635c"; AZUL = "#4a63c8"; VERDE = "#4f9563"
+FAM = "Helvetica Neue, Helvetica, Arial, sans-serif"
+
+MAL = [
+ ("chat", "Sesión nueva", "CONTEXTO CERO", None),
+ ("doc", "Le explicas todo", "OTRA VEZ", None),
+ ("code", "Escribe a ciegas", "SIN PREGUNTAR", None),
+ ("x", "No era eso", "REHACER", ROJO),
+ ("quest", "Decisiones perdidas", "NADIE SE ACUERDA", None),
+ ("box", "Entregado", "SIN SABER SI CUMPLE", None),
+]
+BIEN = [
+ ("doc", "Sesión nueva", "LEE docs/", None),
+ ("ficha", "Ficha de feature", "QUÉ Y CÓMO SE VALIDA", None),
+ ("code", "Construye", "SOBRE LO ACORDADO", None),
+ ("test", "Tests del código", "TRAS IMPLEMENTAR", None),
+ ("pr", "PR con evidencia", "SALIDA REAL PEGADA", None),
+ ("check", "Mergeado", "COBERTURA VERIFICADA", VERDE),
+]
+
+
+def icono(tipo, x, y, c):
+ """Icono de línea 22x22 dibujado desde (x,y). Trazo fino, sin relleno."""
+ s = f''
+ if tipo == "chat":
+ s += f''
+ elif tipo == "doc":
+ s += f''
+ elif tipo == "code":
+ s += f''
+ elif tipo == "x":
+ s += f''
+ elif tipo == "quest":
+ s += f''
+ elif tipo == "box":
+ s += f''
+ elif tipo == "ficha":
+ s += f''
+ elif tipo == "test":
+ s += f''
+ elif tipo == "pr":
+ s += f''
+ elif tipo == "check":
+ s += f''
+ return s + ""
+
+
+def tarjeta(x, y, w, h, ic, titulo, sub, acento):
+ col = acento or TINTA
+ fondo_ic = "#fdf1f0" if acento == ROJO else ("#eef7f1" if acento == VERDE else "#f6f4f0")
+ borde = "#f0d9d7" if acento == ROJO else ("#d8ebdf" if acento == VERDE else BORDE_T)
+ s = f''
+ s += f''
+ s += icono(ic, x + 22, y + 22, col)
+ s += (f'{titulo}')
+ s += (f'{sub}')
+ return s + ""
+
+
+def carril(y0, etiqueta, pasos, fondo, borde, color_et, texto_et, pie, color_pie, revelados=None):
+ n = len(pasos)
+ x0, ancho_c = 50, W - 100
+ s = f''
+ s += f''
+ s += (f'{etiqueta}')
+
+ tw, hueco = 168, 18
+ total = n * tw + (n - 1) * hueco
+ sx = x0 + (ancho_c - total) / 2
+ ty = y0 + 46
+ for i, (ic, tit, sub, ac) in enumerate(pasos):
+ tx = sx + i * (tw + hueco)
+ vis = revelados is None or i < revelados
+ op = 1 if vis else 0.13
+ s += f'' + tarjeta(tx, ty, tw, 108, ic, tit, sub, ac) + ""
+ if i < n - 1:
+ cx1, cx2 = tx + tw, tx + tw + hueco
+ conec = revelados is None or i < revelados - 1
+ s += (f'')
+ s += (f'')
+
+ py = ty + 132
+ completo = revelados is None or revelados >= n
+ s += f''
+ if pie[0] == "loop":
+ s += (f'')
+ s += (f'')
+ else:
+ s += f''
+ s += (f'')
+ s += (f'{pie[1]}')
+ return s + ""
+
+
+def svg(rev_mal=None, rev_bien=None):
+ s = (f'"
+
+
+if __name__ == "__main__":
+ import sys
+ open(sys.argv[1], "w", encoding="utf-8").write(svg())
diff --git a/.template/assets/gen-flujo.py b/.template/assets/gen-flujo.py
new file mode 100644
index 0000000..a1e7581
--- /dev/null
+++ b/.template/assets/gen-flujo.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+"""De la conversación al producto: el repositorio montándose solo."""
+
+W, H = 1200, 620
+BG = "#faf8f5"; TARJETA = "#ffffff"; BORDE = "#e8e3da"
+TINTA = "#2c2c33"; TENUE = "#938d80"; SUAVE = "#b8b2a5"
+AZUL = "#4a63c8"; VERDE = "#4f9563"; AMBAR = "#c8933f"; ROJO = "#c8635c"
+FAM = "Helvetica Neue, Helvetica, Arial, sans-serif"
+MONO = "Menlo, monospace"
+
+FASES = [
+ ("Conversación", "EL AGENTE PREGUNTA ANTES DE ESCRIBIR"),
+ ("Inicialización", "DEJA DE SER UNA PLANTILLA"),
+ ("Acuerdo", "QUÉ SE CONSTRUYE Y CÓMO SE VALIDA"),
+ ("Construcción", "SOBRE LO ACORDADO"),
+ ("Validación", "TESTS SOBRE EL CÓDIGO REAL"),
+ ("Cierre", "PR CON EVIDENCIA, CI EN VERDE"),
+]
+
+# (sangría, nombre, tipo, nace, muere, nota_por_fase)
+FILAS = [
+ (0, ".template/", "dir", 0, 2, {}),
+ (0, "CLAUDE.md", "file", 0, 9, {2: ("rellenado", AZUL)}),
+ (0, "README.md", "file", 0, 9, {2: ("reescrito", AZUL)}),
+ (0, "changelog/", "dir", 6, 9, {}),
+ (1, "2026-08-18_exportar.md", "file", 6, 9, {}),
+ (0, "docs/", "dir", 0, 9, {}),
+ (1, "prd.md", "file", 0, 9, {0: ("vacío", SUAVE), 1: ("M-01 · M-02 · M-03", AZUL)}),
+ (1, "architecture.md", "file", 0, 9, {0: ("vacío", SUAVE), 1: None}),
+ (1, "design-system.md", "file", 0, 9, {0: ("vacío", SUAVE), 1: None}),
+ (1, "data-model.md", "file", 0, 9, {0: ("vacío", SUAVE), 1: None}),
+ (1, "features/", "dir", 3, 9, {}),
+ (2, "exportar-coleccion.md", "ficha",3, 9, {}),
+ (0, "src/app/exportar/", "dir", 4, 9, {}),
+ (1, "route.ts", "file", 4, 9, {}),
+ (0, "tests/", "dir", 5, 9, {}),
+ (1, "exportar.spec.ts", "file", 5, 9, {}),
+]
+PILL = {3: ("Acordada", TENUE, "#f2efe9"), 4: ("En construcción", AMBAR, "#fbf4e6"),
+ 5: ("Verificada", VERDE, "#eef7f1"), 6: ("Verificada", VERDE, "#eef7f1")}
+
+
+def glifo(tipo, x, y, c):
+ g = f''
+ if tipo == "dir":
+ g += f''
+ elif tipo == "ficha":
+ g += f''
+ else:
+ g += f''
+ return g + ""
+
+
+def pastilla(x, y, texto, color, fondo, tam=9):
+ an = len(texto) * tam * 0.6 + 18
+ return (f''
+ f'{texto}')
+
+
+def svg(fase=6):
+ s = (f'"
+
+
+if __name__ == "__main__":
+ import sys
+ open(sys.argv[1], "w", encoding="utf-8").write(svg(int(sys.argv[2]) if len(sys.argv) > 2 else 6))
diff --git a/.template/assets/gen-terminal.py b/.template/assets/gen-terminal.py
new file mode 100644
index 0000000..3324f79
--- /dev/null
+++ b/.template/assets/gen-terminal.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+"""Renderiza una sesión de terminal a GIF. Frames con Pillow, ensamblado con ffmpeg."""
+import subprocess, sys, shutil
+from pathlib import Path
+from PIL import Image, ImageDraw, ImageFont
+
+FUENTE = "/System/Library/Fonts/SFNSMono.ttf"
+TAM = 15
+INTERLINEA = 24
+PAD_X, PAD_Y = 26, 20
+CHROME = 38
+
+C = {
+ "fondo": "#151723",
+ "chrome": "#1d2030",
+ "borde": "#2a2e42",
+ "texto": "#c5cee0",
+ "tenue": "#6b7594",
+ "prompt": "#4fd6be",
+ "cmd": "#eef1f8",
+ "fallo": "#ff757f",
+ "aviso": "#ffc777",
+ "ok": "#c3e88d",
+ "ruta": "#82aaff",
+}
+
+fuente = ImageFont.truetype(FUENTE, TAM)
+_probe = Image.new("RGB", (10, 10)); _d = ImageDraw.Draw(_probe)
+CHAR_W = _d.textlength("M", font=fuente)
+
+
+def estilar(linea):
+ """Una línea de salida real -> tramos coloreados. No altera el texto."""
+ t = linea
+ if t.startswith("$ "):
+ resto = t[2:]
+ if resto.startswith("#"):
+ return [("$ ", C["prompt"]), (resto, C["tenue"])]
+ return [("$ ", C["prompt"]), (resto, C["cmd"])]
+ s = t.strip()
+ if s.startswith("FALLO"):
+ i = t.index("FALLO")
+ return [(t[:i], C["texto"]), ("FALLO", C["fallo"]), (t[i + 5:], C["texto"])]
+ if s.startswith("ATENCIÓN"):
+ i = t.index("ATENCIÓN")
+ return [(t[:i], C["texto"]), ("ATENCIÓN", C["aviso"]), (t[i + 8:], C["texto"])]
+ if s.startswith("docs/") or s.startswith("scripts/"):
+ return [(t, C["ruta"])]
+ if s.startswith("Todo en orden") or s.startswith("Sin fallos"):
+ return [(t, C["ok"])]
+ if s.endswith("fallo(s).") or " fallo(s)" in s:
+ return [(t, C["fallo"])]
+ if s.startswith("Verificación de cobertura"):
+ return [(t, C["tenue"])]
+ return [(t, C["texto"])]
+
+
+def pintar(lineas, cursor_en=None, ancho=None, alto=None):
+ img = Image.new("RGB", (ancho, alto), C["fondo"])
+ d = ImageDraw.Draw(img)
+ d.rectangle([0, 0, ancho, CHROME], fill=C["chrome"])
+ d.line([(0, CHROME), (ancho, CHROME)], fill=C["borde"])
+ for i, col in enumerate(["#ff5f57", "#febc2e", "#28c840"]):
+ cx = 22 + i * 20
+ d.ellipse([cx - 6, CHROME // 2 - 6, cx + 6, CHROME // 2 + 6], fill=col)
+
+ y = CHROME + PAD_Y
+ for li, linea in enumerate(lineas):
+ x = PAD_X
+ for texto, color in estilar(linea):
+ d.text((x, y), texto, font=fuente, fill=color)
+ x += d.textlength(texto, font=fuente)
+ if cursor_en == li:
+ d.rectangle([x + 1, y + 2, x + CHAR_W, y + TAM + 4], fill=C["texto"])
+ y += INTERLINEA
+ return img
+
+
+def render(guion, salida, fps=16):
+ """guion: lista de ('cmd', txt) | ('out', [lineas]) | ('esperar', frames)"""
+ todas = []
+ for tipo, val in guion:
+ if tipo == "cmd":
+ todas.append("$ " + val)
+ elif tipo == "out":
+ todas.extend(val)
+ max_chars = max((len(l) for l in todas), default=60)
+ ancho = int(PAD_X * 2 + max_chars * CHAR_W) + 8
+ alto = CHROME + PAD_Y * 2 + len(todas) * INTERLINEA
+
+ frames, lineas = [], []
+ for tipo, val in guion:
+ if tipo == "cmd":
+ lineas.append("$ ")
+ for i in range(0, len(val) + 1, 2):
+ lineas[-1] = "$ " + val[:i]
+ frames.append(pintar(lineas, len(lineas) - 1, ancho, alto))
+ lineas[-1] = "$ " + val
+ for _ in range(5):
+ frames.append(pintar(lineas, len(lineas) - 1, ancho, alto))
+ elif tipo == "out":
+ for linea in val:
+ lineas.append(linea)
+ frames.append(pintar(lineas, None, ancho, alto))
+ elif tipo == "esperar":
+ for _ in range(val):
+ frames.append(pintar(lineas, None, ancho, alto))
+ return frames, ancho, alto, fps
+
+
+def a_gif(frames, destino, fps):
+ tmp = Path(destino).parent / "_frames"
+ if tmp.exists():
+ shutil.rmtree(tmp)
+ tmp.mkdir(parents=True)
+ for i, f in enumerate(frames):
+ f.save(tmp / f"{i:04d}.png")
+ paleta = tmp / "pal.png"
+ subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-framerate", str(fps),
+ "-i", str(tmp / "%04d.png"),
+ "-vf", "palettegen=max_colors=128:stats_mode=diff", str(paleta)], check=True)
+ subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-framerate", str(fps),
+ "-i", str(tmp / "%04d.png"), "-i", str(paleta),
+ "-lavfi", "paletteuse=dither=none:diff_mode=rectangle",
+ "-loop", "0", str(destino)], check=True)
+ subprocess.run(["magick", str(destino), "-layers", "Optimize", str(destino)], check=True)
+ shutil.rmtree(tmp)
diff --git a/.template/changelog/2026-08-18_16-23_gifs-del-readme.md b/.template/changelog/2026-08-18_16-23_gifs-del-readme.md
new file mode 100644
index 0000000..b6e5d4f
--- /dev/null
+++ b/.template/changelog/2026-08-18_16-23_gifs-del-readme.md
@@ -0,0 +1,60 @@
+# Cuatro GIF para el README: el argumento, el artefacto y la prueba
+
+**Fecha:** 2026-08-18 16:23
+**Tipo:** Documentación
+**Requisitos:** Ninguno (cambio sobre el andamiaje de la plantilla)
+
+## Qué se hizo
+
+El README explicaba el protocolo entero en prosa. Ahora lo enseña, con cuatro GIF que hablan tres
+idiomas distintos a propósito:
+
+- **`comparativa.gif`** (cabecera) — dos carriles de tarjetas: sin plantilla, cada sesión empieza
+ de cero y la flecha vuelve al principio; con plantilla, la línea llega recta a un check verde.
+ Es un **argumento**.
+- **`flujo.gif`** ("¿Cómo funciona el protocolo?") — un árbol de archivos creciendo fase a fase:
+ los `docs/` marcados `vacío` que se llenan, `.template/` tachándose, y la pastilla de la ficha
+ pasando de Acordada a En construcción y a Verificada. Es un **artefacto**: no explica el
+ producto, es el producto.
+- **`cobertura.gif`** (sección nueva "La regla que lo sostiene") — el script pasando con la ficha
+ en construcción, fallando al marcarla Verificada sin el test, y volviendo a pasar cuando existe.
+ Es una **prueba**.
+- **`excepcion.gif`** (`docs/features/README.md`) — columna vacía y "no aplica" rechazados; con la
+ razón concreta, verde.
+
+El diagrama Mermaid del ciclo se elimina: contaba lo mismo que `flujo.gif` con menos detalle, y
+tener los dos era repetirse.
+
+Todo vive en `.template/assets/`, junto con los tres generadores. Al inicializar un proyecto la
+carpeta se borra y el README se reescribe, así que imágenes y referencias desaparecen juntas.
+
+## Qué se modificó
+
+- `.template/assets/` — nueva: cuatro GIF, tres generadores (`gen-comparativa.py`, `gen-flujo.py`,
+ `gen-terminal.py`) y un `README.md` con cómo regenerarlos
+- `README.md` — GIF de cabecera bajo la llamada a la acción; `flujo.gif` sustituye al diagrama
+ Mermaid; nueva sección "La regla que lo sostiene" con `cobertura.gif`
+- `docs/features/README.md` — `excepcion.gif` tras la regla de la tercera columna
+- `.template/README.md` — documenta la carpeta `assets/`
+- `CLAUDE.md` y `.claude/commands/init-proyecto.md` — al borrar `.template/` hay que quitar las
+ referencias que apuntan a sus imágenes; se nombra la de `docs/features/README.md`
+
+## Por qué
+
+Un repositorio plantilla tiene la particularidad de que su página de inicio es el producto: lo
+primero que hace alguien es mirar el README y decidir en quince segundos si esto le sirve. Diez
+puntos numerados no ganan esa decisión.
+
+Los tres registros son deliberados y no intercambiables. El diagrama de carriles es una afirmación
+que nadie puede verificar —como toda comparativa de antes y después, es una caricatura amable del
+problema—. El árbol que crece es concreto pero sigue siendo un dibujo. Los GIF de terminal son lo
+único del README que un escéptico puede reproducir en su máquina, y por eso se quedan aunque sean
+los más feos: son la parte que demuestra en vez de prometer.
+
+## Verificado
+
+- Los GIF de terminal se componen de la salida literal de `verificar-cobertura.mjs` ejecutado
+ sobre un proyecto de prueba (un catálogo de vinilos, el ejemplo del propio `CLAUDE.md`). Ni una
+ línea de ese texto está escrita a mano.
+- Peso total de los cuatro: 675 KB. El mayor, `flujo.gif`, 244 KB.
+- `node scripts/verificar-cobertura.mjs` sigue saliendo limpio.
diff --git a/CLAUDE.md b/CLAUDE.md
index b57d464..566b105 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -108,7 +108,8 @@ Puedes lanzar el proceso completo con `/init-proyecto`.
`docs/features/` se queda como está: empieza sin fichas, solo con su `README.md`.
8. **`mejoras/backlog.md`** — borra el ejemplo comentado y déjalo listo para entradas reales.
9. **`.template/`** — bórrala entera (`rm -rf .template`). Es el historial de la plantilla, no
- del proyecto.
+ del proyecto. Con ella se van también las imágenes del README, así que quita las referencias
+ que queden apuntando a `.template/assets/` (hay una en `docs/features/README.md`).
10. **Verificación final** — busca referencias sobrantes:
`grep -ril "plantilla\|template" . --exclude-dir=.git --exclude-dir=node_modules`.
Revisa cada resultado y corrígelo si habla de la plantilla en lugar del proyecto.
diff --git a/README.md b/README.md
index b0cc62d..6d504c6 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,10 @@
Usar esta plantilla →
+
+
+
+
---
## ¿Qué es esto?
@@ -52,16 +56,9 @@ Es agnóstica al stack. El protocolo funciona igual con Next.js, Astro, FastAPI
## ¿Cómo funciona el protocolo?
-```mermaid
-flowchart LR
- A["Requisito en docs/prd.md con criterio de aceptación"]
- B["Ficha de feature Acordada"]
- C["En construcción"]
- D["Tests escritos sobre el código real"]
- E["Verificada"]
- F["PR con evidencia CI verifica la cobertura"]
- A --> B --> C --> D --> E --> F
-```
+
+
+
1. **Cualquier sesión empieza leyendo `docs/`.** Si están vacíos o incompletos, el agente pregunta antes de actuar. Y no pide los ocho documentos: pide los que correspondan al tamaño del proyecto.
2. **Cada funcionalidad del PRD lleva ID y criterio de aceptación comprobable.** "Dado…, cuando…, entonces…", con un resultado que se pueda mirar. Ese criterio es el que después se convierte en test.
@@ -74,6 +71,20 @@ flowchart LR
9. **Antes de mergear a producción**, se ejecuta `/security-review` para detectar vulnerabilidades, credenciales filtradas y problemas comunes.
10. **Las ideas que no entran ahora se anotan en `mejoras/`** sin interrumpir el flujo actual.
+### La regla que lo sostiene
+
+Ningún requisito se queda sin su tercera columna: **o lleva la ruta del test que lo valida, o lleva
+la razón concreta por la que no se puede validar así.** Y no es una norma de buena voluntad — hay
+un script que la comprueba, y corre en CI con cada pull request.
+
+La gracia está en cuándo aprieta y cuándo no. Mientras la ficha está en construcción no exige nada:
+los tests se escriben después de implementar. En el momento en que la das por **Verificada**, el
+test prometido tiene que existir.
+
+
+
+
+
---
## Comandos
diff --git a/docs/features/README.md b/docs/features/README.md
index a784ff8..5686bd3 100644
--- a/docs/features/README.md
+++ b/docs/features/README.md
@@ -99,6 +99,10 @@ construcción* no exige que los archivos existan: los tests van después de impl
Una fila puede declarar varios tests separándolos por comas.
+