Skip to content

Commit 9963e35

Browse files
committed
fix: add educational labs with progress tracking and enhanced IP blocking logic
1 parent b84bd76 commit 9963e35

1 file changed

Lines changed: 141 additions & 1 deletion

File tree

src/data/iteraciones-portfolio.ts

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,11 +809,151 @@ export const ITERACIONES: Iteracion[] = [
809809
},
810810
],
811811
},
812+
// ───────────────────────────────────────────────────────────────────────
813+
{
814+
id: 'pf-platziconf-slides',
815+
fase: 'Fase 19 · Charla Platzi Conf y presentaciones públicas',
816+
nombre: 'Landing de la charla y presentaciones (slides) con estado en vivo',
817+
rango: '18 jul 2026',
818+
ghSince: '2026-07-18',
819+
ghUntil: '2026-07-18',
820+
commits: 16,
821+
resumen:
822+
'Landing propia para la charla en Platzi Conf con tema visual dedicado, y el módulo de slides (Fase 3) gana un endpoint público de estado de presentación y botón para compartir el enlace, dejando de depender de sesión admin para que la audiencia siga la charla en vivo.',
823+
historias: [
824+
{
825+
id: 'PF-PZ-01', titulo: 'Como asistente a la charla, quiero una landing con la agenda y la forma de contactar al ponente',
826+
tipo: 'historia', valor: 'medio', col: 'aceptada', par: 'MR', agente: 'Claude',
827+
fecha: '2026-07-18', tags: ['platziconf', 'landing', 'fase-19'],
828+
dod: [
829+
ok('/platziconf con tema visual propio (platzi-theme) y CTA a Calendly.'),
830+
ok('Fondo full-bleed consistente en pantallas anchas.'),
831+
],
832+
},
833+
{
834+
id: 'PF-PZ-02', titulo: 'Como asistente, quiero ver el estado de la presentación (slide actual) sin tener sesión de administrador',
835+
tipo: 'historia', valor: 'medio', col: 'aceptada', par: 'MR', agente: 'Claude',
836+
fecha: '2026-07-18', tags: ['slides', 'público', 'fase-19'],
837+
dod: [
838+
ok('API de estado de presentación expuesta sin requerir sesión admin, consumida por la vista de presentación.'),
839+
ok('Botón para copiar el enlace de la slide actual al portapapeles desde /admin/slides.'),
840+
],
841+
},
842+
],
843+
},
844+
// ───────────────────────────────────────────────────────────────────────
845+
{
846+
id: 'pf-pulido-visual',
847+
fase: 'Fase 20 · Pulido visual y saneamiento de tech stack',
848+
nombre: 'Fondo con profundidad (blobs), separación de navbar y parser único de tech stack',
849+
rango: '18 jul 2026',
850+
ghSince: '2026-07-18',
851+
ghUntil: '2026-07-18',
852+
commits: 15,
853+
resumen:
854+
'Retoque visual transversal: fondo con blobs degradados en BaseLayout para dar profundidad sin recargar el diseño, y un scrim detrás del navbar para separarlo del contenido al hacer scroll. En paralelo se resuelve una inconsistencia de datos: el tech stack de los proyectos se guardaba en formatos distintos (JSON vs texto plano) y cada vista lo parseaba a su manera; ahora hay un único parseTechStack reutilizado en ProjectCard y las páginas de detalle.',
855+
historias: [
856+
{
857+
id: 'PF-PV-01', titulo: 'Como visitante, quiero una interfaz con más profundidad visual sin perder legibilidad',
858+
tipo: 'historia', valor: 'bajo', col: 'aceptada', par: 'MR', agente: 'Claude',
859+
fecha: '2026-07-18', tags: ['ui', 'diseño', 'fase-20'],
860+
dod: [
861+
ok('Blobs de fondo degradados añadidos a BaseLayout con posicionamiento propio, sin interferir con el contenido.'),
862+
ok('Scrim detrás del navbar para mantener contraste sobre contenido con scroll.'),
863+
],
864+
},
865+
{
866+
id: 'PF-PV-02', titulo: 'Como desarrollador, quiero un único parser de tech stack para no repetir lógica de formato en cada vista de proyecto',
867+
tipo: 'tarea', valor: 'medio', col: 'aceptada', par: 'MR', agente: 'Claude',
868+
fecha: '2026-07-18', tags: ['refactor', 'proyectos', 'fase-20'],
869+
dod: [
870+
ok('parseTechStack maneja tanto JSON como texto plano de forma tolerante.'),
871+
ok('ProjectCard, index y [id].astro migrados al parser único, eliminando JSON.parse directo.'),
872+
],
873+
},
874+
],
875+
},
876+
// ───────────────────────────────────────────────────────────────────────
877+
{
878+
id: 'pf-cv-tracking',
879+
fase: 'Fase 21 · Descarga de CV con huella y auditoría',
880+
nombre: 'Descarga de CV con token de un solo uso, fingerprint y panel de estadísticas',
881+
rango: '18 – 19 jul 2026',
882+
ghSince: '2026-07-18',
883+
ghUntil: '2026-07-19',
884+
commits: 12,
885+
resumen:
886+
'Quién descarga el CV deja de ser invisible: /cv reutiliza el recolector de señales del lab de fingerprinting para capturar un hash de dispositivo antes de emitir un token de un solo uso (ventana de 5 min) que autoriza la descarga real. A diferencia de la demo efímera, aquí el registro es permanente (sin TTL) y las revisitas del mismo dispositivo se acumulan en vez de duplicar filas. /admin/lab/cv-downloads expone el historial completo al administrador.',
887+
historias: [
888+
{
889+
id: 'PF-CV-01', titulo: 'Como visitante, quiero descargar el CV desde la página de contacto',
890+
tipo: 'historia', valor: 'medio', col: 'aceptada', par: 'MR', agente: 'Claude',
891+
fecha: '2026-07-18', tags: ['cv', 'público', 'fase-21'],
892+
dod: [
893+
ok('Enlace de descarga de CV añadido a la página de contacto.'),
894+
ok('/api/cv/capture recibe las señales del dispositivo y emite un token de un solo uso; /api/cv/download lo valida antes de servir el archivo.'),
895+
],
896+
},
897+
{
898+
id: 'PF-CV-02', titulo: 'Como administrador, quiero ver quién descargó mi CV y detectar revisitas del mismo dispositivo',
899+
tipo: 'historia', valor: 'medio', col: 'aceptada', par: 'MR', agente: 'Claude',
900+
fecha: '2026-07-19', tags: ['cv', 'admin', 'seguridad', 'fase-21'],
901+
dod: [
902+
ok('cv-downloads.ts reutiliza el deviceHash del recolector de fingerprinting; sin TTL, a diferencia de las salas de la demo.'),
903+
ok('Revisitas del mismo deviceHash actualizan la fila existente (contador revisits) en vez de crear duplicados.'),
904+
ok('/admin/lab/cv-downloads y su API listan el historial completo con IP, UA y referer.'),
905+
],
906+
},
907+
],
908+
},
909+
// ───────────────────────────────────────────────────────────────────────
910+
{
911+
id: 'pf-labs-educativos-seguridad',
912+
fase: 'Fase 22 · Rutas de aprendizaje y bloqueo escalado de IPs',
913+
nombre: 'Open Security Labs con progreso persistente, IP blocking escalado y fixes de revisitas en fingerprinting',
914+
rango: '19 jul 2026',
915+
ghSince: '2026-07-19',
916+
ghUntil: '2026-07-19',
917+
commits: 20,
918+
resumen:
919+
'Se suma una sección educativa nueva: rutas de aprendizaje ("Linux Real" y más) con labs cronometrados y progreso que el visitante puede marcar como completado, persistido en educationLabProgress. En paralelo, la capa de seguridad gana bloqueo escalado de IPs (TTL creciente por reincidencia: 1h → 24h → 7d) con enforcement inline para honeypots además del cron de auto-block, y se corrige un bug de fondo en el lab de fingerprinting: joinDevice no reconocía correctamente las revisitas de un mismo dispositivo cuando el hash de la librería de referencia (libFpHash) variaba entre visitas.',
920+
historias: [
921+
{
922+
id: 'PF-LE-01', titulo: 'Como visitante, quiero seguir rutas de aprendizaje con labs cronometrados y marcar mi progreso',
923+
tipo: 'historia', valor: 'medio', col: 'aceptada', par: 'MR', agente: 'Claude',
924+
fecha: '2026-07-19', tags: ['educación', 'público', 'fase-22'],
925+
dod: [
926+
ok('educationPaths define rutas, temas y labs con duración y nivel (Inicial/Intermedio/Avanzado).'),
927+
ok('Tabla educationLabProgress persiste qué labs completó cada visitante.'),
928+
ok('API de progreso y toggle en /admin para gestionar el estado de cada lab.'),
929+
],
930+
},
931+
{
932+
id: 'PF-LE-02', titulo: 'Como administrador, quiero que una IP reincidente reciba un bloqueo cada vez más largo, no siempre el mismo TTL',
933+
tipo: 'historia', valor: 'alto', col: 'aceptada', par: 'MR', agente: 'Claude',
934+
fecha: '2026-07-19', tags: ['seguridad', 'blocklist', 'fase-22'],
935+
dod: [
936+
ok('blockIpEscalated calcula TTL creciente (1h → 24h → 7d) según el historial de bloqueos previos de la IP.'),
937+
ok('Honeypots tocados se bloquean inline desde el middleware, sin esperar al cron de auto-block.'),
938+
ok('Tests unitarios de blockIpEscalated y de la lógica de TTL en isBlocked.'),
939+
],
940+
},
941+
{
942+
id: 'PF-LE-03', titulo: 'Como visitante que revisita la sala de fingerprinting, quiero que el tablero me reconozca aunque el hash de la librería de referencia cambie',
943+
tipo: 'bug', valor: 'medio', col: 'aceptada', par: 'MR', agente: 'Claude',
944+
fecha: '2026-07-19', tags: ['fingerprint', 'bug', 'fase-22'],
945+
dod: [
946+
ok('joinDevice usa el ID único de dispositivo (no la etiqueta) para decidir si es una revisita, y ya no depende de que libFpHash coincida exactamente.'),
947+
ok('Tests con distintos escenarios de libFpHash (coincide, cambia, ausente) validando que la revisita se reconoce igual.'),
948+
],
949+
},
950+
],
951+
},
812952
]
813953

814954
export const COMMITS_POR_MES = [
815955
{ mes: 'abr', commits: 80 },
816956
{ mes: 'may', commits: 21 },
817957
{ mes: 'jun', commits: 104 },
818-
{ mes: 'jul', commits: 838 },
958+
{ mes: 'jul', commits: 901 },
819959
]

0 commit comments

Comments
 (0)