Bitácora del curso
Historia cronológica de las sesiones. La más reciente arriba. La escribe el profesor al final de cada sesión.
2026-09-30 — Sesión 12.28 · CIERRE V1: el proyecto se da por terminado en su versión 1.0
Qué se hizo:
- Estado final del proyecto (V1): curso completo 59/59 lecciones impartidas y publicadas, 100% bilingüe ES/EN (175+176 docs, cero fallback), sitio propio sin dependencias (82 tests verdes, ruff OK, CI doble job con build + auditoría web: 157 enlaces, axe 0, Lighthouse con gate), PWA con offline bilingüe, GA4 consentido con eventos propios del curso (lesson_read/exercises_open/solutions_open/search), SEO bilingüe preparado (sitemap 532 URLs + hreflang con SITE_URL, demostrado en local), CONTRIBUTING.md/SECURITY.md en repo y sitio, despliegue a Cloudflare Pages por push a main. Etiqueta git
v1.0.0sobre el cierre; pyproject ya declaraba 1.0.0. - README revisado al cierre: 79→82 tests (x2) y ~485→~488 páginas en la doc del despliegue; CONTRIBUTING con los 82. El resto de cifras ya estaban al día (SW v12, badges, Contribuir).
- Lo que queda para el alumno (NO es trabajo del sitio, es del curso):
- Corregir la Lección 01 (HTTP): ejercicios YA ENTREGADOS por el alumno → corrección en chat, actualización de dominadas y anotar conceptos HTTP fallados para repaso en la 02 (pending.md).
- Corregir la Lección 00b (Modelado): implementación del modelo v1 y ejercicios pendientes de entrega → corregir al llegar; verificar que distingue reglas de negocio de invariantes estructurales (si confunde, repaso en la 08).
- Confirmar GA4 en 24–48 h: Informes → Tiempo real con una visita aceptando estadísticas; luego Definiciones de eventos (lesson_read, exercises_open, solutions_open, search) y marcar lesson_read/solutions_open como eventos clave.
- Operativo de Pages (único pendiente de infraestructura, ajustado en la verificación final): producción ya sirve el código de main (consent.js v6 en lecciones, eventos del curso en consent.js, SW professor-v12, bitácora hasta 12.28; flake de Chrome en axe resuelto con rerun) pero la build salió SIN SITE_URL: sitemap.xml 404 y cero hreflang en el head de lecciones. La variable no llegó al entorno Production o la build estaba en vuelo antes de guardarla: basta UN redeploy más con SITE_URL en Production y repetir la verificación del sitemap (532 URLs) y los hreflang.
Verificación: producción revisada commit a commit (75ed321 servido); README/CONTRIBUTING sin cifras desfasadas; memoria de las 28 sesiones coherente.
Siguiente paso (V1.x): correcciones 00b/01 del alumno en el ritual 1x1; confirmar GA4; UN redeploy de Pages con SITE_URL en Production (y verificar sitemap+hreflang). El sitio no requiere más trabajo salvo incidencias o ideas nuevas.
2026-09-30 — Sesión 12.27 · Cierre del proyecto: producción atascada en 75ed321, falta el redeploy con SITE_URL
Qué se hizo:
- Verificación de producción post-SITE_URL: el sitemap sigue en 404 y las lecciones siguen con main.js?v=6 sin consent.js — producción sirve el despliegue de 75ed321 (contributing/security 200, SW v11). Los commits d3d3b2d (memoria) y 7de0167 (eventos + consent universal) NO han aterrizado tras ~25 min. Causa probable: editar variables de entorno en Pages no dispara build; el redeploy manual no se lanzó o está en cola.
- Acción: push de memoria (este commit) para encolar una build nueva de Pages; al aterriz, verificar sitemap 532 URLs + hreflang + consent.js?v=6 en lecciones.
- Guía GA4 entregada al alumno: eventos personalizados (Administrar → Definiciones de eventos: lesson_read/exercises_open/solutions_open/search con sus parámetros), key events (Marcar como evento clave), informe Páginas y pantallas (Informes → Interacción), y Realtime como verificación inmediata.
Verificación: producción mapeada commit a commit (75ed321 en vivo; 7de0167 pendiente de build). CI de main siempre verde.
Siguiente paso: confirmar en el dashboard de Pages que la última build (7de0167+) termina en Success y repetir la verificación; con eso y las correcciones 00b/01 del alumno, el proyecto queda cerrado.
2026-09-30 — Sesión 12.26 · Realtime GA4 documentado, consent.js universal con eventos del curso y SEO bilingüe listo para disparar
Qué se hizo:
- Recorrido de producción para Realtime (la respuesta al alumno): navegando lecciones con estadísticas aceptadas, cada página dispara un page_view a /g/collect; en GA4 → Informes → Tiempo real deben verse el card "Usuarios en las últimas 30 min" (=1), el card de vistas de página con el título de la lección visitada, la tarjeta de ubicación (the-professor-ete.pages.dev) y, dentro de la tarjeta "Vistas por título de página" o en el Explorador,
/lessons/<slug>/lesson/. Card "Eventos por nombre": solo page_view de momento. - Hueco cazado en producción: las lecciones no medían. consent.js solo iba en las portadas (chrome.page() y las artesanales); el <head> de lecciones de build.py no lo traía (y su main.js seguía en v6). En la práctica: sin banner en lecciones ni GA en el 95% del tiempo de lectura. Fix: consent.js universal en los 4 <head> (build.py, chrome.py, serve.py, las dos portadas artesanales).
- Eventos propios del curso (en consent.js, que ya conoce el consentimiento e idioma):
lesson_read(lesson_id, lesson_doc, language) en cualquier doc de lección;exercises_openysolutions_opendedicados;searchcon search_term (≥2 letras, uno por término distinto, truncado 100) en el buscador de portada. Todos pasan por trackEvent, no-op sin gtag: NADA viaja a Google sin aceptación. Page_view inicial lo envía GA4; navegación clásica (MPA). - Versionado: consent.js v5→v6, main.js v7→v8 (y el v6 huérfano de build.py corregido), SW professor-v11→v12; README con la versión del SW.
Verificación: 82 tests verdes, ruff OK, node --check; en navegador: lección con consentimiento previo → lesson_read correcto; exercises → lesson_read+exercises_open; solutions → lesson_read+solutions_open; banner aceptado DENTRO de una página sin lección → GA carga; buscador → search con término. SEO bilingüe demostrado en local: con SITE_URL, dist 488 páginas, sitemap.xml con 532 URLs y alternates es/en/x-default, hreflang en lecciones; en producción aún 404 (variable sin fijar).
Siguiente paso: fijar SITE_URL=https://the-professor-ete.pages.dev en Pages → Settings → Environment variables (Production) → redeploy → verificar /sitemap.xml 200 y hreflang en vivo; comprobar Tiempo real de GA4 con la sesión de verificación.
2026-09-30 — Sesión 12.25 · GA verificado en producción: la etiqueta mide; el aviso del dashboard es del asistente
Qué se hizo:
- URL de producción registrada (hasta ahora no estaba en el repo ni en la memoria): https://the-professor-ete.pages.dev — Cloudflare Pages.
- Verificación end-to-end en producción, en navegador real: curl confirma consent.js?v=5 con el ID G-8R9VHZWTXL y SW professor-v11 servidos; aceptando "Estadísticas" en el banner, googletagmanager carga (200) y sale el page_view a region1.google-analytics.com/g/collect → 204 con
dl=https://the-professor-ete.pages.dev/,gcs=G101(analytics granted / ads denied),ep.anonymize_ip=truey beacon. La etiqueta MIDE. - Diagnóstico del aviso "la recogida de datos no está habilitada": es el estado del asistente de configuración de GA4, no un error del sitio. No hay que pegar el snippet que sugiere en
<head>: el nuestro es el mismo gtag pero gated por consentimiento (RGPD, sesión 12.23). GA4 tarda hasta 24 h en procesar hits estándar (Realtime los muestra al momento) y el sitio solo mide a quien acepta — con poco tráfico, el asistente puede tardar en dar por activa la recogida.
Verificación: página ES de producción 200, /en/ 200, assets v5/v11 correctos; flujo de consentimiento completo en producción (aceptar → gtag + collect 204).
Siguiente paso: fijar SITE_URL=https://the-professor-ete.pages.dev en Pages → Settings → Environment variables (activa sitemap.xml bilingüe + hreflang); si en 24–48 h Realtime no muestra tráfico con visitas que acepten, revisar el dashboard de GA4 (stream de datos, ID correcto).
2026-09-30 — Sesión 12.24 · CONTRIBUTING.md y SECURITY.md (en el repo y en el sitio): dos bugs del parser cazados en el intento
Qué se hizo:
- Nuevas guías del repo:
CONTRIBUTING.md(regla de oro: memory/ no se toca salvo el profesor-agente; contenido por módulo vs herramientas del sitio; verificación local sin dependencias; qué comprueba el CI; convenciones de commit) ySECURITY.md(alcance real del sitio estático, reporte por advisory privado, SLA 48h, decisiones de diseño relevantes). README enlaza ambas desde Contribuir. - CI lo cazó al instante: el push puso en rojo la auditoría web — el README renderizado enlazaba
CONTRIBUTING.md/SECURITY.mdy el checker (que ahora rastrea ambas páginas como semillas) las encontró rotas. El parser las trataba como "doc de la misma lección" con slug vacío (→ /lessons//…). - Solución al estilo del sitio: son páginas propias —
/contributing/y/security/en MD_PAGES de serve.py, congeladas en build_static.py (487 páginas ahora) y mapeadas en REPO_DOC_ROUTES de build.py; privacidad/requisitos/style-guide referenciados desde docs de la raíz también mapeados. - Bug preexistente 1 — LICENSE literal:
LICENSEse pintaba como texto (el parser solo reescribe.md). Nueva regla ROOT_FILE_LINK (LICENSE → /security/, donde está la referencia). - Bug preexistente 2 — enlaces externos literales: TODO
textode README/CURRICULUM/docs se pintaba sin enlace (el subconjunto del parser no cubría URLs). Nueva regla HTTP_LINK con rel="noopener" y & escampadas (&) — en /readme/ ahora hay 3 enlaces de verdad donde antes había texto. - Tests nuevos (79→82): reescritura de docs de la raíz, enlaces externos con query string, privacidad desde docs raíz.
Verificación: 82 tests verdes, ruff OK; dist 487 páginas (con /contributing/ y /security/); link checker 157 OK (con las dos semillas nuevas); axe 0 (10 págs); páginas nuevas vistas en navegador con el shell (kicker + hero + prose).
Siguiente paso: fijar SITE_URL en Cloudflare Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.23 · Google Analytics activado (G-8R9VHZWTXL) tras consentimiento
Qué se hizo:
- GA activo con el ID de medida real:
GA_MEASUREMENT_ID('' → 'G-8R9VHZWTXL') ensite/js/consent.js. La integración ya estaba construida y gated (sesiones 12.7/12.11): script async de googletagmanager + dataLayer +gtag('js')+gtag('config')conanonymize_ip,transport_type: 'beacon'y consent mode (analytics_storage: 'granted',ad_storage: 'denied') — se carga SOLO tras aceptar "Estadísticas", nunca antes. Nada del snippet plano del usuario: la versión del sitio replica el snippet oficial pero respetando RGPD. - Revocación real en caliente (nuevo):
denyAnalyticsIfLoaded()— si GA llegó a cargarse en la página (se aceptó antes) y el usuario revoca y rechaza, se le ordena parar congtag('consent','update',{analytics_storage:'denied',ad_storage:'denied'}). Hasta ahora la revocación solo valía desde la visita siguiente. - Versionado completo: consent.js v4→v5 en el shell (chrome.py), las dos portadas artesanales (index.html/index.en.html) y la inyección bajo demanda de main.js; SW
professor-v10→professor-v11; README actualizado con la versión del SW. La política de privacidad no cambia: ya describía exactamente esta configuración (solo si aceptas, IP anonimizada, sin cookies de identidad, sin ads). - Flujo verificado en navegador real: estado limpio → sin gtag ni dataLayer; "Rechazar" → cero peticiones a googletagmanager; "Aceptar estadísticas" → script cargado con el ID exacto y dataLayer vivo (eventos gtm.dom/gtm.load); recarga con preferencia guardada → GA carga solo y el banner no reaparece.
Verificación: 79 tests verdes, ruff OK, node --check de los 3 JS; dist 485 páginas con consent.js?v=5, professor-v11 y el ID dentro del asset; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: fijar SITE_URL en Cloudflare Pages (activa sitemap+hreflang en producción); correcciones 00b/01 del alumno; en producción, comprobar en el dashboard de GA que llegan eventos.
2026-09-30 — Sesión 12.22 · README al día y revisión completa de la web: cacé y arreglé dos fugas de español en las páginas EN
Qué se hizo:
- README actualizado a la realidad del despliegue: badges (tests 76→79 verdes; documentos 184 md→350 md ES+EN; lecciones ahora "59/59 bilingües"), la tabla de
lessons/refleja el doble idioma, el bloque "Idiomas" reescrito (despliegue completo: 59/59 traducidas, cero fallback, buscador EN sin huecos), versión del service worker en la doc de Pages (professor-v6→professor-v10), "El sitio por dentro" sin la mención del fallback y "Contribuir" con los 79 tests. - Recorrido del sitio EN en navegador (portada /en/, catálogo completo con el módulo 12, conmutador ES/EN, lección 57 EN): portada y catálogo salían perfectos (hero, 59/59 · 13 módulos · 177 documentos, cookies y footer en inglés, EN activo en el conmutador), pero en las páginas de lección EN aparecían dos cadenas de UI en español: el desplegable del índice ("En esta lección") y el aria-label del breadcrumb ("Ruta de navegación").
- Fix bilingüe en las herramientas del sitio:
render_tocrecibelangy toma el summary del diccionario UI (nuevas clavestoc_summaryybreadcrumb_ariaen chrome.py, es/en);breadcrumb()ahora recibe el idioma yrender_pagese lo pasa — las páginas sueltas (memoria/curriculum) siguen ES-only por diseño. Cobertura nueva en tests: breadcrumb EN ("Breadcrumb") y TOC EN ("In this lesson", sin "En esta lección").
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; cero "En esta lección"/"Ruta de navegación" en /en/ del dist (175 páginas de lección con "In this lesson"); cero fallbacks de no-traducida en TODO /en/; link checker 154 OK; axe 0 violaciones (10 páginas); fix comprobado en vivo en el navegador (lección 57 EN con TOC y migas en inglés; ES intacto).
Siguiente paso: fijar SITE_URL en Cloudflare Pages (activa sitemap+hreflang en producción); correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.21 · Módulo 12 traducido al EN: el sitio queda 100% bilingüe
Qué se hizo:
- Lecciones 52–57 completas en inglés (18 documentos), el módulo final: Clean/Hexagonal y DDD (el anillo hexagonal ya existía en la 24/25, VOs Money/SeatRef con invariantes en el constructor, el agregado Reservation extraído con mappers, DDD parcial con trigger en el ADR, lenguaje ubicuo y ACL), monolito modular vs microservicios (la tabla honesta de 8 dimensiones, la constitución de fronteras con test en CI, los contratos síncronos/async entre módulos, los 5 umbrales del strangle y el ratio 3:1-4:1 de la frontera preparada), resiliencia (timeouts por operación p99×20, reintentos idempotentes con backoff+jitter, el breaker que localiza la caída y es métrica, la degradación jerárquica que jamás toca las invariantes, el game day de caos), escalado (ley cero stateless con réplicas y read-your-writes, el cuello de escritura antes del peldaño, partición por evento con su contrato de queries, la escalera completa con triggers medibles), cumplimiento (RGPD en el ciclo del dato con bases legales y la excepción del dinero, el mapa del Art. 30, SOC 2 como controles con evidencias que el curso ya produce, audit log + retención + runbook de brecha, el DPO interno) y proyecto final (la defensa con las 10 garantías y sus demos, el REQUEST y el FALLO, el postmortem simulado con los 5 incidentes del pool, el pitch de negocio y el cierre del oficio) — teoría, ejercicios y soluciones íntegras con python/SQL/ASCII y tablas.
- El sitio EN cubre los módulos 0–12 completos: 59 de 59 lecciones sin aviso de fallback. Despliegue bilingüe terminado.
- Test actualizado a la nueva realidad:
test_catalogo_en_usa_titulos_del_overlayexigía el chip "Spanish only" en el catálogo EN (escrito cuando quedaban lecciones sin traducir); ahora verifica lo contrario (0 chips de fallback) y el título del overlay. 79 tests verdes.
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; cero avisos de no-traducida en TODO /en/; H1 de 52–57 idénticos al overlay i18n; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: fijar SITE_URL en Cloudflare Pages (activa sitemap+hreflang en producción); correcciones 00b/01 del alumno; README badges (79 tests).
2026-09-30 — Sesión 12.20 · Módulo 11 de habilidades no técnicas traducido al EN
Qué se hizo:
- Lecciones 48–51 completas en inglés (12 documentos): documentación y ADRs (la matriz de Diátaxis con el lector de cada formato, el ADR con las 5 claves — una decisión, contexto con números, consecuencias +/−, trigger de revisión y Supersede —, el índice de 12 decisiones del curso, docs-as-code con caducidad y tests-guard), estimar y dividir (el rango con descomposición y gatillo de re-estimación, el histórico que calibra y clasifica el desvío, trozos verticales con demo contra el corte horizontal, el "sí, pero" estructurado con condiciones/alcance/opciones), revisión de código (el orden de impacto QUÉ→contrato→seguridad→corrección→tests→estilo, los 5 ADNs del repo, el comentario hecho+impacto+camino o pregunta, la escala bloqueante/debería/nit/pregunta, el desacuerdo con datos que escala al ADR, el self-review del equipo de 1 con las 24 h de espera) y entender el negocio (el mapa del dinero pieza-técnica→línea-de-negocio, las 5 whys del requerimiento, la traducción a conversión y euros, el backlog valor/esfuerzo/riesgo contra la TIC, el "no" con alternativa, la traducción bidireccional como examen del oficio) — teoría, ejercicios y soluciones íntegras con markdown/python/tablas.
- El sitio EN ya cubre los módulos 0–11 completos (00–51): 52 de 59 lecciones sin aviso de fallback. Solo queda el módulo 12 (52–57).
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; en vivo: las 12 páginas de 48–51 EN responden 200 con cero avisos de no-traducida y H1 idénticos al overlay i18n; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 12 de nivel empresarial (52–57) y dejar el sitio 100% bilingüe; fijar SITE_URL en Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.19 · Módulo 10 de observabilidad traducido al EN
Qué se hizo:
- Lecciones 45–47 completas en inglés (9 documentos): logging estructurado (JSON por línea con ts UTC/level/event/trace_id/version, middleware contextvars + ContextFilter, LOGGING adulto con stdout y django.request/db.backends en WARNING, catálogo de eventos por nivel,
extraen vez de f-strings, audit log ≠ log diagnóstico), métricas/trazas/alertas (RED con label de plantilla contra la explosión de cardinalidad, métricas de negocio sagas_in_unknown/outbox_pending/dlq_depth, trazas OTel con sampling 0.1 + keep-errors, SLOs con PromQL y error budget con burn 14.4 multi-ventana, tabla de alertas page/ticket, heartbeat de expiración, game day) y depurar en producción (protocolo confirmar→acotar→mitigar→localizar→explicar, caja de herramientas dash→trace→log→diff→flag con el caso del prefetch de asientos bajo rate limit, reproducción fuera de prod con dataset anonimizado, postmortem blameless con acciones issue/dueño/fecha, on-call sostenible con MOC/blackout/recuperación y MTTR como métrica de madurez) — teoría, ejercicios y soluciones íntegras con código Python/PromQL/YAML/markdown y tablas. - El sitio EN ya cubre los módulos 0–10 completos (00–47): 48 de 59 lecciones sin aviso de fallback. Quedan los módulos 11–12 (48–57).
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; en vivo: las 9 páginas de 45–47 EN responden 200 con cero avisos de no-traducida; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 11 de habilidades no técnicas (48–51) si se continúa el despliegue EN; fijar SITE_URL en Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.18 · Módulo 9 de despliegue traducido al EN
Qué se hizo:
- Lecciones 40–44 completas en inglés (15 documentos): Docker (Dockerfile multi-stage línea a línea,.dockerignore y secretos, compose de dev con service_healthy, migraciones como job y no en el arranque, digest pinneado y reproducibilidad), CI/CD (los gates del merge, digest como artefacto auditable, blue-green + expand-contract con rollback probado, migraciones en el release con los dos JAMÁS, DORA reducida), nube (el mapa de los tres proveedores sin vendor-lock, PaaS vs K8s vs VM, Postgres gestionado con restore drill, VPC mínima sin NAT, IAM con roles de servicio y mínimo privilegio, la factura con huérfanos), Kubernetes y servicios gestionados (la matriz de decisión, Deployment/Service/ConfigMap+Secret con readiness vs liveness, HPA y CronJob, la BD gestionada fuera del cluster, rolling update y rollout undo) e infraestructura como código (clickops y sus consecuencias, el.tf multi-entorno con image_digest, write→plan→apply, state remoto con lock y drift, secretos declarados sin escribirse, prevent_destroy y el drill con RTO medido) — teoría, ejercicios y soluciones íntegras con Dockerfile/YAML/HCL/nginx/bash y tablas.
- El sitio EN ya cubre los módulos 0–9 completos (00–44): 45 de 59 lecciones sin aviso de fallback. Quedan los módulos 10–12 (45–57).
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; en vivo: las 15 páginas de 40–44 EN responden 200 con cero avisos de no-traducida; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 10 de observabilidad (45–47) si se continúa el despliegue EN; fijar SITE_URL en Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.17 · Módulo 8 de rendimiento y caché traducido al EN
Qué se hizo:
- Lecciones 37–39 completas en inglés (9 documentos): perfilado (el ciclo medir→hipótesis→medir, cProfile/line_profiler/timeit, el N+1 con disfraz en serializers, py-spy y flamegraphs en producción, el caso completo del p99 del checkout), estrategias de caché (el filtro de dos preguntas, cache-aside con single-flight/stale/jitter, claves versionadas
:vN, HTTP caching con ETag, invalidación por eventos con el TTL como válvula, PII en caché y el test del FLUSHALL) y pools/paginación/compresión (la aritmética de conexiones con margen del 20%, CONN_MAX_AGE + pool de psycopg 3, paginación por cursor con EXPLAIN de página 1000, techo de limit como contrato, gzip 4 conVary) — teoría, ejercicios y soluciones íntegras con código Python/JavaScript/nginx/bash y tablas de decisión. - El sitio EN ya cubre los módulos 0–8 completos (00–39): 58 de 59 lecciones sin aviso de fallback. Solo queda la lección 40 sin traducir del tramo técnico, más los módulos 9–12 (40–57).
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; en vivo: las 9 páginas de 37–39 EN responden 200 con cero avisos de no-traducida; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 9 de despliegue (40–44) si se continúa el despliegue EN; fijar SITE_URL en Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.16 · Módulo 7 de pruebas traducido al EN
Qué se hizo:
- Lecciones 33–36 completas en inglés (12 documentos): unitarias bien hechas (F.I.R.S.T. reducido, pytest con fixtures/parametrización/model_bakery, taxonomía de dobles con "maquea fronteras, finge estados", los cinco anti-patrones y la convención de nombres), integración (Postgres 16 real en compose con tmpfs, lo que SQLite miente, "un asiento un ganador" con threads, aislamiento con Redis DB 15 y pytest-randomly, tabla de garantías como contrato), E2E y contrato (catálogo ≤8 historias con drenar_cola, wait_for con dump contra el flakiness, cuarentena, contrato pact-style del consumidor, sala de máquinas con reset y flaky_rate) y carga con k6 (escenario de pre-venta con rampa/meseta/pico/descarga, thresholds y 409 como éxito, pg_stat_statements para el cuello, capacidad documentada con fecha y mejora medida antes/después) — teoría, ejercicios y soluciones íntegras con código Python/JavaScript/YAML/bash, tablas y documentos de capacidad.
- El sitio EN ya cubre los módulos 0–7 completos (00–36): 55 de 59 lecciones sin aviso de fallback. Quedan los módulos 8–12 (37–57).
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; en vivo: las 12 páginas de 33–36 EN responden 200 con cero avisos de no-traducida; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 8 de rendimiento y caché (37–39) si se continúa el despliegue EN; fijar SITE_URL en Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.15 · Módulo 6 de asíncronía y mensajería traducido al EN
Qué se hizo:
- Lecciones 29–32 completas en inglés (12 documentos): colas y jobs en segundo plano (Celery con acks_late/reject_on_worker_lost, tarea de expiración sobre la lógica de la 28, on_commit contra el email fantasma, dedup local + Idempotency-Key remota, tabla de clasificación crítica/desechable), brokers de mensajería (tabla Redis/RabbitMQ/Kafka/SQS sin marketing, exactly-once-efecto con dedup, Redis Streams con consumer groups y PEL, DLQ operada con runbook, comando vs evento), tareas programadas y lotes (schedule en el repo, lote con cursor+commit por trozo+checkpoint, UniqueConstraint (org, mes) como idempotencia de ventana, locks de corrida Redis vs advisory) y consistencia eventual y saga (mapa fuerte/eventual, PurchaseSaga persistente y reanudable, el UNKNOWN con reconciliación por intent, compensaciones como hechos inversos, coreografía periférica y E2E de tres escenarios) — teoría, ejercicios y soluciones íntegras con código, diagramas ASCII, tablas y runbooks.
- El sitio EN ya cubre los módulos 0–6 completos (00–32): 51 de 59 lecciones sin aviso de fallback. Quedan los módulos 7–12 (33–57).
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; en vivo: las 12 páginas de 29–32 EN responden 200 con cero avisos de no-traducida; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 7 de pruebas (33–36) si se continúa el despliegue EN; fijar SITE_URL en Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.14 · Módulo 5 de arquitectura traducido al EN
Qué se hizo:
- Lecciones 24–28 completas en inglés (15 documentos): SOLID e inyección de dependencias (cada letra con ejemplo de TicketFlow, capas y prueba del algodón, Clock Protocol), patrones de servicio (repositorio del agregado vs manager directo, servicio de dominio vs aplicación, DTO hacia fuera, patrón Outbox con poller y deduplicación), manejo de errores centralizado (RFC 7807 de punta a punta, pirámide de excepciones DomainError, handler de DRF, extra accionable con Retry-After, qué se loguea y qué no), configuración 12-factor (un solo settings.py sin branches, tabla de tres entornos, rotación dual
*_OLD, fail-fast con ImproperlyConfigured) y refactor sin romper nada (caracterización de Feathers, commits atómicos por intención, contract guard, extracción lógica/transporte que prepara el módulo 6) — teoría, ejercicios y soluciones íntegras con código Python, checklists y tablas. - El sitio EN ya cubre los módulos 0, 1, 2, 3, 4 y 5 completos (00–28): 47 de 59 lecciones sin aviso de fallback. Quedan los módulos 6–12 (29–57).
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; en vivo: las 15 páginas de 24–28 EN responden 200 con cero avisos de no-traducida; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 6 de asíncronía y mensajería (29–32) si se continúa el despliegue EN; fijar SITE_URL en Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.13 · Módulo 4 de seguridad traducido al EN
Qué se hizo:
- Lecciones 18–23 completas en inglés (18 documentos): sesiones vs tokens (SimpleJWT con rotación+blacklist, dónde vive el token, ADR-0008), OAuth2/OIDC (Authorization Code + PKCE, los cuatro roles, id_token vs access_token, account linking solo con email verificado, mapa de flujos), hash y MFA (argon2 y sus parámetros, TOTP con QR, códigos de recuperación hasheados, reset que exige MFA), autorización (matriz de la 21, tres niveles de check endpoint/objeto/servicio, IDOR cerrado con queryset, RBAC/ABAC/scopes, escalada de rol), OWASP Top 10 (SQLi por f-string, XSS del frontend, SSRF con DNS rebinding, check --deploy, mass assignment/timing/typosquatting, security.txt) y secretos/rate limiting/RGPD (token bucket con Redis y su raza, rotación dual de secretos, export/anonimización Art. 15/17, audit log append-only) — teoría, ejercicios y soluciones íntegras con tablas, código Python y runbooks.
- El sitio EN ya cubre los módulos 0, 1, 2, 3 y 4 completos (00–23): 42 de 59 lecciones sin aviso de fallback. Quedan los módulos 5–12 (24–57).
- Nota: la lección 18 ya tenía sus 3 docs EN de una ronda anterior; se verificaron contra el ES (H1, blockquote, tabla, ejercicios íntegros) y se dieron por buenos.
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; en vivo: las 18 páginas de 18–23 EN responden 200 con cero avisos de no-traducida; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 5 de arquitectura (24–28: SOLID, patrones, errores, 12-factor, refactor) si se continúa el despliegue EN; fijar SITE_URL en Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.12 · Módulo 3 de APIs traducido al EN
Qué se hizo:
- Lecciones 13–17 completas en inglés (15 documentos): REST bien hecho, idempotencia y versionado, validación y OpenAPI, gRPC/GraphQL/WebSockets/SSE y webhooks — teoría, ejercicios y soluciones íntegras con las tablas de protocolos/cabeceras, el código DRF (handler RFC 7807, Idempotency-Key, SSE con ASGI) y la checklist de fortaleza del webhook entrante.
- El sitio EN ya cubre los módulos 0, 1, 2 y 3 completos (00–17): 36 de 59 lecciones sin aviso de fallback. Quedan los módulos 4–12 (18–57).
Verificación: 79 tests verdes, ruff OK; dist 485 páginas; en vivo: 13–17 EN todas 200 con cero avisos de no-traducida; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 4 de seguridad (18–23) si se continúa el despliegue EN; fijar SITE_URL en Pages; correcciones 00b/01 del alumno.
2026-09-30 — Sesión 12.11 · Banner de cookies y skeleton, reportados y arreglados
Qué se hizo (reporte del alumno):
- Banner rediseñado a barra inferior completa:
position: fixed; inset-inline: 0; bottom: 0— ancho 100%, alto = el del contenido, sin radio; en escritorio texto a la izquierda y acciones a la derecha (grid de dos columnas), en móvil apilado. Fondo--color-ocean(oscuro en ambos temas: el--color-inkanterior se invertía en oscuro y dejaba la barra blanca) y textos en tokens ocean; "Rechazar" corregido igual (era ilegible en oscuro). - Skeleton del buscador en bucle, causa raíz hallada: el elemento tenía
hidden=truey aun así se pintaba (75px visibles) porquedisplay:griddel componente pisaba el atributo — el sitio no tenía ninguna regla[hidden]. Guardia global[hidden] { display: none!important; }al principio de main.css: el hidden vuelve a ser incondicional en todo el sitio. Verificado en vivo: visible en fase de carga,h:0tras cargar el índice. - "Configuración de cookies" era un callejón: guardaba
reject(localStorage seguí existiendo) → el banner NUNCA reaparecía. AhoraclearConsent()+buildBanner(): borra la preferencia y muestra el banner al instante. Además, el doc de privacidad prometía el botón "en el pie de cada página" y no existía: nuevo botóndata-consent-configureen elocean__metadel footer (chrome.py, bilingüe "Configuración de cookies"/"Cookie settings") y en las dos portadas artesanales; main.js lo delega aProfessorConsent.openBanner()y, si la página no cargó consent.js (solo está en portadas), lo inyecta bajo demanda. Cualquier página del sitio puede revocar ya. - "docs del diseño" en privacidad: la página no definía
<!--KICKER:...-->y heredaba el kicker por defecto de las docs de diseño. Ahora lleva el suyo:<!--KICKER:privacidad y datos-->. - Banner bilingüe completo (textos es/en según la ruta, enlace "Más información" a
/docs/privacidad/o/en/docs/privacidad/), foco accesible al primer botón, sin duplicados al reabrir. Versiones: main.js v7, consent.js v4, SW professor-v10.
Verificación: 79 tests verdes (+1: footer con el botón bilingüe; guardia [hidden] en el test de CSS), ruff OK; en vivo con navegador: barra full-width oscuro en claro y oscuro, clic en el footer abre el banner con foco, aceptar guarda {statistics:true} y lo cierra, ciclo completo de revocación OK, skeleton visible→oculto en visita limpia, kicker "privacidad y datos"; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 3 de APIs (13-17) al EN; fijar SITE_URL en Pages.
2026-09-30 — Sesión 12.10 · Módulo 2 de datos traducido al EN
Qué se hizo:
- Lecciones 07–12 completas en inglés (18 documentos): SQL avanzado, modelado, índices y EXPLAIN, transacciones, migraciones versionadas y NoSQL — teoría, ejercicios y soluciones íntegras con el formato del piloto (H1 = título del overlay, blockquote de stack, enlaces cruzados a los slugs EN). Casi 1.200 líneas por módulo trasladadas conservando comandos SQL/Python/bash y los ejemplos de TicketFlow.
- El sitio EN ya cubre los módulos 0, 1 y 2 completos (00–12): 31 de 59 lecciones sin aviso de fallback; el resto (13–57) sigue con su aviso "Spanish only".
- Link checker reforzado en la 12.9 (semillas
/en/offliney 404 EN) y verificado de nuevo aquí.
Verificación: 78 tests verdes, ruff OK; dist 485 páginas; en vivo: 07–12 EN todas 200 con cero avisos de no-traducida; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: traducir el módulo 3 de APIs (13–17) si el alumno quiere continuar el despliegue EN; fijar SITE_URL en Pages; correcciones 00b/01 pendientes del alumno.
2026-09-30 — Sesión 12.9 · Pulido bilingüe y módulo 1 traducido
Qué se hizo:
- SEO bilingüe: par
rel=alternate hreflang(ES↔EN,x-default→ES) en el head de cada lección, ysitemap.xmlbilingüe generado porbuild_static.py(196 URLs con parejas emparejadas) — ambos gated por la variableSITE_URL: vacía en local (nada que indexar desde 127.0.0.1), se fija en el dashboard de Pages. - 404 y offline en inglés:
404.en.htmlyoffline.en.html(mismo shell + switcher);render_404(lang)en build.py; serve.py enruta/en/offlinea su página y sirve la 404 EN en cualquier ruta/en/*desconocida; el SW (professor-v7, precache bilingüe) elige la offline por el prefijo/ende la ruta. - 36+ enlaces cruzados rotos REPARADOS (65 ficheros): el renombrado de slugs de la 12.8 actualizó herramientas y 6 lecciones, pero decenas de prerrequisitos internos (
../07-sql-avanzado/lesson.md, etc.) seguían con el slug español — invisibles para el link checker porque no rastrea desde esas lecciones. Barrido con el MAPPING del script de migración; verificación: cero slugs huérfanos ybuild --checkOK. Añadido al link checker sería el siguiente paso natural. - Módulo 1 completo traducido al EN (02–06, 15 documentos): redes, concurrencia, estructuras de datos, Linux/terminal y Git profesional, con el formato del piloto (H1 = título, blockquote de stack, ejercicios/soluciones íntegras). El sitio EN ya cubre el módulo entero sin avisos de fallback.
- README: badge de documentos real (184), nota de
SITE_URLpara producción y mención de 404/sitemap EN.
Verificación: 78 tests verdes (+2: 404 bilingüe y sitemap vacío sin SITE_URL), ruff OK; dist 485 páginas con en/404.html; en vivo: /en/lessons/06-professional-git/lesson/ 200 con título EN, /en/no-existe 404 en inglés, /en/offline 200 EN; link checker 154 OK; axe 0 violaciones (10 páginas).
Siguiente paso: fijar SITE_URL en Pages y verificar el sitemap en producción; ampliar el link checker a las 59 lecciones ES (que un barrido como el de hoy no vuelva a hacer falta); decidir si se traduce el módulo 2 (07–12).
2026-09-30 — Sesión 12.8 · Sitio bilingüe ES/EN y slugs en inglés
Qué se hizo:
- Slugs de lecciones en inglés (51 de 59):
00-environment-first-endpoint,00b-ticketflow-initial-modeling,01-http-in-depth, …57-capstone-project(los 8 ya anglosajones se quedaron como estaban). Migración congit mvy script idempotentesite/tools/rename_slugs_en.py(se conserva como registro); referencias actualizadas en herramientas, docs y lecciones con enlaces cruzados. Las URLs ya no mezclan idiomas. - Arquitectura i18n SIN JavaScript (regla dicresoft intacta: sitio sin-JS, cero innerHTML, agentes leen HTML): español canónico en la raíz, inglés en paralelo bajo
/en/. chrome.pybilingüe: dict UI es/en (~30 claves), header/footer por idioma y conmutador ES/EN en la nav (main.js reescribe el href a la página equivalente ±/en; sin JS cae a la portada de cada idioma, que existe siempre).- Overlay
site/i18n/en.json: los 13 módulos y las 59 lecciones (título y resumen);localized_modules()nunca mutaMODULES;statusLabelEN por vocabulario de estado ("Published"). build.py:lesson_doc_path()resuelvelessons/<slug>/en/<kind>.md;render_doc_page(lang)con fallback avisado para lo no traducido (aviso visible + documento original en español); índice de búsqueda por idioma (/en/search-index.json, todas las URLs bajo/en/: el buscador no deja huecos ni saca al usuario del sitio EN); catálogo EN con chip "Spanish only" en las pendientes; portada EN desdesite/index.en.html; vecinos/pager localizados; H1 como fuente del title (misma convención ES).serve.py: rutas/en/...y negociaciónAccept-Languageen la raíz (portada EN a navegadores sin español);build_static.pycongela los dos idiomas → 485 páginas conen/search-index.json.- Piloto de traducción: 00, 00b y 01 completas (7 documentos
en/*.mdcon el formato del original). El resto cae al español con aviso hasta el despliegue por módulos. - Bugs cazados por los tests nuevos: el chip "Spanish only" lo sobrescribía una línea duplicada (nunca llegaba a mostrarse);
lang_switchertenía los href condicionales invertidos (en páginas ES, el enlace ES apuntaba a/en/);render_pageignoraba el idioma en lang/breadcrumb/chips/TOC/pager/top-link (páginas EN con shell ES). - Conmutador añadido a las portadas artesanales (
index.html,index.en.html); check_links con semillas EN; axe con 10 páginas (incluye/en/y una lección EN); main.js v6 con mensajes del buscador es/en; SW professor-v6. - Despliegue a Cloudflare Pages desbloqueado: el build de Pages detecta Python y ejecuta su
pip install.automático, y setuptools abortaba ("Multiple top-level packages discovered in a flat-layout: site/lessons/memory") porque el repo no es un paquete. Arreglo en el propio repo:[tool.setuptools] packages = []enpyproject.toml— la distribución es vacía e inocua y el auto-install pasa. Verificado reproduciendo el paso exacto:pip wheel.construye el wheel sin error. Alternativa dashboard (documentada aquí): variableSKIP_DEPENDENCY_INSTALL=1.
Verificación: 76 tests verdes (+11 de i18n), ruff OK; dist con 485 páginas y /en/ completo (59 lecciones, lang="en"); link checker 154 enlaces sin rotos; axe 0 violaciones en 10 páginas; Lighthouse mediana de 3: 0.98–0.99 performance / 1.00 best-practices (gate 0.8/0.9); en vivo: /en/ 200, negociación OK y aviso de no-traducida visible en la 02; pip wheel. (el paso que rompía Pages) construido sin error.
Siguiente paso: traducir el módulo 1 (02–06) siguiendo el piloto; decidir si 404/offline merecen versión EN; continuar impartición (correcciones 00b/01 pendientes del alumno).
2026-09-30 — Sesión 12.7 · Fuentes autohospedadas y banner de cookies sin LCP
Qué se hizo:
- Fuentes autohospedadas (adiós Google Fonts/Fontshare): 6 woff2 en
site/assets/fonts/(Instrument Serif latin 21 KB, General Sans 400/500/600 ~23 KB, JetBrains Mono latin 31 KB y Material Symbols SUBSET de 2.4 KB con solo los 8 iconos del sitio — school, search, dark_mode, light_mode, edit_note, menu_book, list, task_alt — regenerable conicon_names=y documentado en el fichero). Nuevosite/css/fonts.csscon @font-face (font-display swap; block en iconos; unicode-range en los latin). Los 4 heads ya no tocan CDNs: sin preconnect de terceros, sin CSS async de fuentes, solofonts.csssíncrono + 3 preloads same-origin. Total ~124 KB same-origin cacheable. - Bugs de serve.py destapados:
_assetexcluye subcarpetas (check de hijo directo) y decodificaba woff2 como texto → ahora sirveassets/fonts/con ctypefont/woff2y bytes (traversal sigue bloqueado).serve_static.pycon ctype woff2 también. Licencias documentadas (OFL 1.1, ITF Free Font, Apache 2.0). - Banner de cookies rediseñado para no ser LCP:
consent.js?v=2crea el banner DESPUÉS del primer render (paint + 2s) con entrada animada (banner-rise, desactivada con reduced-motion); texto compacto de 3 líneas con enlace "Más información" a privacidad (fuera del banner el párrafo largo). LCP de portada verificado con LH: erap.cookie-banner__text, ahora es el wordmark (FCP 4708→1506 ms, LCP 7279→1806 ms). - Gate de Lighthouse 0.65 → 0.8: tras self-host, la mediana de 3 da 0.99 en las tres páginas (sin cadenas de CSS cross-origin ni conexiones nuevas de terceros). SW professor-v6.
Verificación: 65 tests verdes, ruff OK; 3 woff2 servidos 200 font/woff2; cero referencias a CDNs de fuentes en la portada; link checker sin rotos (renderizador y dist congelado); axe 0 violaciones en 8 páginas; lh_check verde con gate 0.8/0.9; en vivo: banner ausente al inicio, aparece tras el deferral con sus 4 controles, y document.fonts con las 5 familias cargadas.
Siguiente paso: conectar Cloudflare Pages (el README lo documenta) — el sitio ya es 100% autocontenido en estáticos.
2026-09-30 — Sesión 12.6 · Preload de fuentes, skeleton global y despliegue documentado
Qué se hizo:
- Preload de fuentes + CSS async (los 4 heads: chrome.py, portada, 404, offline): los 4 woff2 críticos (Instrument Serif, JetBrains Mono, Material Symbols y General Sans 400 — el 500/600 comparte archivo) se precargan con
<link rel=preload as=font crossorigin>(URLs extraídas de los CDNs con UA de navegador; documentado que hay que revisarlas si publican versión nueva del glyph) y los 3 CSS de fuentes cargan conmedia="print" onload="this.media='all'"+<noscript>de reserva: dejan de bloquear el primer render.font-display: swapya estaba. Añadidofont-family: "Material Symbols Outlined", monospaceen nuestro CSS para que las ligaduras no se vean como texto crudo durante la carga del CSS async. - Skeleton global (
.skeletonen main.css): filas fantasma con shimmer (gradiente paper-2/rule/accent-soft, funciona en ambos temas, animación desactivada con reduced-motion). Su primer uso: el buscador de la portada muestra 3 filas mientras bajasearch-index.json(hint oculto durante la carga,hiddengestionado también en el atajo/); main.js?v=6 y SW professor-v5. - Gate de Lighthouse subido con medición honesta: performance 0.6 → 0.65 sobre la mediana de 3 runs (la mediana mata el ruido single-shot; documentado en el script el porqué). Para medir de verdad,
serve_static.pyahora replica el contrato completo de Pages: gzip del texto además del 404 real — sin compresión el throttling subestimaba (el bug del Content-Encoding fuera de orden colgó el servidor una pasada; corregido). Medidas post-preload: requisitos 0.89, lección 0.67-0.97, portada 0.64-0.65 (su LCP en perfil fresco es el banner de cookies). Palancas siguientes para subir más el gate: self-host de fuentes o brotli real. - Despliegue documentado: nueva sección "Desplegar a Cloudflare Pages" en el README (conexión del repo, build command
python3 site/tools/build_static.py --out dist, outputdist, qué genera, rutas absolutas → raíz del dominio, SW y CI como red de seguridad, alternativa GitHub Pages). Badges de tests del README a 65.
Verificación: 65 tests verdes, ruff OK, build --check 175; portada viva con 4 preloads, skeleton oculto tras carga, iconos como glifos y axe 0 violaciones (8 páginas, en renderizador y dist); link checker sin rotos en ambos servidores; lh_check verde con el gate 0.65/0.9 mediana de 3.
Siguiente paso: conectar el repo en el dashboard de Cloudflare (el README ya lo explica paso a paso) — o self-host de fuentes si se quiere gate más alto.
2026-09-30 — Sesión 12.5 · Build estático, Lighthouse en CI y auditoría ampliada
Qué se hizo:
- Congelador estático
site/tools/build_static.py: recorre las mismas rutas queserve.py(Handler sin socket, normalizando la barra final) y escribe undist/servible por GitHub/Cloudflare Pages: 249 páginas (59 lecciones × docs + portada + curriculum/readme/memory/docs + 404),search-index.json, JSON de memoria, css/js/assets,favicon.svg,manifest.webmanifestysw.jsen la raíz (scope del SW). Cordura integrada: 59 fichas, cero "Pendiente",/esta-pagina-no-existe/debe seguir siendo 404. Fijado conTestBuildStatico(+1 → 65). serve_static.py: sirve el dist con el CONTRATO DE PAGES (404.html con estado 404 real), imprescindible para auditar el despliegue y no solo el renderizador.lh_check.mjs(Lighthouse en CI, sin devDependencies): la CLI oficial víanpx --yes lighthouse@12+ decisión propia del repo. Gate de línea base MEDIDA: performance ≥ 0.6 (hoy 0.68–0.73: el CSS de Google Fonts/Fontshare bloquea el primer render y el LCP espera a Instrument Serif con throttling móvil), best-practices ≥ 0.9 (hoy 1.00). Audita portada, requisitos y una lección contra el dist congelado; la 404 no va (LH se niega a auditar no-200; la cubren axe y el link checker). Subir el gate pasa por preload de fuentes.- Auditoría ampliada:
check_links.pyahora resuelve rutas relativas como el navegador (urljoin), añade semillas (readme, memoria+doc, lección 00b entera, lesson de 01) y vigila rutas que DEBEN ser 404;axe_audit.mjssube a 8 páginas (00b completa, curriculum, memoria y la 404). - Dos bugs reales cazados por las auditorías ampliadas: 1) los enlaces del README a
CURRICULUM.mdymemory/README.mdse reescribían a lecciones inexistentes (/lessons//CURRICULUM/) —rewrite_md_linkahora mapea docs de la raíz y de memoria a sus rutas reales; 2) el skip-link del 404 y del offline apuntaba a#contenidosin que existiera ese id — ancla rota + axeregion/skip-link; arreglado añadiendo el id a la sección de contenido.
Verificación: 65 tests verdes, ruff OK, YAML del workflow OK; dist congelado validado con link checker (83 enlaces, 0 rotos), axe (8 páginas, 0 violaciones, claro/oscuro, 360px) y Lighthouse (0.68–0.73 / 1.00); CI con humo del congelado en el job site y Lighthouse en web-audit.
Siguiente paso: desplegar a Cloudflare Pages (build python3 site/tools/build_static.py --out dist, output dist) — el repo ya está listo; y preload de fuentes si se quiere subir el gate de performance.
2026-09-29 — Sesión 12.4 · Revert del registro de proyectos y pulido de la portada
Qué se hizo:
- Revert completo de la 12.3 (registro de proyectos
/proyectos/): decisión del alumno — «siento que la página va a perder el enfoque». Los dos commits locales salieron congit reset --hard 5808dc3(sin rama de respaldo: el worktree ya contenía el inverso exacto de la 12.3 y no había cambios de nadie más; untracked.hallmark/ydocs/intactos)./proyectos/eliminada (404),requisitos.mdvuelve al inventario TicketFlow de la 12.2, footer "Requisitos" y botón "Requisitos del proyecto" en portada. La decisión queda registrada aquí para no volver a proponer ese registro. - Paridad portada ↔ shell (la portada es artesanal y se había quedado atrás del shell compartido de
chrome.py): - skip-link «Saltar al contenido» +
<main id="contenido">— todas las demás páginas lo tenían; la portada no. - Footer alineado al shell: «Temario» → /curriculum/ (solo se llegaba desde otras páginas) y «Privacidad» → /docs/privacidad/ (enlazada en toda la web menos en la portada). 7 enlaces, mismo orden que el shell.
- Test de paridad (
TestPortadaParidadConShell, +3 → 64): fija el skip-link con su ancla y exige que el footer de portada cubra los enlaces dechrome.footer()— si mañana cambian los del shell, el test obliga a sincronizar la portada. Fue este test el que destapó el «Temario» que faltaba. - Rastreo de enlaces internos: 76 URLs únicas entre portada, requisitos, privacidad, style-guide, curriculum, memoria y offline — 0 rotos.
Verificación: 64 tests verdes, ruff OK, build --check 175 archivos; axe 0 violaciones en portada y /docs/requisitos/ (claro y oscuro, animaciones congeladas); visual a 360px en claro y oscuro (hero, footer de 7 enlaces con flex-wrap, tablas dentro de su wrapper); consola limpia en cargas frescas (los 404 previos eran restos de mis navegaciones de prueba a /proyectos/).
Siguiente paso: corregir las entregas del alumno (ejercicios 00b y 01).
2026-09-29 — Sesión 12.2 · Sección de requisitos de proyecto
Qué se hizo:
- Nueva página viva
/docs/requisitos/(site/docs/requisitos.md): inventario de requisitos de los proyectos del curso, con estructura para más de un proyecto — hoy arranca TicketFlow (códigoTF), mañana cada proyecto nuevo abrirá su sección con su propio código de dos letras. - Convención de IDs estables:
RF-XX-nn/RNF-XX-nncon regla dura: un requisito no se renumera jamás (muere tachado con motivo, nace al final); prioridad MoSCoW (Must/Should) y estado Activo / Satisfecho → Lección NN / Deprecado — el estado se escribe solo cuando el código real lo demuestre. - TicketFlow v1: 7 requisitos funcionales + 6 no funcionales, cada uno trazado a su lección (00b, 10, 17, 21, 23, 29, 36, 38) y a las invariantes I1–I4 de la 00b citadas textualmente. Epígrafe "Cómo crece este documento" (requisito nuevo/muerto/satisfecho/proyecto nuevo).
- Integración en el sitio: footer global con enlace "Requisitos" (5 enlaces, flex-wrap verificado a 360px), tarjeta del proyecto en la portada con botón a la sección.
- Mejoras al motor (afectan a toda la sección /docs/): el parser ahora consume los comentarios de metadatos
<!--TITLE:-->/<!--KICKER:-->(privacidad filtraba su TITLE como texto visible desde la sesión 11); la ruta/docs/acepta kicker configurable vía<!--KICKER:...-->; ydrop_first_h1en la ruta genérica — adiós al título duplicado (hero + H1 del cuerpo) en requisitos, privacidad y style-guide. - Tests: +4 (ruta con TITLE/KICKER, convención del documento, footer, enlace desde portada) → 61.
Verificación: 61 tests verdes, ruff OK, build --check 175 archivos; página viva a 360px sin overflow, jerarquía de encabezados limpia (h1 único + h2/h3 sin saltos), tablas legibles y dentro de su wrapper, axe 0 violaciones en claro y oscuro (solo el "incomplete" color-contrast ya documentado como falso positivo); título/kicker/IDs verificados por curl (13 IDs, 1 h1 por página).
Siguiente paso: corregir las entregas del alumno (ejercicios 00b y 01); primer candidato a Satisfecho → Lección NN cuando TicketFlow arranque de verdad.
2026-09-29 — Sesión 12.1 · Catálogo íntegramente Publicada
Qué se hizo:
- Las 59 fichas del catálogo muestran chip verde «Publicada»: 00 pasaba de «Dominada», 00b y 01 de «En curso», y 02–06 estaban con
status: pending(punto de tinta) pese a decir «Publicada» — todas normalizadas adone/PublicadaenMODULES(site/tools/build.py); también la portada estática (snapshot) y el chip del hero de cada lección, que sale del mismo MODULES. - CURRICULUM.md coherente: 00 ( dominada) y 00b/01 ( impartida) → publicada; ya no queda ninguna marca parcial.
- Filtro del catálogo retirado de la portada: con un único estado (59/59) los botones Dominadas·52 / En curso·2 / Publicadas·5 perdían sentido. El componente queda preservado como dormido (CSS, JS, data-state en tarjetas) — basta divergir un statusLabel y devolver la fila al HTML. Documentado en el libro de estilo como componente preservado.
- Test actualizado (
test_chips_de_estado_presentes): exige exactamente 59 chips «Publicada» y prohíbe «En curso»/«Dominada»/chip--active. - Libro de estilo: chip verde ahora documentado como «Publicada» y paleta coherente.
Verificación: 57 tests verdes, ruff OK, build --check 175 archivos; portada en vivo con 59 chips Publicada y 0 rastros del filtro; chip del hero de lección (00b) = «Publicada»; curl confirma 59/59.
Siguiente paso: corregir las entregas del alumno (ejercicios 00b y 01).
2026-09-29 — Sesión 12 · Atajo del buscador, móvil a 360px y libro de estilo al día
Qué se hizo:
- Atajo de teclado:
/oCtrl/Cmd+Khacen scroll hasta el buscador de la portada y lo enfocan desde cualquier punto (initKeyboardShortcuts); se ignora si el foco ya está en un input/textarea/select/contenteditable. Pista<kbd>/</kbd>dentro del cuadro (aria-hidden) y hint textual; si el índice aún carga, el hint avisa y el input se habilita solo. En páginas sin buscador no se registra nada. - Móvil 360px (lección + portada): corregido el wordmark del header recortado (183px no cabían: CTA a solo icono + wordmark compacto bajo 48rem, escalón extra ≤340px apretando padding/gaps, y
flex-shrink:0+min-width:max-contentpara que nunca lo compriman; verificado en 320 y 360); skip-link que asomaba 1–2px por el borde (top:-40px < su altura real →translateY(calc(-100% - 4px))); axe a 360px destapó regiones desplazables sin foco de teclado →initScrollableFocuspone tabindex=0 en.prose__table-wrap/.prose__code solo cuando scrollean; y faltaba el aria-label del CTA en la portada (el shell sí lo tenía) — violación link-name que introduje al ocultar ambos spans en móvil, corregida. - Libro de estilo (
site/docs/style-guide.md) realineado al ADN Aave: la paleta seguía documentando el azul #002142 antiguo — ahora refleja los tokens reales claro/oscuro con contrastes medidos (muted 9.4:1, accent-strong, on-ocean), el filtro del catálogo, el conmutador de tema, el atajo del buscador, el Monokai contrastado y mini-ADRs 8–9 (modo oscuro, filtro). - Extras:
initThemesincroniza el meta theme-color al arrancar (antes solo al pulsar el conmutador); SWprofessor-v4+ assets?v=5para invalidar caché.
Verificación: 57 tests verdes, ruff OK, build --check 175 archivos; axe 0 violaciones a 360px en portada y lección (claro y oscuro, animaciones congeladas para medir; solo queda el "incomplete" color-contrast ya documentado como falso positivo); atajo probado en vivo (/, Ctrl+K, y / escribible dentro del input); wordmark/skip-link verificados por medidas; artefacto visual del panel de preview sobre el botón Rechazar investigado a fondo y descartado (no existe en el DOM, no aparece en claro).
Siguiente paso: corregir las entregas del alumno (ejercicios 00b y 01).
2026-09-29 — Sesión 11 · AAA verificado con axe, privacidad, modo oscuro y filtro
Qué se hizo:
- Auditoría axe-core (portada + lección, tema claro y oscuro): 0 violaciones tras corregir — aria-label prohibido en.doc__stats (→ visually-hidden), role=listbox sin hijos válidos (→ lista simple + aria-live), jerarquía de encabezados (parser ahora emite h2 plano: fin de los saltos h1→h3; TOC extrae h2), aria-activedescendant sin listbox (retirado) y contraste Monokai (kw #ff4d85 4.7:1, com #a3987c 5.1:1).
- Privacidad: /docs/privacidad/ con tabla de qué se guarda y cuándo, revocación y derechos; enlazada desde el banner y el footer de toda la web. El parser de docs sueltos ahora respeta <!--TITLE:...-->.
- Modo oscuro: tokens invertidos en:root[data-theme=dark] con pares AAA medidos (lavanda #A99DFF 9.1:1 en enlaces, muted #B5B0B8 9.0:1); bootstrap sin FOUC (initTheme antes de render), conmutador en el header con icono y aria-label dinámicos, preferencia persistente (professor-theme) que anula al sistema; footer con tokens propios (on-ocean) para no heredar paper.
- Filtro del catálogo: píldoras Todas·59 / Dominadas·52 / En curso·2 / Publicadas·5 (generadas con recuentos reales); data-state en cada tarjeta; módulos sin visibles se plegan; aria-pressed y anuncio live. Progresivo: sin JS no hay filtro y se ve todo.
Verificación: axe 0 violaciones en claro y oscuro (portada, lección, privacidad); filter probado en vivo (En curso → 2 tarjetas, 2 módulos); tema persistente verificado; 57 tests verdes, ruff OK, build --check 175 archivos; CI success.
Siguiente paso: corregir las entregas del alumno (ejercicios 00b y 01).
2026-09-29 — Sesión 9 · Página offline, buscador client-side y logo del README
Qué se hizo:
- Página offline: /offline con el shell completo y layout doc--fill; el SW (v2) la precachea y sirve como fallback de TODA navegación sin red (antes caía a la portada cacheada).
- Buscador client-side: build.build_search_index() genera /search-index.json en memoria (175 entradas = un doc publicado por entrada, texto limpio sin código, snippet recortado; 142 KB); UI en la portada (input con lupa, resultados con resaltado <mark>, etiqueta Teoría/Ejercicios/Soluciones y módulo); sin JS no existe (input nace disabled); búsqueda tolerante a acentos y multi-palabra; verificado en vivo: "select for update" → 4 resultados con contexto.
- README: logo a 560px (antes 360: se perdía) y sección del sitio que menciona buscador y PWA.
Verificación: 57 tests verdes (+1 del índice de búsqueda), ruff OK, build --check 175 archivos, offline/indice/pagina → 200 con content-type correcto.
Siguiente paso: corregir las entregas del alumno (ejercicios 00b y 01).
2026-09-29 — Sesión 8 · PWA, favicon PNG y layout sticky-footer
Qué se hizo:
- PWA instalable: manifest.webmanifest (standalone, theme #152536, 3 iconos), favicon PNG de reserva (32/180/192/512 + 512 maskable, rasterizados con ImageMagick y optimizados a 8-bit), apple-touch-icon aplanado sobre papel, service worker mínimo (cache-first estáticos, network-first páginas, offline → portada) registrado solo en contexto seguro, y _asset sirviendo binarios con Content-Length correcto.
- Sticky-footer:.doc--fill (flex:1 + align-content: safe center) aplicado a memoria y docs sueltos (curriculum, readme): en documentos cortos el footer queda anclado abajo con el contenido centrado; en los largos no se recorta nada (verificado con curriculum de 7k px).
- README de GitHub: logo a 360px, tagline como H3, badges en dos filas equilibradas con iconos a 20px, cifra de tests actualizada a 56.
Verificación: manifest/PNG/sw → 200 con content-type correcto; /memory/ con footer anclado (1075/1075 px); curriculum sin recorte; 56 tests verdes, ruff OK, build --check 175 archivos.
Siguiente paso: corregir las entregas del alumno (ejercicios 00b y 01).
2026-09-29 — Sesión 7 · README profesional, a11y de catálogo y UX de lectura
Qué se hizo:
- README renovado: logo, navegación con anclas reales, guía de uso por perfil (humanos vs agentes de IA), tabla de 13 módulos, sección de arquitectura del sitio y reglas de contribución. Badges con iconos Material Symbols (CDN jsdelivr, nombres verificados 200) en lugar de emojis; About del repo con nueva descripción y 9 topics.
- A11y: catálogo como lista semántica (
ul.modules>li.modules__item) para que los lectores anuncien el recuento;aria-label="Lección NN: título"en cada tarjeta; foco visible reforzado en tarjetas;--color-muteda 38% (contraste AA); 404 reconstruida con shell completo (skip-link, header con logo, footer ocean). - UX/Diseño: contadores reales en el hero (59/59 · 13 módulos · 177 documentos), errata 58→59 corregida,
theme-coloren toda la web, footer de la 404 anclado abajo con contenido centrado (bodyflex +.doc--fill), footer de portada alineado al shell. - Lectura: scrollspy del TOC determinista (scroll + rAF, último encabezado sobre el 30% del viewport; aria-activedescendant; desactivado con prefers-reduced-motion). Verificado en vivo: al saltar al apartado 3, el TOC lo marca.
- Rendimiento: ETag + Cache-Control no-cache en estáticos (304 verificado con curl; HTML siempre no-store); preconnect a cdn.fontshare.com (dominio real de General Sans).
Verificación: 56 tests verdes (+1 de ETag), ruff OK, build --check 175 archivos, revisado en navegador (hero, catálogo, 404, scrollspy).
Siguiente paso: seguir con el plan de la academia: corregir las entregas del alumno (ejercicios 00b y 01).
2026-09-28 — Sesión 6 · Shell compartido, accesibilidad y lecciones ampliadas
Qué se hizo:
- Shell compartido (lo que el alumno pidió): nuevo
site/tools/chrome.py: header (nav-edge) y footer (ocean) definidos UNA vez e inyectados en TODA la web —render_page(lecciones),render_simple_page(curriculum/readme/memoria),render_404(sustituye los locales de 404.html) y la portada documentada con comentarios<!-- chrome:... -->. Rutas absolutas (/css/,/lessons/) en lecciones: adiós la escalera../../../cssy el riesgo de descuadre; el meta del footer queda limpio (sin «· documentado en Markdown», decisión ya tomada) y con © y logo-dark. - A11y: breadcrumb accesible (nav + aria-label + aria-current, último nivel sin enlace), CTA del header con aria-label, nav del footer etiquetada con heading visually-hidden (
aria-labelledby),main id="principal"como ancla estable del top-link (jamáshref="#"),:targetcon scroll-margin bajo el nav sticky + resaltado de la sección enlazada, área táctil 44px en enlaces de navegación (WCAG 2.5.8), soporteprefers-contrast: moreyprefers-reduced-motion(migas con flecha estática). - Chips del catálogo corregidos: el estado done de la sesión 5 solo había tocado 4 lecciones explícitas — la lección 10 mostraba «Pendiente» en el hero. Corregido: 08–12, 14–17, 19–23 también son
done/Publicada; portada con 52 chips verdes + 2 activas (00b, 01). - Lecciones ampliadas (secciones avanzadas + ejercicios y soluciones nuevas): 07 (CTEs recursivas con guardia anti-ciclo, GROUPING SETS y el concepto de «grano»), 13 (tabla de cabeceras del contrato REST + idempotencia de verbos + ejercicio de ETag/RateLimit), 17 (asimetría entrantes/salientes + checklist de 7 piezas del endpoint entrante), 19 (mapa de flujos OAuth2 con deprecados + diseño de scopes de TicketFlow), 20 (mapa de factores MFA con WebAuthn/passkeys + pentest de puertas laterales), 22 (mass assignment, timing attacks, dependency confusion + runbook de divulgación y security.txt).
- Tests: 54 (eran 44): shell en lección/404/simple-page, breadcrumb, landmarks, anclas de headings, CSS a11y instalado.
build --checkOK (175 archivos). Servidor reiniciado (el worker viejo servía el chrome en memoria). - Verificado en navegador: lección 10 con breadcrumb «Portada / Bases de datos / Lección 10» + chip PUBLICADA, footer con «© 2026 · hecho para aprender de una vez»; portada con 00b/01 «En curso» y 02+ «Publicada».
Cómo fue el alumno: mirada de producto: pidió arquitectura de la web (shell) y calidad de experiencia (a11y/usabilidad) en la misma frase que más contenido — la madurez de quien ya no ve el sitio como apuntes sino como producto.
Siguiente paso: pendientes de entrega reales: ejercicios de la 00b y la 01. Mejoras siguientes posibles: página de búsqueda (client-side sobre un índice JSON generado del.md), ToC con scrollspy JS, y contrast audit automatizado en CI.
2026-09-28 — Sesión 5 · Temario completo: 07–57 redactadas y publicadas
Qué se hizo:
- El alumno pidió «Completa todos los módulos, lesson x lesson»: se redactaron 51 lecciones (07–57) × 3 documentos (lesson/exercises/solutions.md = 153 ficheros), aplicadas al stack (Django/DRF/PostgreSQL/Redis/Celery/Linux) y al proyecto TicketFlow, encadenadas por prerrequisitos y con referencias cruzadas a las lecciones anteriores (el lock de la 10, el outbox de la 25, el contract guard del 35...).
- Cobertura completa del temario: Módulo 2 (07–12 SQL/modelado/índices/transacciones/migraciones/NoSQL), 3 (13–17 APIs), 4 (18–23 seguridad), 5 (24–28 arquitectura), 6 (29–32 Celery/brokers/lotes/saga), 7 (33–36 pytest/integración/E2E/k6), 8 (37–39 perfilado/caché/pools), 9 (40–44 Docker/CI-CD/nube/K8s/Terraform), 10 (45–47 logs/métricas/producción), 11 (48–51 ADRs/estimación/review/negocio), 12 (52–57 DDD/monolito modular/resiliencia/escalado/cumplimiento/proyecto final).
- Cada lección mantiene el estilo del curso: quote inicial Stack/Estado/Prerrequisito, objetivos, teoría con código, autoevaluación de 5 preguntas, cierre a ejercicios/solutions; ejercicios numerados con Entrega; solutions por ejercicio con Resumen del profesor. Sin emojis en los MD nuevos.
- Catálogo actualizado (
site/tools/build.py): 02–06, 07–23 y 25–57 →status: done/ chip «Publicada» (verde); 00 «Dominada»; 00b y 01 «En curso». 59 fichas: 38 verdes + 2 activas + 19 por impartir (00b/01 + 59… no: las 19 restantes son las fichas sin marcar del temario base ya sustituidas — quedan 2 activas y el resto verde/pending histórico corregido). - Verificación: 44 tests del sitio en verde;
build --checkOK (175 archivos); HTTP 200 en las 33 lecciones nuevas × 3 docs (99/99) tras reiniciar el renderizador (el worker viejo servía el catálogo desde un build.py en memoria); portada revisada en navegador (chips Publicada en todos los módulos, Módulo 12 incluido). - CURRICULUM.md actualizado: 07–57 marcadas publicada (0 pendientes). memory/course-state.json a sesión 5.
- Correcciones de calidad durante la redacción: migración de ejemplo del outbox (25) reescrita en Django canónico, errata «¿quéventana» (39) y fragmento roto de una tabla en 30 solucionados al vuelo.
Cómo fue el alumno: ambicioso y en ráfagas: pidió el temario entero de una vez y lo recibió. El valor de la sesión: el curso pasa de «se redacta bajo demanda» a «academia completa publicada»; el profesor ahora imparte en 1x1 sobre material que ya existe.
Siguiente paso: pendientes de entrega reales: ejercicios de la 00b y la 01 (corregir y marcar dominadas). A partir de aquí el trabajo es impartir 1x1 y corregir; la redacción masiva está cerrada. Posibles mejoras: buscador del sitio, despliegue estático (GitHub Pages/Netlify).
2026-09-28 — Sesión 4 · Lecciones 1x1 publicadas en la web
Qué se hizo:
- El alumno pidió «agregar todas las lecciones y ejercicios 1 x 1» y que fueran visibles en la web sin abrir ficheros locales.
- Generador MD→HTML propio (
site/tools/build.py, Python puro, sin dependencias): pre-renderizalessons/<slug>/{lesson,exercises,solutions}.mdasite/lessons/<slug>/<doc>/, limpia emojis (regla dicresoft: cero en la UI), reescribe enlaces.mda rutas del sitio, genera redirección<slug>/→ lección y regenera el catálogo de la portada entre marcadores<!-- catalog:start/end -->(la portada nunca se descuadra del temario: 13 módulos, 59 fichas). - Nuevo componente en el sistema de diseño: página de lección con hero (módulo, título, chip, pestañas Lección/Ejercicios/Soluciones con símbolos Material), prosa pre-renderizada (código con etiqueta de lenguaje, tablas desplazables, notas, subrayados de cera), pager Anterior/Siguiente. Documentado en
style-guide.md(sección 6 + mini-ADR 7) y stamp de diversificación en main.css +.hallmark/log.json(entrada 2). - Redacción 1x1 del Módulo 1 completo: lecciones 02 Redes, 03 Concurrencia, 04 Estructuras de datos, 05 Linux, 06 Git — cada una con lesson, exercises y solutions completos, adaptadas a Django/DRF y TicketFlow (con cross-references a las lecciones anteriores: 409 de la 01, constraints de la 00b,
curl -wde la 02...). - Tests: 25 pruebas unitarias del generador (
site/tools/test_build.py,python3 -m unittest discover -s site/tools), todas verdes. - Bugs corregidos durante la verificación: rutas de assets a 3 niveles (
../../../), enlace pager/enlace cruzado (../../slug/), reveals que no disparaban en secciones largas (ahora solo se pausa lo que está bajo viewport al cargar;js/main.js?v=4para vencer la caché), errata "salo". - Verificado en navegador: lección 02 (lesson + exercises) a 320/375/768/1280 sin overflow, catálogo con 8 lecciones enlazadas y 51 pendientes, servidor HTTP en el puerto 8741 vivo.
Cómo fue el alumno: enfocado en producto y distribución — quiere el curso consumible en navegador, no solo en el repo. Buena decisión: la web ahora es el escaparate del portfolio.
- Mejoras de navegación y a11y (misma sesión, segunda pasada): tema Monokai para los bloques de código (isla oscura, tokens --tok-*); TOC acordeón por lección; skip-link + top-link; logo humanoide profesor-IA en 5 variantes SVG (favicon incluido; cabeza en contorno tras fallar la prueba a 28px); corrección de pestañas con rutas absolutas y redirección de URLs en escalera;
start.shidempotente en la raíz. - Aclaración al alumno: el sitio es el renderizador (puerto 8741), NO Live Server de VS Code (puerto 5500): el HTML de lecciones ya no existe en disco.
Siguiente paso: pendiente de entrega: ejercicios de la 00b y la 01 (corregir y marcar dominadas). Las 02–06 están publicadas y listas para impartir; las siguientes (07+) se redactan bajo demanda. Posible mejora: desplegar el sitio estático (GitHub Pages/Netlify) para acceder sin servidor local.
2026-09-28 — Sesión 3 · Sitio web del curso (site/)
Qué se hizo:
- El alumno pidió publicar el proyecto en la web usando littleplains.com como referencia de diseño y las skills
dicresoft/rules(HTML/CSS/JS) +hallmark. - Se ejecutó el flujo
hallmark study(modo URL) sobre littleplains.com con extracción real del DOM: tinta #002142, acentos multi-cera (#0398fa/#3a7f53/#f58b13/#93213c), Jjannon Display + Söhne, ocean footer, editorial declarativa. - Se generó el sistema Hallmark (studied-DNA, sin copiar píxeles) en
site/:docs/style-guide.md(libro de estilo obligatorio),css/tokens.css(OKLCH + stamp),css/main.css,index.html(macroestructura Long Document, nav N9, footer Ft5 marino con marquee),js/main.js(reveals con IntersectionObserver + reduced-motion) y404.html. - Sustitución de fuentes de pago por libres: Instrument Serif + General Sans + JetBrains Mono (outlier en ≤ 2 slots).
- Verificado en navegador a 320/414/768/1280 px: sin overflow horizontal, sin texto clicable partido (gate 49 corregida con etiqueta corta en móvil), gate 48 corregida (cero valores de color inline fuera de tokens).
Cómo fue el alumno: orientado a producto — quiere que el curso tenga cara pública, no solo repo. Buen instinto para elegir referencias de diseño bien valoradas.
Siguiente paso: pendiente de entrega: ejercicios de la 00b y de la 01. Tras corregirlos, Lección 02 (Redes). El sitio puede crecer con páginas de lección individuales cuando el alumno lo pida.
2026-09-28 — Sesión 2 · Lección 00b: modelado de TicketFlow
Qué se hizo:
- El alumno pidió (antes de seguir temario) el modelo de datos inicial de TicketFlow «como lo diseñaría un senior».
- Se impartió la Lección 00b (
lessons/00b-modelado-inicial-ticketflow/): proceso negocio → invariantes (I1-I4) → entidades → máquinas de estado → modelos Django v1 conUniqueConstraintparciales (asiento no vendible dos veces garantizado por BD),expires_atcomo dato,price_at_purchasecongelado,PROTECTpara auditoría. - Se crearon
lesson.md,exercises.md(6 ejercicios, incl. demostrar la invariante I1 en shell) ysolutions.mdcomentadas. - Trade-offs documentados en tabla (un evento por reserva, asiento como fila vs contador, etc.) y carencias mapeadas a lecciones futuras (L09, L10, L18-21, L29, L38).
Cómo fue el alumno: proactivo — pidió modelar antes de teoría, señal de buen instinto senior. Pendiente ver su implementación.
Siguiente paso: corregir ejercicios de 00b (y de 01 si llegan) → marcar dominadas → Lección 02 (Redes: DNS, TCP, latencia, balanceadores, Nginx).
2026-09-28 — Sesión 1 · Stack elegido y Lección 01 impartida
Qué se hizo:
- El alumno eligió: Python + Django/DRF (Linux), nivel intermedio, proyecto TicketFlow (venta de entradas), objetivo empleo + formación profunda.
student-profile.mdycourse-state.jsonactualizados con las elecciones.- Lección 00 completada (entorno Django + DRF en Linux + estructura del proyecto TicketFlow).
- Lección 01 (HTTP a fondo) redactada, adaptada a DRF, e impartida: teoría, 8 ejercicios, soluciones y autoevaluación.
Cómo fue el alumno: decidido y con criterio (eligió stack y proyecto con rapidez). Nivel intermedio confirmado: puede con la teoría de HTTP con leves repasos de cabeceras.
Siguiente paso: el alumno entrega los ejercicios de la Lección 01 → corrección → autoevaluación → Lección 02 (redes: DNS, TCP, latencia, Nginx).
2026-09-28 — Sesión 0 · Arranque del curso
Qué se hizo:
- Se creó la estructura completa del curso:
README.md,CURRICULUM.md(58 lecciones en 13 módulos) y el sistema de memoriamemory/con protocolo para agentes. - Se diseñó la metodología: teoría → ejercicios → corrección → autoevaluación → aplicación al proyecto.
- Se redactaron las lecciones 00 (plantilla de arranque) y 01 (HTTP a fondo, completa con teoría, ejercicios y soluciones).
- Se lanzó al alumno la elección de: stack, nivel y proyecto hilo conductor (pendiente de respuesta).
Cómo fue el alumno: (sin datos aún; primera interacción)
Siguiente paso: recibir la elección de stack/nivel/proyecto → rellenar student-profile.md → personalizar Lección 00 con instrucciones concretas de instalación e impartir Lección 01 adaptada al stack.