English · Español
Teoría 05 — Repetición espaciada¶
🇪🇸 Una pregunta de examen fallada no se descarta — se convierte en una tarjeta de revisión. El algoritmo SM-2 (Wozniak, 1990) determina cuándo se vuelve a presentar, basándose en lo bien que se recordó. La intuición: el olvido es exponencial, así que el espaciado óptimo también lo es. Una tarjeta correcta hoy se ve mañana, pasado, en 6 días, luego 14, 30, 60 — siempre justo antes de que se olvide. Curva de retención maximizada con coste de tiempo mínimo.
Por qué repetición espaciada para los fallos de examen¶
Una pregunta de examen fallada es un dato de alta señal: identifica un hueco preciso en el dominio del learner. Volverla a presentar inmediatamente (al día siguiente) y en intervalos crecientes a medida que la memoria se consolida da una curva de retención muy superior a la de releer el material fuente. La literatura sobre esto tiene décadas; Ebbinghaus (1885) caracterizó la curva del olvido, Wozniak (1985, 1990) formalizó SM-1 a SM-2 para optimizar frente a ella, Ye et al. (2024) extendieron la familia con FSRS-5. El currículo del tutor de gramática ejercita un conjunto discreto de conjugaciones — exactamente el tipo de datos para el que las ganancias de la repetición espaciada son mayores.
El portal hace la elección automática: cualquier pregunta de examen respondida incorrectamente (exam_responses.correct_at_first_try = FALSE) siembra una fila en review_cards. El learner ve la tarjeta agendada en el widget "Today's reviews"; calificarla del 0 al 5 avanza o reinicia su intervalo. No hace falta una acción explícita de "añadir a mi mazo".
El algoritmo SM-2¶
El estado de SM-2 por tarjeta son dos números reales y una fecha:
| Campo | Tipo | Valor inicial | Significado |
|---|---|---|---|
ease |
REAL | 2.5 | Factor de facilidad; multiplicador sobre el siguiente intervalo. |
interval_days |
INT | 1 | Días hasta la siguiente revisión. |
due_on |
DATE | hoy + 1 | Cuándo aparece la tarjeta la próxima vez. |
El input de calificación es q ∈ {0, 1, 2, 3, 4, 5} según el paper original:
- 0 — laguna total; sin recuerdo.
- 1 — incorrecto; la respuesta correcta me sonaba.
- 2 — incorrecto; la respuesta correcta estaba en la punta de la lengua.
- 3 — correcto, pero con dificultad seria.
- 4 — correcto, tras dudar.
- 5 — recuerdo perfecto.
La regla de actualización, escrita para nuestra tabla:
def sm2_update(card, q):
if q < 3:
# Failure — restart the interval.
card.interval_days = 1
card.ease = max(1.3, card.ease - 0.20)
else:
# Success — multiply the interval.
if card.repetition_count == 0:
card.interval_days = 1
elif card.repetition_count == 1:
card.interval_days = 6
else:
card.interval_days = round(card.interval_days * card.ease)
# Easiness drifts with quality, bounded below at 1.3.
delta = 0.10 - (5 - q) * (0.08 + (5 - q) * 0.02)
card.ease = max(1.3, card.ease + delta)
card.due_on = today + card.interval_days
card.last_reviewed_at = now()
El portal no lleva repetition_count como columna separada. Se infiere de last_reviewed_at IS NULL (count = 0) y last_reviewed_at AND interval_days = 1 (count = 1, recién reseteado). Dos estados bastan para la ramificación de SM-2.
La fórmula de drift del ease¶
La línea:
se descompone en:
| q | (5 − q) | delta | cambio del ease |
|---|---|---|---|
| 5 | 0 | +0.10 | crece |
| 4 | 1 | 0.00 | plano |
| 3 | 2 | −0.14 | encoge |
| 2 | 3 | −0.32 | (se dispara la rama de fallo; ease −= 0.20 en su lugar) |
| 1 | 4 | −0.54 | (se dispara la rama de fallo) |
| 0 | 5 | −0.80 | (se dispara la rama de fallo) |
El ease sigue la percepción de dificultad del learner por tarjeta. Una tarjeta calificada consistentemente con 4 se queda en ease 2.5; una calificada con 5 cada vez sube despacio; una calificada con 3 cada vez baja despacio. Una vez en 1.3 (suelo), el algoritmo prácticamente se aplana y la tarjeta se queda en una cadencia ajustada para siempre — por diseño; algunas tarjetas son simplemente difíciles.
Geometría del intervalo¶
Una tarjeta que se califica con 4 cada vez, con ease = 2.5, sigue:
| Revisión | Intervalo (días) | Fecha (si empezó el 2026-05-23) |
|---|---|---|
| 1 | 1 | 2026-05-24 |
| 2 | 6 | 2026-05-30 |
| 3 | 15 (6 × 2.5) | 2026-06-14 |
| 4 | 38 (15 × 2.5) | 2026-07-22 |
| 5 | 95 (38 × 2.5) | 2026-10-25 |
| 6 | 238 (95 × 2.5) | 2027-06-20 |
Seis revisiones exitosas empujan una tarjeta un año hacia adelante. La probabilidad de retención en cada revisión es empíricamente ~90% (calibración de Wozniak; replicada por los usuarios de Anki a escala).
Retiro de tarjetas¶
El portal retira tarjetas cuando se cumple:
Una tarjeta retirada desaparece de la cola diaria de revisión. El pool de preguntas del tutor de gramática es lo bastante pequeño para que retirar tarjetas sea deseable — si no, el learner acumula una cola creciente de "sí, sigo sabiendo eat → ate". Las tarjetas retiradas viven en la tabla para auditoría; se añade una columna retired_at en la migración alembic phase-41-add-card-retirement.py.
FSRS-5 como opción futura¶
El Free Spaced Repetition Scheduler (FSRS-5, Ye et al. 2024 — sucesor de FSRS-4.5) reemplaza la actualización heurística de SM-2 con un modelo de retención de tres parámetros ajustado por usuario:
donde \(t\) son los días desde la última revisión, \(S\) es estabilidad (análoga al intervalo), \(w_0\) es un parámetro global de escala ajustado a partir del historial de revisiones del learner. El scheduler elige el momento de la próxima revisión para acertar una probabilidad de retención objetivo (90% por defecto), lo cual es un objetivo más directo que el esquema basado en multiplicador de intervalo de SM-2.
La decisión de la Fase 41: SM-2 para el MVP, FSRS-5 aplazado a una futura enmienda. Tres razones:
- Cold-start. FSRS-5 necesita ~50 revisiones históricas por learner para ajustar sus parámetros de forma fiable. El portal arranca con cero revisiones; la heurística de SM-2 está calibrada para escenarios sin historial.
- Coste de implementación. SM-2 son 20 líneas de Python. FSRS-5 son ~300 líneas más un bucle de ajuste por usuario. El presupuesto del MVP no justifica lo segundo sin evidencia de que lo primero se quede corto.
- Coste de validación. Comparar ambos requiere desplegar los dos y hacer A/B-testing por learner durante meses. El volumen de datos por learner del tutor de gramática lo hace inviable a escala de un único learner.
Si/cuando se incorpora un segundo o tercer learner (por §A3 del addendum), el historial agregado de revisiones puede justificar el upgrade a FSRS-5. La ruta de migración está documentada en BLUEPRINT.md §6: FSRS-5 lee la misma tabla review_cards, añade columnas stability y difficulty, y corre en paralelo a SM-2 durante una fase antes del corte.
Ciclo de vida de la tarjeta¶
El ciclo de vida completo de una tarjeta de revisión, como máquina de estados:
[seed]
│
▼
[new] ← exam_response with correct_at_first_try = FALSE inserts
│ with ease=2.5, interval_days=1, due_on=today+1
▼
[due] ← due_on <= today; appears on the Today's reviews widget
│
├──q≥3──► [reviewed-success] → interval grows, due_on advances
│ │
│ └──interval>180 AND ease>2.8──► [retired]
│
└──q<3──► [reviewed-failure] → interval=1, ease shrinks, due_on=today+1
│
└──► [due] again tomorrow
El estado se calcula a partir de (due_on, last_reviewed_at, ease, interval_days); no se necesita una columna status.
Widget diario de revisión¶
El right rail en las páginas de teoría (y una página de revisión dedicada) muestra:
donde N = SELECT COUNT(*) FROM review_cards WHERE student_id = ? AND due_on <= CURRENT_DATE AND ease ≤ 2.8.
El widget se refresca en cada carga de página. Iniciar una sesión de revisión avanza por las tarjetas en orden due_on ASC (las más antiguas primero), una a una, con la UI por tarjeta de la pantalla 4 de la teoría 04.
Un contador de racha de revisión aparece junto al widget cuando el usuario ha despejado la cola diaria: "Today's reviews complete · 12-day streak." La racha es una tabla lateral (review_streaks) calculada por la noche; no entra en el alcance del modelo de datos de esta fase (es una mejora de Fase 41+1).
Cómo los fallos de examen siembran tarjetas¶
El punto de integración: cuando se inserta una fila en exam_responses con correct_at_first_try = FALSE, un trigger o un hook a nivel de aplicación asegura que existe una fila en review_cards para (student_id, exam_question_id):
INSERT INTO review_cards (
student_id, exam_question_id, ease, interval_days, due_on
)
VALUES (?, ?, 2.5, 1, DATE('now', '+1 day'))
ON CONFLICT (student_id, exam_question_id) DO NOTHING;
El ON CONFLICT DO NOTHING es crítico: si el learner ya había fallado esta pregunta antes, se preserva el estado de la tarjeta existente. El nuevo fallo se captura en exam_responses pero no resetea el estado SR — la tarjeta ya está en el sistema.
El formato de rúbrica de la Fase 30 (respuestas de examen estructuradas) incluye una pista difficulty que, en el futuro de FSRS-5, se convertirá en el valor inicial de difficulty. SM-2 la ignora.
Analíticas por tarjeta¶
El MVP no muestra ninguna, pero los datos se recogen. Una vista de analíticas futura calcularía:
- Retención por fase. ¿Qué fracción de tarjetas de la fase 32 se recuerdan con q ≥ 4 frente a la fase 11?
- Tiempo hasta retiro. Mediana del número de revisiones antes de que una tarjeta se retire.
- Distribución del ease. Histograma a lo largo del mazo — una cola izquierda pesada sugiere que el set de preguntas es demasiado duro.
Estas vistas son útiles a escala multi-learner. El MVP de un único learner puede calcularlas ad hoc contra el archivo SQLite vía un notebook de Marimo (la herramienta de la Fase 4).
Lo que este diseño NO incluye¶
- Sin detección de "leeches". La regla leech de Anki (una tarjeta que falla N veces dispara revisión manual) es una mejora futura.
- Sin undo. Una vez calificada una tarjeta, la calificación se compromete. Añadir undo significa un write-ahead log; fuera de alcance.
- Sin mazos filtrados. Los mazos filtrados/temporales de Anki (p. ej., "todas las tarjetas que fallé la semana pasada") no tienen análogo en el MVP.
- Sin estadísticas entre learners. Incluso cuando N > 1, los datos son por learner; las estadísticas agregadas deliberadamente no se exponen (privacidad).
- Sin notificación push móvil. Emails / pushes "Tienes 5 revisiones pendientes" — anti-objetivo a nivel de despliegue.
Recapitulación en un párrafo¶
Una pregunta de examen fallada se convierte en una fila en review_cards con estado SM-2. Cada revisión posterior aplica la regla de actualización de SM-2: q ≥ 3 multiplica el intervalo por el factor ease actual (que va a la deriva hacia arriba o abajo), q < 3 resetea el intervalo a un día y encoge el ease. Las tarjetas se retiran cuando se da intervalo > 180 y ease > 2.8 a la vez. FSRS-5 es la alternativa moderna, aplazada hasta que la población de learners justifique el coste de implementación. El widget diario de revisión en el right rail es la única superficie; el algoritmo en sí son veinte líneas.
Siguiente: theory/06-bilingual-policy-in-the-portal.md — cómo §0.6 se materializa en los caminos del código.