ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Meseo (meseo.es) — formación de equipos de sala, multi-restaurante. Cada restaurante es un venue en data/themes.json con su propia identidad (nombre, colores, textos); TXOKO es el venue fundador. La marca de la app es Meseo: ningún restaurante presta su nombre al producto. PWA monolítica (index.html + sw.js) con sincronización vía Supabase, módulo de vinos con MapLibre, juegos de repaso (Mr. Shoesmith, Camarero Survivors), Quiz del Día y notificaciones push.README 전문 보기 ↗
Meseo (meseo.es) — formación de equipos de sala, multi-restaurante. Cada restaurante es un venue en data/themes.json con su propia identidad (nombre, colores, textos); TXOKO es el venue fundador. La marca de la app es Meseo: ningún restaurante presta su nombre al producto.
PWA monolítica (index.html + sw.js) con sincronización vía Supabase, módulo de vinos con MapLibre, juegos de repaso (Mr. Shoesmith, Camarero Survivors), Quiz del Día y notificaciones push.
v7.204 · Jul 2026
La versión se expone en tres sitios sincronizados:
<meta name="app-version"> y <meta name="app-build"> en el <head>APP_VERSION / APP_BUILD en JS (también en window.*)Refactor completo del módulo LQA (Examen, Situaciones, Auditor) tras auditoría de v5.7. Sin cambios en el contenido (preguntas y situaciones intactas); todo el trabajo es en el motor, la UX y la analítica.
Motor compartido (_lqaEngine)
lqaSitState, lqaExamState, lqaAudState) — antes había let globales por modo (lqaSituationIndex, lqaExamIndex, lqaAuditorIndex + sus arrays/scores) que se contaminaban entre sesiones, recargas y pestañas._lqaShuffle() con Fisher-Yates correcto — antes sort(()=>Math.random()-.5) que es sesgado._lqaPushScore() que persiste resultados a emp.lqaScores con cap rodante de 50 entradas (parity con emp.sessions.slice(-50) introducido en v5.7)._lqaInstallKbd() / _lqaUninstallKbd() para gestionar el listener de teclado entre pantallas sin leaks._lqaConfirmExit() para el botón ← / tecla Esc con confirm() antes de descartar progreso a media sesión.Feedback enriquecido (U1)
Antes: ✓ / ✗ + explicación. Las 4 opciones desaparecían — el usuario no veía qué eligió mal frente a la correcta.
Ahora: las 4 opciones (o las 2 verdictos del Auditor) se muestran tras cada respuesta con borde verde (correcta), borde rojo (la del usuario si era errónea) y marker ✓/✗. Aplicado a Examen, Situaciones y Auditor.
Pantalla "Revisar respuestas" (U5)
Nueva en los 3 modos. Al final de la sesión hay un botón "Revisar respuestas" que lista las 10 preguntas en <details> colapsibles, cada una con: enunciado, opciones marcadas con verde/rojo (con el escenario completo en Situaciones), explicación, y referencia al estándar LQA cuando aplica.
Desglose por categoría — Situaciones (A1)
Cada situación tiene cat (llegada/servicio/vino/carta/montaje/comportamiento). Ahora:
<acertadas>/<total> para esa sesión, ordenadas por % ascendente (zonas débiles primero).emp.lqaSitByCat = {<cat>: {correct, total}} a lo largo del tiempo. Datos disponibles para el dashboard de supervisor y para futuras features de "modo zona débil".Atajos de teclado (U2) Aplicados a los 3 modos:
1–4 (o 1–2 en Auditor) → seleccionar opción / verdictoEnter o Espacio → avanzar a la siguienteEsc → confirmar salida de la sesiónCaptura de tiempo (E3)
time_sec se persiste en emp.lqaScores[].time_sec y se muestra en la pantalla de resultados (+X XP · Ys). Antes solo se capturaba en el módulo de exámenes de platos; los modos LQA quedaban sin esa señal.
Resultados idempotentes
Antes: renderLqaSituationResults re-persistía a emp y re-otorgaba XP cada vez que se llamaba. Si volvías de "Revisar respuestas" a la pantalla de resultados se duplicaba el XP. Ahora _lqaCompleteX() persiste una única vez y marca state.completed = true; las re-renders son puras.
Cambios secundarios
<h1 tabindex="-1">) en pantallas de resultados con .focus() para anuncio del lectores de pantalla.aria-live="polite" en pantallas de feedback.role="radiogroup" en grupos de opciones.min-height: 48px en botones de opción (cumple WCAG touch target).Compatibilidad
emp.lqaScores ahora incluye time_sec adicional. Lecturas anteriores (pre-v5.8) siguen funcionando: el campo es ignorado donde no se usa.emp.lqaSitByCat es nuevo; código que no lo lee no se ve afectado.LQA_SITUATIONS, LQA_EXAM_QUESTIONS, LQA_AUDIT_SCENARIOS igual que antes — no hay cambios en el contenido.Diferido a v5.9
emp.lqaSitByCatAmpliación de contenido del simulador LQA basada en la auditoría LQA del 01-may-2026 (78.7% global).
Nuevas situaciones (22) en LQA_SITUATIONS — array pasa de 25 a 47 entradas
Distribución final por categoría (47 totales): comportamiento ×13 · servicio ×10 · vino ×9 · montaje ×6 · carta ×5 · llegada ×4. Real-vs-pedagógicas: 28 reales · 19 pedagógicas.
No hay cambios en código motor — startLqaSituations(), renderLqaSituationQuestion(), lqaSituationAnswer(), renderLqaSituationResults() siguen idénticos. La sesión sigue eligiendo 10 situaciones aleatorias del pool ampliado.
Auditoría post-v5.6 detectó 20 hallazgos. Este release los resuelve todos los factibles.
Seguridad
lb-avatar y lb-name. Ahora pasan por escapeHTML(). Un nombre con <script>…</script> ya no ejecuta JS en cada espectador del ranking._pinAttempts reseteaba el campo streak cuando un lockout expiraba, neutralizando el backoff exponencial (30 s → 60 s → 5 min → 15 min). Ahora el estado se preserva intacto entre recargas; sólo recordPinSuccess lo limpia.SUPA_KEY en query string vía sendBeacon — sendBeacon no soporta headers, así que el código metía la apikey en la URL (queda en logs de proxies, history exporters, Referer). Sustituido por fetch(..., {keepalive:true}) con apikey en headers.awardXP toast — ${reason} se interpolaba sin escapar; algunos callers concatenan títulos de logros/misiones que en el futuro podrían contener <. Escapado con escapeHTML().window.txokoDiag siempre expuesto — la función global de diagnóstico mostraba el usuario actual, su XP/streak y los top-5 de la nube por console.log. Ahora se expone solo cuando DEBUG === true (localhost o ?debug=1).Limpieza de datos en logout
logout() llama a pushManager.getSubscription().unsubscribe() y borra la fila correspondiente vía DELETE /push_subscriptions?endpoint=eq.… con keepalive:true.Lógica de negocio
duelWins++ siempre iba a currentUser aunque ganase el segundo jugador en pass-and-play. Ahora se acredita al ganador real; el XP solo se otorga cuando coincide con el usuario logueado.if(emp.streak===N) con igualdad estricta perdía bonuses si la racha saltaba un valor (p. ej., al hacer Math.max(local, remote) en cloud restore). Ahora hay un mapa streakMilestones que registra qué hitos ya pagaron, así un salto 0→7 cobra 3 + 7 días retroactivamente, sin pagar dos veces.Robustez
fetchT(url, opts, ms) con AbortController (default 10 s, 8 s en login). En WiFi mala el flujo de login ya no cuelga indefinidamente.crypto.subtle no disponible — en orígenes inseguros (HTTP, IPs locales) crypto.subtle es undefined y hashPin lanzaba un TypeError críptico. Ahora un guard explícito tira un Error claro._restoreCache crecía sin bound. Sweep periódico cada 60 s + cap duro de 50 entradas.sessions[] local sin trim — la nube trunca a 20 entradas en upload pero local crecía sin límite hasta romper la quota de localStorage. Trim a 50 tras cada examen.showBrowserNotif apuntaba a /favicon.ico que no existe. Ahora usa icon.svg.Refactors
document.body subtree:true recalculaba ripples en cada cambio del DOM (drain continuo de CPU en móviles). Sustituido por una sola delegación en document que captura pointerdown y matchea por selector con closest. addRipple acepta un explicitTarget para que la delegación le pase el botón matched en lugar de event.currentTarget (que sería document).storage reemplazaba DB entero, perdiendo escrituras en vuelo (p. ej., examScores incrementados en otra pestaña). Ahora hay un merge por empleado con Math.max para counters (XP, streak, txokoRecord, duelWins), unión para mapas (knownDishes, examCorrect, streakMilestones) y la sesión más larga gana.loadDB ahora intenta leer txoko_data_v3, _v2, _v1 si no encuentra v4, promueve los datos al esquema actual y elimina la clave legacy. Usuarios offline en versiones antiguas conservan XP/streaks tras actualizar.Higiene
escapeHtml y escapeHTML consolidados — el segundo (legacy) ahora delega al primero. El bug de tratar 0/false como cadena vacía está corregido.↻ actualizando… y alert('Error') del flujo de duelo.Seguridad
try/catch ya no se inyectan crudos vía innerHTML — se escapan con escapeHtml() (mitiga un posible vector XSS y deja de filtrar stack traces a usuarios finales).crossorigin="anonymous" para permitir SRI. Los hashes SRI deben añadirse en el deploy (integrity="sha384-...") — ver nota al pie del bucket de scripts en index.html.USE_SERVER_PIN_VERIFY. Para activarlo:
supabase/supervisor_pin.sql en tu proyecto Supabase.USE_SERVER_PIN_VERIFY = false por true en index.html (línea cercana a la definición de SUP_PIN_HASH).SUP_PIN_HASH del bundle.PWA
manifest.json con icons SVG embebidos (data URL) — la app es ahora "Add to Home Screen" en iOS/Android.sw.js ahora cachea el shell con estrategia stale-while-revalidate — la app abre offline.notificationclick ya no depende de que la URL contenga la cadena "Txoko" (rompía si se desplegaba bajo otro path); usa el scope del SW.Bugs
JSON.parse silencioso reemplazado por warnings reales (console.warn) detrás del flag DEBUG, para detectar regresiones de schema sin spamear al usuario.Accesibilidad
aria-label.ESC para cerrar.Hygiene
console.* ruidosos reemplazados por dbg(...) que solo emite si DEBUG === true (localhost o ?debug=1).escapeHtml() global expuesto en window para uso futuro.Versión anterior. Ver historial git para detalles.
Suite de smoke tests sin dependencias (Node ≥18) que protege un PWA de un solo
archivo sin paso de build. Se ejecuta en cada push/PR vía GitHub Actions
(.github/workflows/ci.yml).
npm test # node tests/smoke.mjs
Comprueba: sintaxis del <script> principal, validez/estructura de los JSON en
data/, que toda ruta de showTab apunte a una función existente, que las rutas
de loadLazyData existan en disco, versión del service worker, crossorigin en
dependencias CDN y ausencia de marcadores de conflicto de git.
index.html incluye una Content-Security-Policy (meta tag) construida
del inventario completo de orígenes que la app contacta. Bloquea scripts
externos inyectados y limita los destinos de connect-src (anti-exfiltración).
Usa 'unsafe-inline' en script-src por los ~294 handlers onclick inline
(eliminarlos es trabajo de modularización). Rollback: borrar la meta + bump SW.node tools/compute-sri.mjs # inyecta integrity="sha384-…" en index.html
node tools/compute-sri.mjs --check # CI/verificación: falla si falta o está stale
SHA-256(pin+salt) — vulnerable porque el hash es legible con la anon key
y un PIN de 4 dígitos con salt compartido se revierte al instante. Para activar la
verificación con bcrypt server-side (no legible por anon):
Ejecutar supabase/employee_pin.sql en el editor SQL de Supabase.
Poner USE_SERVER_EMP_PIN_VERIFY = true en index.html y desplegar.
La migración es lazy y no destructiva: cada empleado verifica en local una
vez más y se migra a bcrypt al entrar. Tras 2-4 semanas (todos han entrado),
limpiar el hash legacy: UPDATE public.employees SET pin = NULL; y quitar pin
del payload de supaUpsertEmployee().
Fallback: ante fallo de red/RPC, vuelve al compare local — un corte de Supabase
nunca deja al staff sin entrar. Rollback: USE_SERVER_EMP_PIN_VERIFY = false.
Sitio estático. Subir index.html, styles.css, sw.js, manifest.json, la
carpeta data/ (raíz) a cualquier hosting estático (GitHub Pages, Netlify, etc.).
Requisito: HTTPS para que el service worker y push notifications funcionen.
© 2026 Duvan Stiven Ramírez Duque. Uso interno autorizado a Txoko by Martín Berasategui · Ritz-Carlton Abama.