{
  "course": "the-professor — Backend de Nivel Empresarial",
  "version": 1,
  "language": "es",
  "lastSession": {
    "date": "2026-09-30",
    "sessionNumber": "12.28",
    "summary": "Sesión 12.28 (CIERRE V1 — proyecto terminado en su versión 1.0): el curso queda 59/59 impartido y publicado, 100% bilingüe ES/EN (cero fallback), sitio sin dependencias (82 tests, ruff, CI con build+auditoría: 157 enlaces, axe 0, Lighthouse gate), PWA offline bilingüe, GA4 consentido con eventos del curso (lesson_read/exercises_open/solutions_open/search), SEO bilingüe listo (sitemap 532 URLs + hreflang con SITE_URL), CONTRIBUTING/SECURITY en repo y sitio, y despliegue Pages por push. Etiqueta git v1.0.0. README/CONTRIBUTING revisados (82 tests, ~488 páginas). QUEDA PARA EL ALUMNO: (1) corregir Lección 01 (ejercicios entregados), (2) corregir 00b (modelo v1 + ejercicios), (3) confirmar GA4 en 24-48h (Realtime + Definiciones de eventos + key events), (4) UN redeploy de Pages con SITE_URL en el entorno Production: al cierre producción ya sirve el código de main (consent.js v6 en lecciones, eventos, SW professor-v12, bitácora 12.28) pero la build salió sin SITE_URL — sitemap.xml 404 y sin hreflang en el head; con la variable aplicada, repetir la verificación del sitemap (532 URLs) y los hreflang. — Resumen de sesiones previas: Sesión 12.27 (cierre del proyecto): verificación post-SITE_URL — producción ATASCADA en el despliegue de 75ed321 (contributing/security 200, SW v11, lecciones con main.js?v=6 sin consent.js, sitemap 404): los commits d3d3b2d y 7de0167 no han aterrizado tras ~25 min. Causa probable: fijar variables de entorno en Pages no dispara build y el redeploy manual no se lanzó. Acción: push de memoria para encolar build nueva. Guía GA4 entregada (eventos personalizados, key events, informe Páginas y pantallas, Realtime). Siguiente: confirmar build Success en el dashboard de Pages, verificar sitemap 532 URLs + hreflang + consent.js v6 en lecciones, y con las correcciones 00b/01 del alumno dar por cerrado el proyecto. — Resumen de sesiones previas: Sesión 12.26 (Realtime GA4 + consent.js universal con eventos + SEO listo): recorrido de producción documentado para Tiempo real de GA4 (page_view por lección, card de usuarios=1, vistas por título, ubicación pages.dev). HUECO CAZADO: las lecciones no medían — consent.js solo iba en portadas y el head de lecciones (build.py) no lo traía (main.js además en v6). Fix: consent.js universal en los 4 heads. Eventos propios del curso en consent.js (trackEvent no-op sin gtag = nada viaja sin aceptación): lesson_read (lesson_id, lesson_doc, language), exercises_open, solutions_open, search (search_term, >=2 letras, 1 por término, truncado 100). Versionado: consent.js v6, main.js v8, SW professor-v12. Verificado en navegador: lesson_read/exercises_open/solutions_open/search disparando; banner aceptado dentro de página cualquiera carga GA. SEO bilingüe demostrado en local con SITE_URL: sitemap 532 URLs con alternates es/en/x-default + hreflang en lecciones; producción aún 404 hasta fijar la variable en Pages. 82 tests verdes, ruff OK. Pendiente: SITE_URL en Pages + redeploy + verificar sitemap/hreflang. — Resumen de sesiones previas: Sesión 12.25 (GA verificado en producción): URL de producción registrada https://the-professor-ete.pages.dev (Cloudflare Pages). Verificación end-to-end en navegador real: consent.js?v=5 con G-8R9VHZWTXL y SW professor-v11 servidos; al aceptar estadísticas, gtag carga y sale page_view a /g/collect → 204 con dl=the-professor-ete.pages.dev, gcs=G101 (analytics granted/ads denied), anonymize_ip y beacon. LA ETIQUETA MIDE. El aviso 'recogida de datos no habilitada' del dashboard es el estado del asistente de GA4 (procesa hasta 24h; el sitio solo mide a quien acepta; NO pegar su snippet en <head>: el nuestro es el mismo gtag gated por RGPD). Pendiente: SITE_URL=https://the-professor-ete.pages.dev en Pages (sitemap+hreflang); revisar Realtime de GA4. — Resumen de sesiones previas: Sesión 12.24 (CONTRIBUTING.md y SECURITY.md en repo y sitio): guías nuevas (regla de oro memory/, contenido por módulo vs site-tools, verificación local, CI, convención de commits; seguridad: alcance estático, advisory privado, SLA 48h, GA solo tras consentimiento) + README enlazándolas. El CI cazó el problema al push: las guías no eran páginas del sitio y el link checker las marcó rotas desde /readme/. Fix estilo del sitio: /contributing/ y /security/ en MD_PAGES (serve.py) + congelado (build_static.py, dist 487 páginas) + REPO_DOC_ROUTES (build.py) + semillas en check_links.py. DOS bugs preexistentes del parser cazados: LICENSE se pintaba literal (nueva ROOT_FILE_LINK → /security/) y TODOS los enlaces externos [texto](https://…) se pintaban sin enlace (nueva HTTP_LINK con rel=noopener; /readme/ recupera sus 3 enlaces). Tests 79→82 verdes, ruff OK, link checker 157 OK, axe 0 (10 págs). — Resumen de sesiones previas: Sesión 12.23 (Google Analytics activado tras consentimiento): GA_MEASUREMENT_ID '' → 'G-8R9VHZWTXL' en site/js/consent.js — la integración gated ya existía (script async + dataLayer + gtag('js') + gtag('config') con anonymize_ip, beacon y consent mode analytics_storage granted/ad_storage denied): se carga SOLO tras aceptar 'Estadísticas', nunca antes. NUEVO denyAnalyticsIfLoaded(): si GA ya cargó en la página y el usuario revoca+rechaza, gtag('consent','update', denied) para la sesión en caliente — la revocación ya no espera a la visita siguiente. Versionado: consent.js v4→v5 (chrome.py, index.html, index.en.html e inyección bajo demanda de main.js), SW professor-v10→v11, README con la versión del SW; privacidad.md no cambia (ya describía esta configuración). Flujo verificado en navegador: limpio = sin gtag/dataLayer; Rechazar = cero googletagmanager; Aceptar = script con ID exacto y dataLayer vivo (gtm.dom/gtm.load); recarga con preferencia = GA solo, sin banner. 79 tests verdes, ruff OK, node --check de los 3 JS, dist 485 páginas con ?v=5 y el ID, link checker 154 OK, axe 0 (10 págs). — Resumen de sesiones previas: Sesión 12.22 (README + revisión completa de la web): README actualizado al estado real del despliegue (badges 79 tests verdes y 350 md ES+EN, lecciones '59/59 bilingües', tabla lessons/ con doble idioma, bloque Idiomas reescrito con el despliegue completo y cero fallback, SW professor-v10 en la doc de Pages, Contribuir con 79 tests). Recorrido del sitio EN en navegador (portada /en/, catálogo con módulo 12, conmutador ES/EN, lección 57 EN): portada y catálogo perfectos; en las páginas de lección EN se cazaron DOS fugas de español de UI — el summary del TOC ('En esta lección') hardcodeado en build.py y el aria-label del breadcrumb ('Ruta de navegación') hardcodeado en chrome.py. Fix: render_toc y breadcrumb reciben lang y tocan el diccionario UI (nuevas claves toc_summary y breadcrumb_aria es/en); render_page pasa el idioma; páginas sueltas siguen ES-only por diseño. Tests nuevos: breadcrumb EN 'Breadcrumb' y TOC EN 'In this lesson' sin 'En esta lección'. 79 tests verdes, ruff OK, dist 485 páginas, cero 'En esta lección'/'Ruta de navegación' en /en/ del dist (175 lecciones EN con 'In this lesson'), cero fallbacks en TODO /en/, link checker 154 OK, axe 0 (10 págs); fix verificado en vivo en el navegador (57 EN con migas y TOC en inglés, ES intacto). — Resumen de sesiones previas: Sesión 12.21 (módulo 12 EN, cierre del despliegue bilingüe): lecciones 52-57 traducidas al inglés (18 docs: Clean/Hexagonal y DDD con VOs, agregado y DDD parcial; monolito modular vs microservicios con constitución de fronteras y strangle; resiliencia con timeouts, reintentos+jitter, breaker y degradación; escalado con ley cero, réplicas, partición y escalera con triggers; cumplimiento con RGPD, mapa Art. 30, SOC 2 y DPO interno; proyecto final con las 10 garantías y el postmortem simulado). EL SITIO QUEDA 100% BILINGÜE: 59 de 59 lecciones EN sin fallback. Test actualizado: el catálogo EN ya no lleva chip 'Spanish only' (verifica 0 fallbacks + título del overlay). 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en TODO /en/. — Resumen de sesiones previas: Sesión 12.20 (módulo 11 EN): lecciones 48-51 traducidas al inglés (12 docs: documentación y ADRs con Diátaxis, índice de 12 decisiones y docs-as-code con caducidad; estimación en rangos con gatillo y trozos verticales con demo; revisión de código por impacto con escala bloqueante/debería/nit y self-review de equipo de 1; entender el negocio con mapa del dinero, 5 whys y traducción bidireccional). El sitio EN cubre los módulos 0-11 completos (00-51, 52 de 59 lecciones sin fallback); solo queda el módulo 12 (52-57). 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 48-51 en vivo. — Resumen de sesiones previas: Sesión 12.19 (módulo 10 EN): lecciones 45-47 traducidas al inglés (9 docs: logging estructurado JSON con trace_id por contextvars, métricas RED + negocio con trazas OTel sampling 0.1 y SLOs con burn rate multi-ventana, depuración en prod con protocolo de 5 pasos, postmortem blameless y on-call sostenible). El sitio EN cubre los módulos 0-10 completos (00-47, 48 de 59 lecciones sin fallback); quedan 48-57. 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 45-47 en vivo. — Resumen de sesiones previas: Sesión 12.18 (módulo 9 EN): lecciones 40-44 traducidas al inglés (15 docs: Docker multi-stage y compose, CI/CD con digest auditable y blue-green, nube sin vendor-lock con IAM y factura, K8s con probes/HPA y BD fuera del cluster, Terraform con state con lock y prevent_destroy). El sitio EN cubre los módulos 0-9 completos (00-44, 45 de 59 lecciones sin fallback); quedan 45-57. 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 40-44 en vivo. — Resumen de sesiones previas: Sesión 12.17 (módulo 8 EN): lecciones 37-39 traducidas al inglés (9 docs: perfilado con cProfile/py-spy, caché con single-flight y claves versionadas, pools/paginación por cursor/compresión). El sitio EN cubre los módulos 0-8 completos (00-39, 58 de 59 lecciones sin fallback); quedan 40-57. 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 37-39 en vivo. — Resumen de sesiones previas: Sesión 12.16 (módulo 7 EN): lecciones 33-36 traducidas al inglés (12 docs: unitarias con pytest/fakes y anti-patrones, integración con Postgres 16 real y tabla de garantías, E2E con catálogo de historias y contrato pact-style, carga con k6 y capacidad documentada). El sitio EN cubre los módulos 0-7 completos (00-36, 55 de 59 lecciones sin fallback); quedan 37-57. 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 33-36 en vivo. — Resumen de sesiones previas: Sesión 12.15 (módulo 6 EN): lecciones 29-32 traducidas al inglés (12 docs: colas y jobs con Celery/on_commit/idempotencia, brokers con Redis Streams y DLQ operada, tareas programadas con lotes y locks, consistencia eventual y saga con PurchaseSaga y compensaciones). El sitio EN cubre los módulos 0-6 completos (00-32, 51 de 59 lecciones sin fallback); quedan 33-57. 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 29-32 en vivo. — Resumen de sesiones previas: Sesión 12.14 (módulo 5 EN): lecciones 24-28 traducidas al inglés (15 docs: SOLID e inyección con Clock Protocol, patrones de servicio con Outbox y DTO, errores centralizados RFC 7807 con handler y taxonomía DomainError, configuración 12-factor con settings plano y rotación dual, refactor con caracterización y contract guard). El sitio EN cubre los módulos 0-5 completos (00-28, 47 de 59 lecciones sin fallback); quedan 29-57. 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 24-28 en vivo. — Resumen de sesiones previas: Sesión 12.13 (módulo 4 EN): lecciones 18-23 traducidas al inglés (18 docs: sesiones vs tokens/SimpleJWT, OAuth2-OIDC con PKCE y account linking, hash/MFA con TOTP y reset seguro, autorización con matriz y IDOR, OWASP Top 10 con SSRF/mass assignment/timing, secretos-rate limiting-RGPD con token bucket y anonimización). El sitio EN cubre los módulos 0-4 completos (00-23, 42 de 59 lecciones sin fallback); quedan 24-57. 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 18-23 en vivo. — Resumen de sesiones previas: Sesión 12.12 (módulo 3 EN): lecciones 13-17 traducidas al inglés (15 docs: REST, idempotencia/versionado, validación/OpenAPI, gRPC-GraphQL-WS/SSE, webhooks). El sitio EN cubre los módulos 0-3 completos (00-17, 36 de 59 lecciones sin fallback); quedan 18-57. 79 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 13-17 en vivo. — Resumen de sesiones previas: Sesión 12.11 (banner + skeleton, reporte del alumno): banner de cookies rediseñado a barra inferior completa (100% ancho, alto del contenido, fondo --color-ocean legible en ambos temas; Rechazar corregido); skeleton del buscador se quedaba en bucle porque display:grid pisaba hidden — guardia global [hidden]{display:none!important} en main.css; 'Configuración de cookies' era un callejón (no borraba) → ahora clearConsent()+buildBanner() y NUEVO botón en el footer de TODA la web (data-consent-configure, bilingüe; main.js delega y carga consent.js bajo demanda); privacidad con su propio kicker 'privacidad y datos' (antes heredaba 'docs del diseño'). Banner bilingüe es/en con enlace a privacidad por idioma y foco accesible. main.js v7, consent.js v4, SW professor-v10. 79 tests, ruff OK, axe 0 (10 págs), link checker 154 OK. — Resumen de sesiones previas: Sesión 12.10 (módulo 2 EN): lecciones 07-12 traducidas al inglés (18 docs: SQL avanzado, modelado, índices/EXPLAIN, transacciones, migraciones versionadas, NoSQL). El sitio EN cubre los módulos 0-2 completos (00-12, 31 de 59 lecciones sin fallback); el resto mantiene el aviso Spanish only. 78 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas, cero avisos de fallback en 07-12 en vivo. — Resumen de sesiones previas: Sesión 12.9 (pulido bilingüe + módulo 1 EN): hreflang ES/EN + x-default en lecciones y sitemap.xml bilingüe (196 URLs), ambos gated por SITE_URL (fijar en dashboard Pages); 404.en.html y offline.en.html con render_404(lang), ruta /en/offline, 404 EN para /en/* desconocidas y SW professor-v7 con offline por idioma (precache bilingüe); REPARADOS 36+ enlaces cruzados rotos por el renombrado de slugs (65 ficheros con el MAPPING del script; eran invisibles al link checker); módulo 1 completo traducido (02-06, 15 docs lessons/<slug>/en/*.md). 78 tests verdes, ruff OK, axe 0 (10 págs), link checker 154 OK, dist 485 páginas.  Siguiente: fijar SITE_URL en Pages y verificar sitemap; link checker con las 59 lecciones ES; traducir el módulo 2 (07-12) si procede; correcciones 00b/01 del alumno. — Resumen de sesiones previas: Sesión 12.8 (bilingüe + slugs EN): slugs de lecciones a inglés (51/59, git mv + site/tools/rename_slugs_en.py); sitio ES (raíz) + EN (/en/) SIN JavaScript: chrome.py bilingüe (UI es/en ~30 claves, conmutador ES/EN en la nav; main.js reescribe el href a la página equivalente), overlay site/i18n/en.json (13 módulos + 59 lecciones), build.py con lesson_doc_path/render_doc_page(lang) y fallback avisado ('Spanish only'), buscador /en/search-index.json (todo bajo /en/), negociación Accept-Language en la raíz y portada index.en.html; piloto traducido 00/00b/01 (7 docs lessons/<slug>/en/<kind>.md, título por H1); build_static congela 485 páginas (los dos idiomas). Bugs cazados por los tests: chip 'Spanish only' sobrescrito por línea duplicada, lang_switcher con hrefs invertidos, render_page ignorando el idioma en shell/crumbs/chips/pager. Cloudflare Pages desbloqueado: [tool.setuptools] packages=[] en pyproject.toml (el pip install . automático ya no revienta por flat-layout; equivale a SKIP_DEPENDENCY_INSTALL=1; verificado con pip wheel .). 76 tests verdes, ruff OK, axe 0 violaciones (10 págs), Lighthouse mediana de 3: 0.98-0.99 perf / 1.00 BP (gate 0.8/0.9), check_links 154 OK, SW professor-v6. Sesiones 12.5-12.7 (se mantienen): build_static.py + serve_static.py (contrato Pages: 404 real + gzip), lh_check mediana de 3, fuentes autohospedadas (6 woff2, Material Symbols subset 2.4KB, cero CDNs), banner de cookies tras el primer render (LCP = wordmark), despliegue a Cloudflare Pages documentado en el README. Sesión 12.4: la 12.3 (registro de proyectos /proyectos/) se REVERTIÓ por decisión del alumno — NO volver a proponerlo. Sesión 12.2: /docs/requisitos/ con RF/RNF-TF, MoSCoW, invariantes I1-I4. Sesión 12.1: catálogo 59/59 Publicada. Siguiente: traducir el módulo 1 (02-06) al EN; conectar Cloudflare Pages (sin bloqueo ya); correcciones 00b/01 del alumno.",
    "agent": "Buffy"
  },
  "student": {
    "stack": "Python + Django + Django REST Framework (DRF)",
    "stackDetail": "Python 3.12+, Django 5.x, DRF, pytest, Celery, Redis, Docker. SO: Linux. BD: SQLite → PostgreSQL (Módulo 2).",
    "level": "Intermedio",
    "project": "TicketFlow — plataforma de venta de entradas (eventos, reservas con concurrencia, pagos con webhooks, caché de disponibilidad)"
  },
  "currentLesson": {
    "id": "00b",
    "title": "Modelado inicial de TicketFlow",
    "status": "impartida-implementacion-pendiente",
    "path": "lessons/00b-ticketflow-initial-modeling/"
  },
  "nextLesson": {
    "id": "01",
    "title": "HTTP a fondo (ejercicios pendientes; 02–57 ya publicadas y listas para impartir)",
    "path": "lessons/01-http-in-depth/"
  },
  "lessonsCompleted": [
    "00"
  ],
  "moduleProgress": {
    "0-puesta-a-punto": {
      "completed": 1,
      "total": 2
    },
    "1-fundamentos": {
      "completed": 0,
      "total": 6
    },
    "2-bases-de-datos": {
      "completed": 0,
      "total": 6
    },
    "3-diseno-de-apis": {
      "completed": 0,
      "total": 5
    },
    "4-auth-y-seguridad": {
      "completed": 0,
      "total": 6
    },
    "5-arquitectura": {
      "completed": 0,
      "total": 5
    },
    "6-async-y-mensajeria": {
      "completed": 0,
      "total": 4
    },
    "7-pruebas": {
      "completed": 0,
      "total": 4
    },
    "8-rendimiento-y-cache": {
      "completed": 0,
      "total": 3
    },
    "9-despliegue": {
      "completed": 0,
      "total": 5
    },
    "10-observabilidad": {
      "completed": 0,
      "total": 3
    },
    "11-habilidades-no-tecnicas": {
      "completed": 0,
      "total": 4
    },
    "12-nivel-empresarial": {
      "completed": 0,
      "total": 6
    }
  },
  "workInProgress": [
    "Lección 01 (HTTP): ejercicios entregados por el alumno → corregir y actualizar estado",
    "Lección 00b (Modelado): implementación del modelo v1 y ejercicios pendientes → corregir",
    "Impartición 1x1: TODO el temario (00-57) está publicado; el profesor imparte lección a lección cuando el alumno avanza"
  ],
  "pendingCount": 2
}
