diff --git a/src/index.html b/src/index.html index 59e2f53..4701760 100644 --- a/src/index.html +++ b/src/index.html @@ -89,7 +89,6 @@ - \ No newline at end of file diff --git a/src/page/cajas.js b/src/page/cajas.js deleted file mode 100644 index 4470c6d..0000000 --- a/src/page/cajas.js +++ /dev/null @@ -1,760 +0,0 @@ -PERMS['cajas'] = 'Cajas'; -PERMS['cajas:edit'] = '> Editar'; -PAGES.cajas = { - navcss: 'btn8', - icon: 'static/appico/credit_cards.png', - AccessControl: true, - Title: 'Cajas', - - // View/edit a specific transaction (movimiento) - movimiento: function (cajaId, movimientoId) { - if (!checkRole('cajas')) { - setUrlHash('cajas'); - return; - } - - var field_fecha = safeuuid(); - var field_tipo = safeuuid(); - var field_monto = safeuuid(); - var field_persona = safeuuid(); - var field_notas = safeuuid(); - var field_foto = safeuuid(); - var render_foto = safeuuid(); - var btn_volver = safeuuid(); - var btn_borrar = safeuuid(); - - container.innerHTML = html` -

Movimiento de Caja

-
- - - - - - -
- - -
- `; - - // Load transaction data - DB.get('cajas_movimientos', movimientoId).then((data) => { - function load_data(data) { - if (!data) return; - - // Format datetime for datetime-local input - var fechaValue = data['Fecha'] || ''; - if (fechaValue) { - // Convert ISO string to datetime-local format (YYYY-MM-DDTHH:mm) - fechaValue = fechaValue.substring(0, 16); - } - - document.getElementById(field_fecha).value = fechaValue; - document.getElementById(field_tipo).value = data['Tipo'] || ''; - document.getElementById(field_monto).value = data['Monto'] || 0; - - // Get persona name - var personaId = data['Persona'] || ''; - var personaName = personaId; - if (SC_Personas[personaId]) { - personaName = SC_Personas[personaId].Nombre || personaId; - } - document.getElementById(field_persona).value = personaName; - document.getElementById(field_notas).value = data['Notas'] || ''; - - // Load photo attachment if present - DB.getAttachment('cajas_movimientos', movimientoId, 'foto') - .then((durl) => { - if (durl) { - document.getElementById(render_foto).src = durl; - } else { - document.getElementById(render_foto).style.display = 'none'; - } - }) - .catch(() => { - document.getElementById(render_foto).style.display = 'none'; - }); - } - - if (typeof data === 'string') { - TS_decrypt( - data, - SECRET, - (data, wasEncrypted) => { - load_data(data); - }, - 'cajas_movimientos', - movimientoId - ); - } else { - load_data(data || {}); - } - }); - - document.getElementById(btn_volver).onclick = () => { - setUrlHash('cajas,' + cajaId); - }; - - // Show delete button only if user has edit permission - if (checkRole('cajas:edit')) { - document.getElementById(btn_borrar).style.display = 'inline-block'; - document.getElementById(btn_borrar).onclick = () => { - if (confirm('¿Quieres borrar este movimiento?')) { - DB.del('cajas_movimientos', movimientoId).then(() => { - toastr.success('Movimiento borrado!'); - setTimeout(() => { - setUrlHash('cajas,' + cajaId); - }, SAVE_WAIT); - }); - } - }; - } - }, - - // Create new transaction (movimiento) - nuevo_movimiento: function (cajaId) { - if (!checkRole('cajas:edit')) { - setUrlHash('cajas,' + cajaId); - return; - } - - var field_fecha = safeuuid(); - var field_tipo = safeuuid(); - var field_monto = safeuuid(); - var field_persona = safeuuid(); - var field_notas = safeuuid(); - var field_foto = safeuuid(); - var render_foto = safeuuid(); - var field_caja_destino = safeuuid(); - var div_caja_destino = safeuuid(); - var btn_guardar = safeuuid(); - var btn_cancelar = safeuuid(); - - var resized = ''; - - container.innerHTML = html` -

Nuevo Movimiento

-
- - - - - - - -
- - -
- `; - - // Set current datetime - var now = new Date(); - var tzOffset = now.getTimezoneOffset() * 60000; - var localISOTime = new Date(now - tzOffset).toISOString().slice(0, 16); - document.getElementById(field_fecha).value = localISOTime; - - // Load personas for selection - var selectedPersona = ''; - var container_personas = document.querySelector('#personaSelector'); - addCategory_Personas( - container_personas, - SC_Personas, - selectedPersona, - (personaId) => { - document.getElementById(field_persona).value = personaId; - selectedPersona = personaId; - }, - 'Persona', - false, - '- No hay personas registradas -' - ); - - // Load cajas for destination selection - DB.map('cajas', (data, key) => { - function addCajaOption(cajaData, cajaKey) { - if (cajaKey === cajaId) return; // Don't show current caja - var select = document.getElementById(field_caja_destino); - if (!select) return; - var option = document.createElement('option'); - option.value = cajaKey; - option.textContent = cajaData.Nombre || cajaKey; - select.appendChild(option); - } - - if (typeof data === 'string') { - TS_decrypt( - data, - SECRET, - (cajaData, wasEncrypted) => { - addCajaOption(cajaData, key); - }, - 'cajas', - key - ); - } else { - addCajaOption(data, key); - } - }); - - // Show/hide destination caja based on transaction type - document.getElementById(field_tipo).addEventListener('change', function () { - var tipo = this.value; - var divDestino = document.getElementById(div_caja_destino); - if (tipo === 'Transferencia') { - divDestino.style.display = 'block'; - } else { - divDestino.style.display = 'none'; - } - }); - - // Photo upload handler (click image to upload) - document.getElementById(render_foto).onclick = () => { - document.getElementById(field_foto).click(); - }; - - document.getElementById(field_foto).addEventListener('change', function (e) { - const file = e.target.files[0]; - if (!file) return; - - const reader = new FileReader(); - reader.onload = function (ev) { - const url = ev.target.result; - document.getElementById(render_foto).src = url; - resized = url; - }; - reader.readAsDataURL(file); - }); - - document.getElementById(btn_guardar).onclick = () => { - var guardarBtn = document.getElementById(btn_guardar); - if (guardarBtn.disabled) return; - - var tipo = document.getElementById(field_tipo).value; - var monto = parseFloat(document.getElementById(field_monto).value); - var personaId = document.getElementById(field_persona).value; - var fecha = document.getElementById(field_fecha).value; - var notas = document.getElementById(field_notas).value; - var cajaDestinoId = document.getElementById(field_caja_destino).value; - - // Validation - if (!tipo) { - alert('Por favor selecciona el tipo de movimiento'); - return; - } - if (!monto || monto <= 0) { - alert('Por favor ingresa un monto válido'); - return; - } - if (!personaId) { - alert('Por favor selecciona una persona'); - return; - } - if (!fecha) { - alert('Por favor selecciona una fecha'); - return; - } - - // Validate destination caja for transfers - if (tipo === 'Transferencia') { - if (!cajaDestinoId) { - alert('Por favor selecciona la caja destino para la transferencia'); - return; - } - if (cajaDestinoId === cajaId) { - alert('No puedes transferir a la misma caja'); - return; - } - } - - // Validate photo for expenses - if (tipo === 'Gasto' && !resized) { - alert('La foto del ticket es obligatoria para gastos'); - return; - } - - guardarBtn.disabled = true; - guardarBtn.style.opacity = '0.5'; - - var movimientoId = safeuuid(''); - var fechaISO = new Date(fecha).toISOString(); - - var data = { - Caja: cajaId, - Fecha: fechaISO, - Tipo: tipo, - Monto: monto, - Persona: personaId, - Notas: notas, - }; - - // Add destination caja for transfers - if (tipo === 'Transferencia') { - data.CajaDestino = cajaDestinoId; - } - - document.getElementById('actionStatus').style.display = 'block'; - DB.put('cajas_movimientos', movimientoId, data) - .then(() => { - // Save photo attachment if present - var attachPromise = Promise.resolve(true); - if (resized && resized.indexOf('data:') === 0) { - attachPromise = DB.putAttachment( - 'cajas_movimientos', - movimientoId, - 'foto', - resized, - 'image/png' - ); - } - - attachPromise - .then(() => { - // Update source caja balance - return updateCajaBalance(cajaId, tipo, monto); - }) - .then(() => { - // If transfer, update destination caja balance - if (tipo === 'Transferencia' && cajaDestinoId) { - return updateCajaBalance(cajaDestinoId, 'Ingreso', monto); - } - return Promise.resolve(); - }) - .then(() => { - toastr.success('Movimiento guardado!'); - setTimeout(() => { - document.getElementById('actionStatus').style.display = 'none'; - setUrlHash('cajas,' + cajaId); - }, SAVE_WAIT); - }) - .catch((e) => { - console.warn('Error saving:', e); - document.getElementById('actionStatus').style.display = 'none'; - guardarBtn.disabled = false; - guardarBtn.style.opacity = '1'; - toastr.error('Error al guardar el movimiento'); - }); - }) - .catch((e) => { - console.warn('DB.put error', e); - document.getElementById('actionStatus').style.display = 'none'; - guardarBtn.disabled = false; - guardarBtn.style.opacity = '1'; - toastr.error('Error al guardar el movimiento'); - }); - }; - - document.getElementById(btn_cancelar).onclick = () => { - setUrlHash('cajas,' + cajaId); - }; - - function updateCajaBalance(cajaId, tipo, monto) { - return DB.get('cajas', cajaId).then((caja) => { - function updateBalance(cajaData) { - var currentBalance = parseFloat(cajaData.Balance || 0); - var newBalance = currentBalance; - - if (tipo === 'Ingreso') { - newBalance = currentBalance + monto; - } else if (tipo === 'Gasto' || tipo === 'Transferencia') { - // For transfers, this updates the source caja (deduct amount) - newBalance = currentBalance - monto; - } - - cajaData.Balance = fixfloat(newBalance); - return DB.put('cajas', cajaId, cajaData); - } - - if (typeof caja === 'string') { - return new Promise((resolve, reject) => { - TS_decrypt( - caja, - SECRET, - (cajaData, wasEncrypted) => { - updateBalance(cajaData).then(resolve).catch(reject); - }, - 'cajas', - cajaId - ); - }); - } else { - return updateBalance(caja || {}); - } - }); - } - }, - - // View/edit a cash register (caja) - edit: function (mid) { - if (!checkRole('cajas')) { - setUrlHash('cajas'); - return; - } - - // Check for special routes - var parts = location.hash.split(','); - if (parts[1] === 'nuevo_movimiento' && parts[2]) { - PAGES.cajas.nuevo_movimiento(parts[2]); - return; - } - if (parts[1] === 'movimiento' && parts[2] && parts[3]) { - PAGES.cajas.movimiento(parts[2], parts[3]); - return; - } - - var nameh1 = safeuuid(); - var field_nombre = safeuuid(); - var field_balance = safeuuid(); - var field_notas = safeuuid(); - var btn_guardar = safeuuid(); - var btn_borrar = safeuuid(); - var btn_nuevo_movimiento = safeuuid(); - var movimientos_container = safeuuid(); - - var isMonederos = mid === 'monederos'; - - container.innerHTML = html` -

${isMonederos ? 'Monederos' : 'Caja'}

- ${isMonederos ? '' : BuildQR('cajas,' + mid, 'Esta Caja')} -
- - - -
- ${ - isMonederos - ? '' - : html` - - - ` - } -
- -

Movimientos de ${isMonederos ? 'Monederos' : 'esta Caja'}

- ${ - isMonederos - ? html`

Aquí se muestran todas las transacciones de los monederos (módulo Pagos)

` - : html`` - } -
- `; - - // Load caja data - if (!isMonederos) { - DB.get('cajas', mid).then((data) => { - function load_data(data) { - document.getElementById(nameh1).innerText = mid; - document.getElementById(field_nombre).value = data['Nombre'] || ''; - document.getElementById(field_balance).value = data['Balance'] || 0; - document.getElementById(field_notas).value = data['Notas'] || ''; - } - - if (typeof data === 'string') { - TS_decrypt( - data, - SECRET, - (data, wasEncrypted) => { - load_data(data); - }, - 'cajas', - mid - ); - } else { - load_data(data || {}); - } - }); - - document.getElementById(btn_guardar).onclick = () => { - var guardarBtn = document.getElementById(btn_guardar); - if (guardarBtn.disabled) return; - - guardarBtn.disabled = true; - guardarBtn.style.opacity = '0.5'; - - var data = { - Nombre: document.getElementById(field_nombre).value, - Balance: parseFloat(document.getElementById(field_balance).value) || 0, - Notas: document.getElementById(field_notas).value, - }; - - document.getElementById('actionStatus').style.display = 'block'; - DB.put('cajas', mid, data) - .then(() => { - toastr.success('Guardado!'); - setTimeout(() => { - document.getElementById('actionStatus').style.display = 'none'; - setUrlHash('cajas'); - }, SAVE_WAIT); - }) - .catch((e) => { - console.warn('DB.put error', e); - guardarBtn.disabled = false; - guardarBtn.style.opacity = '1'; - document.getElementById('actionStatus').style.display = 'none'; - toastr.error('Error al guardar la caja'); - }); - }; - - document.getElementById(btn_borrar).onclick = () => { - if (confirm('¿Quieres borrar esta caja? Los movimientos no se borrarán.')) { - DB.del('cajas', mid).then(() => { - toastr.error('Caja borrada!'); - setTimeout(() => { - setUrlHash('cajas'); - }, SAVE_WAIT); - }); - } - }; - - document.getElementById(btn_nuevo_movimiento).onclick = () => { - setUrlHash('cajas,nuevo_movimiento,' + mid); - }; - } else { - // Monederos - show aggregated wallet data - document.getElementById(nameh1).innerText = 'Monederos'; - document.getElementById(field_nombre).value = 'Monederos (Tarjetas)'; - - // Calculate total balance from all personas - var totalBalance = 0; - Object.values(SC_Personas).forEach((persona) => { - totalBalance += parseFloat(persona.Monedero_Balance || 0); - }); - document.getElementById(field_balance).value = fixfloat(totalBalance); - document.getElementById(field_notas).value = 'Movimientos de todos los monederos del sistema'; - } - - // Load movements for this caja (or all pagos for monederos) - if (isMonederos) { - // Show pagos transactions - const config = [ - { - key: 'Fecha', - label: 'Fecha', - type: 'template', - template: (data, element) => { - var fecha = data.Fecha || ''; - if (fecha) { - var d = new Date(fecha); - element.innerText = d.toLocaleDateString() + ' ' + d.toLocaleTimeString(); - } - }, - default: '', - }, - { key: 'Tipo', label: 'Tipo', type: 'text', default: '' }, - { - key: 'Monto', - label: 'Monto', - type: 'template', - template: (data, element) => { - var tipo = data.Tipo || ''; - var monto = parseFloat(data.Monto || 0); - var sign = tipo === 'Ingreso' ? '+' : '-'; - var color = tipo === 'Ingreso' ? 'green' : 'red'; - element.innerHTML = html`${sign}${monto.toFixed(2)}€`; - }, - default: '0.00€', - }, - { - key: 'Persona', - label: 'Monedero', - type: 'persona-nombre', - default: '', - }, - { key: 'Metodo', label: 'Método', type: 'text', default: '' }, - { key: 'Notas', label: 'Notas', type: 'text', default: '' }, - ]; - - TS_IndexElement( - 'pagos', - config, - 'pagos', - document.getElementById(movimientos_container), - function (data, new_tr) { - new_tr.onclick = () => { - setUrlHash('pagos,' + data._key); - }; - }, - undefined, - true - ); - } else { - // Show cajas_movimientos for this specific caja - const config = [ - { - key: 'Fecha', - label: 'Fecha', - type: 'template', - template: (data, element) => { - var fecha = data.Fecha || ''; - if (fecha) { - var d = new Date(fecha); - element.innerText = d.toLocaleDateString() + ' ' + d.toLocaleTimeString(); - } - }, - default: '', - }, - { key: 'Tipo', label: 'Tipo', type: 'text', default: '' }, - { - key: 'Monto', - label: 'Monto', - type: 'template', - template: (data, element) => { - var tipo = data.Tipo || ''; - var monto = parseFloat(data.Monto || 0); - var sign = tipo === 'Ingreso' ? '+' : tipo === 'Gasto' ? '-' : '↔'; - var color = tipo === 'Ingreso' ? 'green' : tipo === 'Gasto' ? 'red' : 'blue'; - element.innerHTML = html`${sign}${monto.toFixed(2)}€`; - }, - default: '0.00€', - }, - { - key: 'Persona', - label: 'Persona', - type: 'persona-nombre', - default: '', - }, - { key: 'Notas', label: 'Notas', type: 'text', default: '' }, - ]; - - TS_IndexElement( - 'cajas_movimientos', - config, - 'cajas_movimientos', - document.getElementById(movimientos_container), - function (data, new_tr) { - new_tr.onclick = () => { - setUrlHash('cajas,movimiento,' + mid + ',' + data._key); - }; - }, - function (data) { - // Filter: only show movements for this caja (return true to HIDE the row) - return data.Caja !== mid; - }, - true - ); - } - }, - - // List all cash registers - index: function () { - if (!checkRole('cajas')) { - setUrlHash('index'); - return; - } - - var btn_new = safeuuid(); - var btn_monederos = safeuuid(); - var tableContainer = safeuuid(); - - container.innerHTML = html` -

Cajas

- - -
- `; - - const config = [ - { key: 'Nombre', label: 'Nombre', type: 'text', default: '' }, - { - key: 'Balance', - label: 'Balance', - type: 'template', - template: (data, element) => { - var balance = parseFloat(data.Balance || 0); - var color = balance >= 0 ? 'green' : 'red'; - element.innerHTML = html`${balance.toFixed(2)}€`; - }, - default: '0.00€', - }, - { key: 'Notas', label: 'Notas', type: 'text', default: '' }, - ]; - - TS_IndexElement( - 'cajas', - config, - 'cajas', - document.getElementById(tableContainer), - undefined, - undefined, - true - ); - - document.getElementById(btn_monederos).onclick = () => { - setUrlHash('cajas,monederos'); - }; - - if (!checkRole('cajas:edit')) { - document.getElementById(btn_new).style.display = 'none'; - } else { - document.getElementById(btn_new).onclick = () => { - setUrlHash('cajas,' + safeuuid('')); - }; - } - }, -};