🏪 Almacén Copihue

Board de Tareas

Tocá para marcar como completada
0
Pendientes
0
Críticas
0
Altas
0
Hechas
🏆
💡 Tip del hito
En mobile, nunca uses event delegation para elementos generados con innerHTML
El problema del tap que no respondía venía de una regla simple del DOM mobile: cuando regenerás el HTML con innerHTML= y usás un listener en document.addEventListener('click') con closest(), en Android WebView el touch a veces no propaga como click cuando hay pointer-events conflictivos en los hijos.

La solución definitiva: usar un <button> nativo con onclick directo que referencia una variable global por índice. Los botones nativos siempre disparan click en mobile, sin excepción. Guardá los datos en un array global (_topNombres[i]) y pasá el índice al onclick.
Sesión · Marzo 2026 · venta v111 · reportes v1.18 · GAS v6.9
const TAREAS = [ // ── CRÍTICAS (P1) ── { id:1, p:1, tipo:'bug', done:true, archivo:'copihue-reportes.html', titulo:'Tap en productos del top ✓', desc:'Resuelto en v1.18 — button nativo con prod30H inline. Sin delegation, sin data-nombre.' }, { id:2, p:1, tipo:'bug', done:true, archivo:'venta.html', titulo:'Ingreso mercadería no se cierra afuera ✓', desc:'Resuelto en v90 — cerrarModalIngreso no responde al tap fuera, solo al botón ✕.' }, { id:3, p:1, tipo:'bug', done:true, archivo:'venta.html', titulo:'Precio ajuste rápido $100 en $100 ✓', desc:'Resuelto en v92 — botones +/- con touchend + stopPropagation.' }, { id:4, p:1, tipo:'bug', done:true, archivo:'venta.html', titulo:'Categoría VINOS en ingreso ✓', desc:'Resuelto en v91 — VINOS, CERVEZAS, FIAMBRES, PANADERIA agregados a CATEGORIAS.' }, { id:5, p:1, tipo:'bug', done:false, archivo:'venta.html', titulo:'Luz verde conexión en proveedores — verificar', desc:'Implementado en v94 con puntoProv. Victor no confirmó que funcione — verificar.' }, // ── ALTAS (P2) ── { id:6, p:2, tipo:'mejora', done:true, archivo:'venta.html', titulo:'ELECTRÓNICA ignorada en reposición ✓', desc:'Implementado en v93. Victor no recibe más la lista de electrónica.' }, { id:7, p:2, tipo:'feat', done:false, archivo:'venta.html', titulo:'Carrito de reposición para el super', desc:'Módulo: elegís producto → precio y vencimiento → hoja temporal. Al llegar a casa confirmás y pasa a Historial.' }, { id:8, p:2, tipo:'feat', done:false, archivo:'venta.html', titulo:'Sugerir último vencimiento al ingresar', desc:'Al cargar producto existente, sugerir el último vencimiento registrado del mismo producto.' }, { id:9, p:2, tipo:'feat', done:false, archivo:'venta.html', titulo:'Pool de ofertas activas visible en POS', desc:'Cuando se activa una oferta mostrar en venta.html el pool de productos del turno.' }, { id:10, p:2, tipo:'feat', done:false, archivo:'copihue-reportes.html', titulo:'Filtro por vendedor', desc:'Lista desplegable de vendedores para filtrar tickets y métricas individuales.' }, { id:11, p:2, tipo:'feat', done:true, archivo:'copihue-reportes.html', titulo:'Filtros de período avanzados ✓', desc:'Selector jerárquico: HOY → Calendario → S1-S6 → Meses → Años anteriores colapsables. v1.12.' }, { id:12, p:2, tipo:'mejora', done:true, archivo:'copihue-reportes.html', titulo:'Top productos: toggle venta / ganancia ✓', desc:'Botón para cambiar orden del listado entre mayor venta y mayor ganancia.' }, { id:36, p:2, tipo:'bug', done:true, archivo:'codegs.txt', titulo:'Ganancia real negativa en reportes ✓', desc:'GAS v6.9: qty histórico en gramos (ej: 800) se convertía mal multiplicando costo×800. Fix: div /1000 si nombre tiene (Ngr) y qty≥50.' }, { id:37, p:2, tipo:'feat', done:true, archivo:'copihue-reportes.html', titulo:'Tabla 30 días por producto ✓', desc:'Toggle azul al final. Tabla horizontal scrolleable: producto fijo izq, 30 columnas día con intensidad de color, total fijo der.' }, { id:38, p:2, tipo:'feat', done:true, archivo:'copihue-reportes.html', titulo:'Tap producto → expande 30 días inline ✓', desc:'v1.18: tap en top venta/ganancia expande fila con unidades por día, scroll horizontal. Button nativo, sin modal.' }, { id:39, p:2, tipo:'mejora', done:true, archivo:'venta.html', titulo:'Cache busting automático reportes ✓', desc:'venta.html v111 linkea copihue-reportes.html?v=118. Cada versión fuerza descarga fresca en celular.' }, // ── PENDIENTES ALTOS ── { id:40, p:2, tipo:'feat', done:false, archivo:'codegs.txt', titulo:'GAS v6.9 — republicar en Apps Script', desc:'Fix ganancia real (qty gramos), fix costos históricos. Pendiente republicar para que tome efecto en reportes.' }, { id:41, p:2, tipo:'feat', done:false, archivo:'venta.html', titulo:'Seguridad: repo privado + PIN acceso', desc:'GitHub repo → privado. PIN 4 dígitos en venta.html y reportes. Token secreto en GAS.' }, { id:42, p:2, tipo:'feat', done:false, archivo:'venta.html', titulo:'Sync timestamp ofertas automático', desc:'Al apagar toggle en POS → GAS escribe ultima_modificacion. index.html compara cada 2min y recarga solo ofertas si cambió.' }, { id:43, p:3, tipo:'feat', done:false, archivo:'codegs.txt', titulo:'GAS endpoint action=cliente', desc:'Hoja Clientes en Sheets: nombre|telefono|primera_compra|ultima_compra|visitas|gasto_total. upsertCliente por teléfono.' }, { id:44, p:3, tipo:'bug', done:false, archivo:'copihue-reportes.html', titulo:'Panel 30 días no renderiza números en Android', desc:'9 intentos fallidos. El fondo y título se ven, los números coloreados no. Causas descartadas: pointer-events, clases CSS, inline styles, emojis, flex, grid, button→div role=button, 3 filas separadas. Pendiente: chrome://inspect para ver DOM real en Android. Hipótesis: overflow:hidden heredado o height:0 en el contenedor.' }, // ── MEDIAS (P3) ── { id:13, p:3, tipo:'feat', done:false, archivo:'copihue-reportes.html', titulo:'Gráfico comparativo precios por proveedor', desc:'En modal de producto mostrar gráfico histórico del precio más barato por proveedor.' }, { id:14, p:3, tipo:'feat', done:false, archivo:'copihue-reportes.html', titulo:'Top productos por rotación', desc:'Ranking de productos más vendidos en unidades, separado del top por ganancia.' }, { id:15, p:3, tipo:'feat', done:false, archivo:'venta.html', titulo:'Semáforo de vencimiento en carrito', desc:'Verde = +300 días, amarillo = 8-29 días, naranja = hasta 7 días, rojo = últimos 3 días.' }, { id:16, p:3, tipo:'feat', done:false, archivo:'venta.html', titulo:'Gestión de clientes — fiado', desc:'Menú Clientes/Deudas. Hoja por cliente en Sheets creada desde venta.html.' }, { id:17, p:3, tipo:'feat', done:true, archivo:'index.html', titulo:'Compartir horario por WhatsApp ✓', desc:'Implementado en v002 — canvas con día resaltado en naranja, botón 📲 Compartir horario.' }, { id:18, p:3, tipo:'feat', done:false, archivo:'venta.html', titulo:'Notificación vendedor cuando se activa promo', desc:'Toast o badge en el POS cuando el sistema activa automáticamente una oferta.' }, // ── BAJAS (P4) ── { id:19, p:4, tipo:'feat', done:false, archivo:'codegs.txt', titulo:'FIFO automático doble vencimiento', desc:'Al cargar producto nuevo sugerir sacar lote anterior. Al vender descontar del más antiguo.' }, { id:20, p:4, tipo:'mejora', done:false, archivo:'copihue-reportes.html', titulo:'Ganancia real vs estimada más clara', desc:'Indicador visual más prominente para distinguir datos reales de estimados.' }, { id:21, p:4, tipo:'feat', done:false, archivo:'venta.html', titulo:'Historial de ajustes rápidos en POS', desc:'Panel para ver los últimos ajustes de stock/precio desde el mismo POS.' }, { id:22, p:4, tipo:'mejora', done:false, archivo:'index.html', titulo:'Imagen del producto en cards de ofertas', desc:'Mostrar foto cuando existe en imagenes-productos/, placeholder si no.' }, { id:23, p:4, tipo:'mejora', done:false, archivo:'copihue-reportes.html', titulo:'Exportar reporte del día a WhatsApp', desc:'Resumen del día compartible como cierre de caja.' }, { id:24, p:4, tipo:'idea', done:false, archivo:'venta.html', titulo:'Modo oscuro/claro en el POS', desc:'Toggle de tema para distintas condiciones de luz del local.' }, // ── IDEAS FUTURAS (P5) ── { id:25, p:5, tipo:'idea', done:false, archivo:'index.html', titulo:'Martes Dulce / Domingo Snacks', desc:'Reutilizar motor Jueves Cervecero para eventos automáticos por categoría otros días.' }, { id:26, p:5, tipo:'idea', done:false, archivo:'codegs.txt', titulo:'Sistema de fidelidad — puntos por compra', desc:'Acumular puntos por ticket. El cliente los ve y los canjea en mostrador.' }, { id:27, p:5, tipo:'idea', done:false, archivo:'index.html', titulo:'Modo pedido — el cliente arma el carrito', desc:'El cliente arma su pedido desde el celu y vos lo ves en el POS en tiempo real.' }, { id:28, p:5, tipo:'idea', done:false, archivo:'codegs.txt', titulo:'Alerta stock bajo por WhatsApp', desc:'GAS envía mensaje cuando un producto baja de X unidades.' }, { id:29, p:5, tipo:'idea', done:false, archivo:'index.html', titulo:'Landing con horarios y novedades', desc:'Página de inicio más completa con horario, novedades de la semana y acceso al catálogo.' }, // ── HISTORIAL RESUELTOS ── { id:30, p:1, tipo:'bug', done:true, archivo:'venta.html', titulo:'Botón Cobrar desaparecía con 3+ productos ✓', desc:'Resuelto en v96 — panel-total movido dentro de panel-venta con position:sticky bottom.' }, { id:31, p:2, tipo:'mejora',done:true, archivo:'venta.html', titulo:'Header colapsa automáticamente al vender ✓', desc:'Resuelto en v97.' }, { id:32, p:2, tipo:'feat', done:true, archivo:'venta.html', titulo:'Sin stock → ⚠️ abre ajuste directo ✓', desc:'Resuelto en v98.' }, { id:33, p:2, tipo:'mejora',done:true, archivo:'venta.html', titulo:'Badge versión sincronizado con title ✓', desc:'Resuelto en v98.' }, { id:34, p:2, tipo:'feat', done:true, archivo:'venta.html', titulo:'Carrito persistente con backup en planilla ✓', desc:'Resuelto en v95.' }, { id:35, p:2, tipo:'feat', done:true, archivo:'copihue-reportes.html', titulo:'Stock en top ganancia y modal producto ✓', desc:'Resuelto en v1.11.' }, ]; const PRIORIDAD_LABEL = { 1:'🔴 P1 crítica', 2:'🟠 P2 alta', 3:'🟡 P3 media', 4:'🟢 P4 baja', 5:'⚪ P5 idea' }; const SECCIONES = [ { p:1, label:'🔴 Críticas — arreglar ya' }, { p:2, label:'🟠 Altas — próxima sesión' }, { p:3, label:'🟡 Medias — esta semana' }, { p:4, label:'🟢 Bajas — cuando haya tiempo' }, { p:5, label:'⚪ Ideas futuras' }, ]; // Done se basa en el campo done:true del array — no en localStorage // Completados confirmados por Victor: // 1=tap reportes, 2=cierre modal, 3=precio $100, 4=cat vinos // 5=luz verde proveedores (implementado, pendiente confirmar visual) // 6=electrónica ignorada en reposición ✓ (apareció lista sin electrónica) // 12=toggle top venta/ganancia ✓ // 13=stock en top y modal ✓ (badge semáforo) // 17=compartir horario WhatsApp ✓ (canvas con día resaltado) // — venta.html fixes — // cobrar desaparecía con 3+ productos ✓ (panel-total dentro de panel-venta v96) // header colapsa al vender ✓ (v97) // sin stock → botón ⚠️ abre ajuste directo ✓ (v98) // versionTag sincronizado con title ✓ (v98) // carrito persistente visibilitychange + backup planilla ✓ (v95) // filtros período avanzados reportes ✓ (v1.9) let done = TAREAS.filter(t => t.done).map(t => t.id); let filtroActivo = 'todo'; function filtrar(f, btn) { filtroActivo = f; document.querySelectorAll('.filtro-btn').forEach(b => b.classList.remove('activo')); btn.classList.add('activo'); render(); } function toggleDone(id) { const t = TAREAS.find(t => t.id === id); if (t && t.done) return; // las marcadas como done fijas no se cambian if (done.includes(id)) done = done.filter(d => d !== id); else done.push(id); render(); } function render() { const board = document.getElementById('board'); let html = ''; const tareasFiltradas = filtroActivo === 'todo' ? TAREAS : TAREAS.filter(t => t.tipo === filtroActivo); let totalPend = 0, totalP1 = 0, totalP2 = 0, totalDone = 0; TAREAS.forEach(t => { if (done.includes(t.id)) totalDone++; else { totalPend++; if (t.p === 1) totalP1++; if (t.p === 2) totalP2++; } }); document.getElementById('sTodo').textContent = totalPend; document.getElementById('sP1').textContent = totalP1; document.getElementById('sP2').textContent = totalP2; document.getElementById('sDone').textContent = totalDone; SECCIONES.forEach(sec => { const items = tareasFiltradas.filter(t => t.p === sec.p); if (!items.length) return; html += `
${sec.label}
`; items.forEach(t => { const isDone = done.includes(t.id); const tipoTag = `${t.tipo === 'bug' ? '🐛 bug' : t.tipo === 'feat' ? '✨ feature' : t.tipo === 'mejora' ? '⚡ mejora' : '💡 idea'}`; const archivoTag = `${t.archivo}`; html += `
${isDone?'✓':''}
${t.titulo}
${t.desc}
${tipoTag}${archivoTag}
`; }); html += '
'; }); if (!html) html = '
Sin tareas en esta categoría
'; board.innerHTML = html; } render();
🏆
💡 Tip del hito — Marzo 2026
En mobile, nunca uses event delegation para elementos generados con innerHTML
El tap que no respondía venía de una regla del DOM mobile: cuando regenerás HTML con innerHTML= y usás document.addEventListener('click') con closest(), en Android el touch a veces no propaga como click cuando hay conflictos de pointer-events en los hijos.

✅ Solución definitiva: usá un <button> nativo con onclick directo que referencia un array global por índice. Los botones nativos siempre disparan click en mobile, sin excepción. Guardá los datos en _topNombres[i] y pasá el índice al onclick.
venta v111 reportes v1.18 GAS v6.9 ⚠️ GAS pendiente republicar