240 lines
9.6 KiB
HTML
240 lines
9.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Lista de la Compra Inteligente</title>
|
|
<link rel="stylesheet" href="css/style.css">
|
|
</head>
|
|
<body>
|
|
|
|
<h1>Lista de la Compra Inteligente</h1>
|
|
<p class="subtitle" id="subtitulo">Basada en el historial de tickets de Mercadona</p>
|
|
|
|
<div class="toolbar">
|
|
<button class="btn btn-primary btn-sm" onclick="marcarSemanal()">Marcar sugeridos esta semana</button>
|
|
<button class="btn btn-secondary btn-sm" onclick="desmarcarTodo()">Desmarcar todo</button>
|
|
</div>
|
|
|
|
<div class="layout">
|
|
|
|
<!-- Columna izquierda: predicciones por frecuencia -->
|
|
<div id="columnaProductos">
|
|
<div class="no-datos" id="sinDatos">
|
|
<p>No hay datos cargados todavia.</p>
|
|
<p>Ejecuta este comando para generar el archivo de predicciones:</p>
|
|
<code>python generar_lista.py</code>
|
|
<p>Despues recarga esta pagina.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Panel lateral -->
|
|
<div class="panel">
|
|
|
|
<h2>Lista de la compra <span class="count-badge" id="contadorSeleccionados">0</span></h2>
|
|
<textarea id="listaGenerada" placeholder="Marca productos a la izquierda..."></textarea>
|
|
<div style="margin-top:.75rem; display:flex; gap:.5rem;">
|
|
<button class="btn btn-primary" onclick="copiarLista()">Copiar</button>
|
|
<button class="btn btn-secondary" onclick="limpiarLista()">Limpiar</button>
|
|
</div>
|
|
|
|
<h2>Anadir manualmente</h2>
|
|
<div class="add-row">
|
|
<input type="text" id="nuevoProducto" placeholder="Producto..."
|
|
onkeydown="if(event.key==='Enter') agregarManual()">
|
|
<button class="btn btn-secondary btn-sm" onclick="agregarManual()">Anadir</button>
|
|
</div>
|
|
|
|
<h2>Subir ticket PDF</h2>
|
|
<div class="drop-zone" id="dropZone"
|
|
ondragover="dzOver(event)" ondragleave="dzLeave()" ondrop="dzDrop(event)">
|
|
<input type="file" id="ticketPDF" accept="application/pdf"
|
|
onchange="leerPDF(this.files[0])">
|
|
<span class="drop-zone-icon">📄</span>
|
|
Arrastra el PDF aqui o haz clic para seleccionarlo
|
|
</div>
|
|
<div id="pdfEstado" style="font-size:.8rem; color:#8b949e; margin-top:.4rem;"></div>
|
|
<pre id="pdfTexto"></pre>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script>
|
|
<script src="datos.js"></script>
|
|
<script>
|
|
// -----------------------------------------------------------------------
|
|
// Estado
|
|
// -----------------------------------------------------------------------
|
|
const seleccionados = new Set();
|
|
let productosManuales = [];
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Inicializacion
|
|
// -----------------------------------------------------------------------
|
|
window.addEventListener('load', () => {
|
|
if (typeof predicciones === 'undefined' || !predicciones.length) {
|
|
document.getElementById('sinDatos').style.display = 'block';
|
|
return;
|
|
}
|
|
document.getElementById('sinDatos').style.display = 'none';
|
|
if (typeof GENERADO !== 'undefined') {
|
|
document.getElementById('subtitulo').textContent =
|
|
'Predicciones generadas el ' + GENERADO + ' - ' + predicciones.length + ' productos';
|
|
}
|
|
renderizarPredicciones();
|
|
});
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Renderizado
|
|
// -----------------------------------------------------------------------
|
|
function renderizarPredicciones() {
|
|
const contenedor = document.getElementById('columnaProductos');
|
|
contenedor.innerHTML = '';
|
|
const grupos = [
|
|
{ titulo: 'Compra semanal', badge: 'badge-rojo', label: 'cada semana', items: predicciones.filter(p => p.frecuencia_dias <= 8) },
|
|
{ titulo: 'Compra quincenal', badge: 'badge-naranja', label: 'cada 1-2 semanas', items: predicciones.filter(p => p.frecuencia_dias > 8 && p.frecuencia_dias <= 16) },
|
|
{ titulo: 'Compra mensual', badge: 'badge-verde', label: 'cada 2-4 semanas', items: predicciones.filter(p => p.frecuencia_dias > 16 && p.frecuencia_dias <= 35) },
|
|
{ titulo: 'Compra esporadica', badge: 'badge-gris', label: 'mas de un mes', items: predicciones.filter(p => p.frecuencia_dias > 35) },
|
|
];
|
|
grupos.forEach(grupo => {
|
|
if (!grupo.items.length) return;
|
|
const card = document.createElement('div');
|
|
card.className = 'card';
|
|
card.innerHTML =
|
|
'<div class="card-header"><strong>' + grupo.titulo + '</strong>' +
|
|
'<span class="badge ' + grupo.badge + '">' + grupo.label + '</span></div>' +
|
|
'<div class="prod-list"></div>';
|
|
contenedor.appendChild(card);
|
|
const lista = card.querySelector('.prod-list');
|
|
grupo.items.forEach(prod => lista.appendChild(crearItem(prod)));
|
|
});
|
|
}
|
|
|
|
function crearItem(prod) {
|
|
const li = document.createElement('div');
|
|
li.className = 'prod-item';
|
|
li.dataset.nombre = prod.producto;
|
|
|
|
const chk = document.createElement('input');
|
|
chk.type = 'checkbox';
|
|
chk.checked = seleccionados.has(prod.producto);
|
|
chk.addEventListener('change', () => toggleSeleccionado(prod.producto, chk.checked, span));
|
|
|
|
const span = document.createElement('span');
|
|
span.className = 'prod-nombre' + (chk.checked ? ' tachado' : '');
|
|
span.textContent = prod.producto;
|
|
|
|
const freq = document.createElement('span');
|
|
freq.className = 'prod-freq';
|
|
freq.textContent = '~' + prod.frecuencia_dias + 'd';
|
|
freq.title = 'Estimado: ' + prod.fecha_estimada;
|
|
|
|
li.appendChild(chk);
|
|
li.appendChild(span);
|
|
li.appendChild(freq);
|
|
li.addEventListener('click', e => {
|
|
if (e.target !== chk) { chk.checked = !chk.checked; toggleSeleccionado(prod.producto, chk.checked, span); }
|
|
});
|
|
return li;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Seleccion
|
|
// -----------------------------------------------------------------------
|
|
function toggleSeleccionado(nombre, checked, span) {
|
|
if (checked) { seleccionados.add(nombre); if (span) span.classList.add('tachado'); }
|
|
else { seleccionados.delete(nombre); if (span) span.classList.remove('tachado'); }
|
|
actualizarLista();
|
|
}
|
|
|
|
function marcarSemanal() {
|
|
if (typeof predicciones === 'undefined') return;
|
|
predicciones.filter(p => p.frecuencia_dias <= 8).forEach(p => seleccionados.add(p.producto));
|
|
refrescarCheckboxes();
|
|
actualizarLista();
|
|
}
|
|
|
|
function desmarcarTodo() {
|
|
seleccionados.clear();
|
|
productosManuales = [];
|
|
refrescarCheckboxes();
|
|
actualizarLista();
|
|
}
|
|
|
|
function refrescarCheckboxes() {
|
|
document.querySelectorAll('.prod-item').forEach(li => {
|
|
const chk = li.querySelector('input[type="checkbox"]');
|
|
const span = li.querySelector('.prod-nombre');
|
|
const activo = seleccionados.has(li.dataset.nombre);
|
|
chk.checked = activo;
|
|
span.classList.toggle('tachado', activo);
|
|
});
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Lista
|
|
// -----------------------------------------------------------------------
|
|
function actualizarLista() {
|
|
const todos = [...seleccionados, ...productosManuales];
|
|
document.getElementById('listaGenerada').value = todos.join('\n');
|
|
document.getElementById('contadorSeleccionados').textContent = todos.length;
|
|
}
|
|
|
|
function copiarLista() {
|
|
const txt = document.getElementById('listaGenerada').value;
|
|
if (!txt) return;
|
|
navigator.clipboard.writeText(txt)
|
|
.then(() => alert('Lista copiada al portapapeles'))
|
|
.catch(() => { const ta = document.getElementById('listaGenerada'); ta.select(); document.execCommand('copy'); });
|
|
}
|
|
|
|
function limpiarLista() {
|
|
seleccionados.clear();
|
|
productosManuales = [];
|
|
refrescarCheckboxes();
|
|
actualizarLista();
|
|
}
|
|
|
|
function agregarManual() {
|
|
const input = document.getElementById('nuevoProducto');
|
|
const nombre = input.value.trim();
|
|
if (!nombre) return;
|
|
productosManuales.push(nombre);
|
|
input.value = '';
|
|
actualizarLista();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Drag & Drop + lectura PDF
|
|
// -----------------------------------------------------------------------
|
|
function dzOver(e) { e.preventDefault(); document.getElementById('dropZone').classList.add('drag-over'); }
|
|
function dzLeave() { document.getElementById('dropZone').classList.remove('drag-over'); }
|
|
function dzDrop(e) {
|
|
e.preventDefault();
|
|
dzLeave();
|
|
const file = e.dataTransfer.files[0];
|
|
if (file && file.type === 'application/pdf') leerPDF(file);
|
|
}
|
|
|
|
async function leerPDF(file) {
|
|
if (!file) return;
|
|
const estado = document.getElementById('pdfEstado');
|
|
estado.textContent = 'Leyendo ' + file.name + '...';
|
|
|
|
const pdfData = await file.arrayBuffer();
|
|
const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
|
let texto = '';
|
|
for (let i = 1; i <= pdf.numPages; i++) {
|
|
const page = await pdf.getPage(i);
|
|
const content = await page.getTextContent();
|
|
texto += content.items.map(item => item.str).join(' ') + '\n';
|
|
}
|
|
|
|
estado.textContent = file.name + ' - ' + pdf.numPages + ' pagina(s)';
|
|
const salida = document.getElementById('pdfTexto');
|
|
salida.style.display = 'block';
|
|
salida.textContent = texto.trim();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |