diff --git a/app.py b/app.py
index 44bc956..3152332 100644
--- a/app.py
+++ b/app.py
@@ -189,6 +189,27 @@ def scan():
def serve_cache(filename):
return send_from_directory(CACHE_DIR, filename)
+@app.route('/bulk_price_update', methods=['POST'])
+@login_required
+def bulk_price_update():
+ data = request.get_json()
+ barcodes = data.get('barcodes', [])
+ new_price = data.get('new_price')
+
+ if not barcodes or new_price is None:
+ return jsonify({"error": "Missing data"}), 400
+
+ try:
+ with sqlite3.connect(DB_FILE) as conn:
+ # Use executemany for efficiency
+ params = [(float(new_price), b) for b in barcodes]
+ conn.executemany('UPDATE products SET price = ? WHERE barcode = ?', params)
+ conn.commit()
+ return jsonify({"status": "success"}), 200
+ except Exception as e:
+ print(f"Bulk update failed: {e}")
+ return jsonify({"error": str(e)}), 500
+
if __name__ == '__main__':
init_db()
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
diff --git a/static/styleIndex.css b/static/styleIndex.css
index 94031bd..a758576 100644
--- a/static/styleIndex.css
+++ b/static/styleIndex.css
@@ -157,26 +157,80 @@ th {
font-weight: bold;
}
-#new-product-prompt {
- display: none;
+/* --- BULK ACTIONS PERMANENT BAR --- */
+.bulk-actions {
+ display: flex;
background: var(--accent);
color: white;
- padding: 12px 20px;
+ padding: 10px 20px;
border-radius: 8px;
- margin-bottom: 20px;
- animation: slideDown 0.4s ease;
+ margin-bottom: 15px;
justify-content: space-between;
align-items: center;
+ min-height: 60px;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.bulk-controls {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-shrink: 0;
+}
+
+/* Override the global 100% width for this specific input */
+.bulk-actions input#bulk-price-input {
+ width: 130px !important;
+ margin: 0 !important;
+ padding: 8px !important;
+ height: 36px !important;
+ background: rgba(0,0,0,0.2) !important;
+ border: 1px solid rgba(255,255,255,0.3) !important;
+ color: white !important;
+}
+
+.bulk-actions button {
+ height: 36px;
+ white-space: nowrap;
+ padding: 0 15px;
+ margin: 0;
+}
+
+/* --- RESPONSIVE DESIGN (MOBILE) --- */
+@media (max-width: 900px) {
+ .main-container {
+ flex-direction: column;
+ }
+
+ .column {
+ min-width: 100%;
+ }
+
+ .bulk-actions {
+ flex-direction: column;
+ height: auto;
+ padding: 15px;
+ gap: 10px;
+ text-align: center;
+ }
+
+ .bulk-controls {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .bulk-actions input#bulk-price-input {
+ width: 100% !important; /* On mobile, let it be wide */
+ }
+
+ /* Hide barcode on very small screens to save space */
+ td:nth-child(2), th:nth-child(2) {
+ display: none;
+ }
}
@keyframes slideDown {
- from {
- transform: translateY(-20px);
- opacity: 0;
- }
-
- to {
- transform: translateY(0);
- opacity: 1;
- }
-}
\ No newline at end of file
+ from { transform: translateY(-20px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+}
diff --git a/templates/index.html b/templates/index.html
index e30d11a..4676b91 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -56,35 +56,48 @@
Inventario
-
-
-
-
-
- | Código |
- Nombre |
- Precio |
- Acciones |
-
-
-
- {% for p in products %}
-
- | {{ p[0] }} |
- {{ p[1] }} |
- |
-
-
-
- |
-
- {% endfor %}
-
-
+
+
+
+
0 seleccionados
+
+
+
+
+
+
+
+
@@ -194,6 +207,51 @@
tr[i].style.display = content.toUpperCase().indexOf(filter) > -1 ? "" : "none";
}
}
+
+ function toggleAll(source) {
+ const checkboxes = document.querySelectorAll('.product-checkbox');
+ checkboxes.forEach(cb => cb.checked = source.checked);
+ updateBulkBar();
+ }
+
+ function updateBulkBar() {
+ const checked = document.querySelectorAll('.product-checkbox:checked');
+ document.getElementById('selected-count').innerText = checked.length;
+
+ const okBtn = document.querySelector('.bulk-controls button[onclick="applyBulkPrice()"]');
+ if (okBtn) {
+ okBtn.disabled = checked.length === 0;
+ okBtn.style.opacity = checked.length === 0 ? "0.5" : "1";
+ }
+ }
+
+ function clearSelection() {
+ document.querySelectorAll('.product-checkbox').forEach(cb => cb.checked = false);
+ document.getElementById('select-all').checked = false;
+ updateBulkBar();
+ }
+
+ async function applyBulkPrice() {
+ const price = document.getElementById('bulk-price-input').value;
+ if (!price || price < 0) return alert("Ingresa un precio válido");
+
+ const barcodes = Array.from(document.querySelectorAll('.product-checkbox:checked'))
+ .map(cb => cb.getAttribute('data-barcode'));
+
+ if (!confirm(`¿Actualizar el precio de ${barcodes.length} productos a ${clpFormatter.format(price)}?`)) return;
+
+ const response = await fetch('/bulk_price_update', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ barcodes: barcodes, new_price: price })
+ });
+
+ if (response.ok) {
+ window.location.reload();
+ } else {
+ alert("Error al actualizar productos.");
+ }
+ }