Files
TeleSec/src/page/pagos.js
Naiel f0e32b4ad0 fix
2025-11-26 11:57:25 +01:00

1365 lines
51 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
PERMS["pagos"] = "Pagos";
PERMS["pagos:edit"] = "> Editar";
PAGES.pagos = {
navcss: "btn7",
icon: "static/appico/Database.svg",
AccessControl: true,
Title: "Pagos",
// Datafono view for creating/processing transactions
datafono: function (prefilledData = {}) {
if (!checkRole("pagos:edit")) {
setUrlHash("pagos");
return;
}
// Check for prefilled data from SuperCafé
var prefilledStr = sessionStorage.getItem('pagos_prefill');
if (prefilledStr) {
try {
var prefilled = JSON.parse(prefilledStr);
prefilledData = { ...prefilled, ...prefilledData };
sessionStorage.removeItem('pagos_prefill');
} catch (e) {
console.error("Error parsing prefilled data:", e);
}
}
// Check for scanned persona from QR scanner
var scannedPersona = sessionStorage.getItem('pagos_scanned_persona');
if (scannedPersona) {
prefilledData.persona = scannedPersona;
sessionStorage.removeItem('pagos_scanned_persona');
}
var field_tipo = safeuuid();
var field_monto = safeuuid();
var field_persona = safeuuid();
var field_persona_destino = safeuuid();
var field_metodo = safeuuid();
var field_notas = safeuuid();
var numpad_display = safeuuid();
var div_persona_destino = safeuuid();
var btn_confirm = safeuuid();
var btn_back = safeuuid();
var btn_cancel = safeuuid();
var scan_qr_btn = safeuuid();
var currentStep = 1;
var selectedPersona = prefilledData.persona || "";
var selectedPersonaDestino = "";
container.innerHTML = `
<div style="max-width: 600px; margin: 0 auto; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.3);">
<h1 style="color: white; text-align: center; margin-bottom: 20px;">
Terminal de pago TeleSec
</h1>
<div style="margin: 0 auto;max-width: 435px;background: white;padding: 20px;border-radius: 10px;margin-bottom: 20px;">
<h2 style="text-align: center; color: #333; margin-top: 0;">Paso <span id="stepIndicator">1</span> de 3</h2>
<!-- Step 1: Retailer Input -->
<div id="step1">
<fieldset style="border: 2px solid #667eea; border-radius: 8px; padding: 15px;">
<legend style="color: #667eea; font-weight: bold;">1. Información de Transacción</legend>
<label style="display: block; margin-bottom: 15px;">
<b>Tipo de Transacción:</b><br>
<select id="${field_tipo}" style="background: #fff; width: 100%; padding: 10px; font-size: 16px; border: 2px solid #ddd; border-radius: 5px;">
<option value="">-- operación --</option>
<option value="Ingreso"> Ingreso (Depósito)</option>
<option value="Gasto"> Gasto (Retiro/Pago)</option>
<option value="Transferencia">🔄 Transferencia</option>
</select>
</label>
<label style="display: block; margin-bottom: 15px;">
<b>Método de Pago:</b><br>
<select id="${field_metodo}" style="background: #fff; width: 100%; padding: 10px; font-size: 16px; border: 2px solid #ddd; border-radius: 5px;">
<option value="">-- método --</option>
<option value="Efectivo">💵 Efectivo</option>
<option value="Tarjeta">💳 Tarjeta Monedero</option>
<option value="Otro">❓ Otro</option>
</select>
</label>
<label style="display: block; margin-bottom: 15px;">
<b>Monto:</b><br>
<input type="number" id="${numpad_display}" step="0.01" min="0"
style="width: calc(100% - 24px); padding: 15px; font-size: 32px; text-align: right;
border: 3px solid #667eea; border-radius: 5px; font-weight: bold;"
value="0.00">
</label>
<label style="display: block; margin-bottom: 15px;">
<b>Notas:</b><br>
<textarea id="${field_notas}" rows="3"
style="width: calc(100% - 24px); padding: 10px; font-size: 14px; border: 2px solid #ddd; border-radius: 5px;"
placeholder="Notas adicionales..."></textarea>
</label>
</fieldset>
</div>
<!-- Step 2: Monedero Selection -->
<div id="step2" style="display: none;">
<fieldset style="border: 2px solid #667eea; border-radius: 8px; padding: 15px;">
<legend style="color: #667eea; font-weight: bold;">2. Seleccionar Monedero</legend>
<div style="text-align: center; margin-bottom: 20px;">
<h3 style="color: #333;">Monto:</h3>
<div style="font-size: 48px; font-weight: bold; color: #667eea; margin: 10px 0;" id="step2Amount">0.00€</div>
</div>
<div style="margin-bottom: 15px;">
<b>Selecciona el Monedero:</b>
<input type="hidden" id="${field_persona}">
<div id="personaSelector"></div>
<button id="${scan_qr_btn}" style="width: 100%; padding: 15px; margin-top: 10px;
background: #667eea; color: white; border: none; border-radius: 8px;
font-size: 18px; cursor: pointer; font-weight: bold;">
📷 Escanear QR de Monedero
</button>
</div>
<div id="${div_persona_destino}" style="display: none; margin-top: 15px;">
<b>Monedero Destino:</b>
<input type="hidden" id="${field_persona_destino}">
<div id="personaDestinoSelector"></div>
</div>
</fieldset>
</div>
<!-- Step 3: Client Confirmation -->
<div id="step3" style="display: none;">
<fieldset style="border: 2px solid #667eea; border-radius: 8px; padding: 15px;">
<legend style="color: #667eea; font-weight: bold;">3. Confirmación del Cliente</legend>
<div style="text-align: center; margin-bottom: 20px;">
<h3 style="color: #333;">Confirmar Transacción</h3>
<div style="font-size: 48px; font-weight: bold; color: #667eea; margin: 10px 0;" id="confirmAmount">0.00€</div>
</div>
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
<div style="margin-bottom: 10px;">
<b>Tipo:</b> <span id="confirmTipo"></span>
</div>
<div style="margin-bottom: 10px;">
<b>Método:</b> <span id="confirmMetodo"></span>
</div>
<div style="margin-bottom: 10px;">
<b>Monedero:</b> <span id="confirmPersona"></span>
</div>
<div id="confirmPersonaDestino" style="margin-bottom: 10px; display: none;">
<b>Monedero Destino:</b> <span id="confirmPersonaDestinoName"></span>
</div>
<div style="margin-bottom: 10px;">
<b>Notas:</b> <span id="confirmNotas"></span>
</div>
</div>
<div style="text-align: center; padding: 20px; background: #fff3cd; border-radius: 8px; border: 2px solid #ffc107;">
<p style="margin: 0; font-size: 18px; font-weight: bold; color: #856404;">
⚠️ Cliente: Verifica los datos antes de confirmar
</p>
</div>
</fieldset>
</div>
</div>
<!-- Action Buttons -->
<div id="buttonContainer" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
<button id="${btn_cancel}" style="padding: 20px; font-size: 18px; font-weight: bold;
background: #ff4757; color: white; border: none; border-radius: 8px; cursor: pointer;">
✖️ CANCELAR
</button>
<button id="${btn_confirm}" style="padding: 20px; font-size: 18px; font-weight: bold;
background: #2ed573; color: white; border: none; border-radius: 8px; cursor: pointer;">
→ SIGUIENTE
</button>
</div>
<div id="buttonContainerFinal" style="display: none; grid-template-columns: repeat(2, 1fr); gap: 10px;">
<button id="${btn_back}" style="padding: 20px; font-size: 18px; font-weight: bold;
background: #ffa502; color: white; border: none; border-radius: 8px; cursor: pointer;">
← ATRÁS
</button>
<button id="${btn_confirm}2" style="padding: 20px; font-size: 18px; font-weight: bold;
background: #2ed573; color: white; border: none; border-radius: 8px; cursor: pointer;">
✔️ CONFIRMAR
</button>
</div>
</div>
`;
// Tipo change handler
document.getElementById(field_tipo).addEventListener('change', function() {
var tipo = this.value;
var divDestino = document.getElementById(div_persona_destino);
var metodoSelect = document.getElementById(field_metodo);
if (tipo === 'Transferencia') {
divDestino.style.display = 'block';
} else {
divDestino.style.display = 'none';
}
// Restrict Ingreso to Efectivo only
if (tipo === 'Ingreso') {
metodoSelect.value = 'Efectivo';
metodoSelect.disabled = true;
} else {
metodoSelect.disabled = false;
}
});
// Confirm/Next button
document.getElementById(btn_confirm).onclick = () => {
if (currentStep === 1) {
// Validate step 1
var tipo = document.getElementById(field_tipo).value;
var metodo = document.getElementById(field_metodo).value;
var monto = parseFloat(document.getElementById(numpad_display).value);
if (!tipo) {
alert("Por favor selecciona el tipo de transacción");
return;
}
if (!metodo) {
alert("Por favor selecciona el método de pago");
return;
}
if (tipo === 'Ingreso' && metodo !== 'Efectivo') {
alert("Los ingresos solo pueden ser en Efectivo");
return;
}
if (isNaN(monto) || monto <= 0) {
alert("Por favor ingresa un monto válido");
return;
}
// Move to step 2
document.getElementById('step1').style.display = 'none';
document.getElementById('step2').style.display = 'block';
document.getElementById('stepIndicator').innerText = '2';
document.getElementById('step2Amount').innerText = monto.toFixed(2) + '€';
currentStep = 2;
// Load personas for selection
loadPersonaSelector();
if (tipo === 'Transferencia') {
loadPersonaDestinoSelector();
}
} else if (currentStep === 2) {
// Validate step 2
var personaId = document.getElementById(field_persona).value;
if (!personaId) {
alert("Por favor selecciona un monedero");
return;
}
var tipo = document.getElementById(field_tipo).value;
if (tipo === 'Transferencia') {
var personaDestinoId = document.getElementById(field_persona_destino).value;
if (!personaDestinoId) {
alert("Por favor selecciona el monedero destino");
return;
}
if (personaId === personaDestinoId) {
alert("No puedes transferir al mismo monedero");
return;
}
}
// Move to step 3 - confirmation
document.getElementById('step2').style.display = 'none';
document.getElementById('step3').style.display = 'block';
document.getElementById('stepIndicator').innerText = '3';
var monto = parseFloat(document.getElementById(numpad_display).value);
document.getElementById('confirmAmount').innerText = monto.toFixed(2) + '€';
// Populate confirmation data
var tipoText = document.getElementById(field_tipo).selectedOptions[0].text;
var metodoText = document.getElementById(field_metodo).selectedOptions[0].text;
var personaName = SC_Personas[personaId]?.Nombre || personaId;
var notas = document.getElementById(field_notas).value || "(sin notas)";
document.getElementById('confirmTipo').innerText = tipoText;
document.getElementById('confirmMetodo').innerText = metodoText;
document.getElementById('confirmPersona').innerText = personaName;
document.getElementById('confirmNotas').innerText = notas;
if (tipo === 'Transferencia') {
var personaDestinoId = document.getElementById(field_persona_destino).value;
var personaDestinoName = SC_Personas[personaDestinoId]?.Nombre || personaDestinoId;
document.getElementById('confirmPersonaDestinoName').innerText = personaDestinoName;
document.getElementById('confirmPersonaDestino').style.display = 'block';
}
// Switch to final button layout
document.getElementById('buttonContainer').style.display = 'none';
document.getElementById('buttonContainerFinal').style.display = 'grid';
currentStep = 3;
}
};
// Confirm final transaction button
document.getElementById(btn_confirm + "2").onclick = () => {
processTransaction();
};
// Back button
document.getElementById(btn_back).onclick = () => {
if (currentStep === 3) {
// Go back to step 2
document.getElementById('step3').style.display = 'none';
document.getElementById('step2').style.display = 'block';
document.getElementById('stepIndicator').innerText = '2';
document.getElementById('buttonContainer').style.display = 'grid';
document.getElementById('buttonContainerFinal').style.display = 'none';
currentStep = 2;
}
};
// Cancel button
document.getElementById(btn_cancel).onclick = () => {
if (confirm("¿Seguro que quieres cancelar esta transacción?")) {
setUrlHash("pagos");
}
};
// QR Scanner button
document.getElementById(scan_qr_btn).onclick = () => {
setUrlHash("pagos,scan_qr");
};
function loadPersonaSelector() {
var container = document.querySelector('#personaSelector');
container.innerHTML = '';
document.getElementById(field_persona).value = selectedPersona;
addCategory_Personas(
container,
SC_Personas,
selectedPersona,
(personaId) => {
document.getElementById(field_persona).value = personaId;
selectedPersona = personaId;
},
"Monedero",
false,
"- No hay personas registradas -"
);
}
function loadPersonaDestinoSelector() {
var container = document.querySelector('#personaDestinoSelector');
container.innerHTML = '';
document.getElementById(field_persona_destino).value = selectedPersonaDestino;
addCategory_Personas(
container,
SC_Personas,
selectedPersonaDestino,
(personaId) => {
document.getElementById(field_persona_destino).value = personaId;
selectedPersonaDestino = personaId;
},
"Monedero Destino",
false,
"- No hay personas registradas -"
);
}
function processTransaction() {
var tipo = document.getElementById(field_tipo).value;
var monto = parseFloat(document.getElementById(numpad_display).value);
var personaId = document.getElementById(field_persona).value;
var metodo = document.getElementById(field_metodo).value;
var notas = document.getElementById(field_notas).value;
if (!personaId) {
alert("Por favor selecciona un monedero");
return;
}
if (tipo === 'Transferencia') {
var personaDestinoId = document.getElementById(field_persona_destino).value;
if (!personaDestinoId) {
alert("Por favor selecciona el monedero destino");
return;
}
if (personaId === personaDestinoId) {
alert("No puedes transferir al mismo monedero");
return;
}
}
// Check if persona has enough balance for Gasto or Transferencia
if (tipo === 'Gasto' || tipo === 'Transferencia') {
if (metodo == "Tarjeta") {
var persona = SC_Personas[personaId];
var currentBalance = parseFloat(persona.Monedero_Balance || 0);
if (currentBalance < monto) {
if (!confirm(`Saldo insuficiente (${currentBalance.toFixed(2)}€). ¿Continuar de todos modos?`)) {
return;
}
}
}
}
// Create transaction
var ticketId = safeuuid("");
var transactionData = {
Ticket: ticketId,
Fecha: CurrentISOTime(),
Tipo: tipo,
Monto: monto,
Persona: personaId,
Metodo: metodo,
Notas: notas,
Estado: "Completado"
};
if (tipo === 'Transferencia') {
transactionData.PersonaDestino = document.getElementById(field_persona_destino).value;
}
// Add prefilled data if exists
if (prefilledData.origen) {
transactionData.Origen = prefilledData.origen;
}
if (prefilledData.origen_id) {
transactionData.OrigenID = prefilledData.origen_id;
}
// Update wallet balances
// Don't update balance for Efectivo Gastos (paying with cash)
var shouldUpdateBalance = !(tipo === 'Gasto' && metodo === 'Efectivo');
if (shouldUpdateBalance) {
updateWalletBalance(personaId, tipo, monto, () => {
if (tipo === 'Transferencia') {
var destinoId = transactionData.PersonaDestino;
updateWalletBalance(destinoId, 'Ingreso', monto, () => {
saveTransaction(ticketId, transactionData);
});
} else {
saveTransaction(ticketId, transactionData);
}
});
} else {
// Skip balance update for Efectivo Gastos
saveTransaction(ticketId, transactionData);
}
}
function updateWalletBalance(personaId, tipo, monto, callback) {
var persona = SC_Personas[personaId];
if (!persona) {
alert("Error: Persona no encontrada");
return;
}
var currentBalance = parseFloat(persona.Monedero_Balance || 0);
var newBalance = currentBalance;
if (tipo === 'Ingreso') {
newBalance = currentBalance + monto;
} else if (tipo === 'Gasto' || tipo === 'Transferencia') {
newBalance = currentBalance - monto;
}
persona.Monedero_Balance = newBalance;
TS_encrypt(persona, SECRET, (encrypted) => {
betterGunPut(gun.get(TABLE).get("personas").get(personaId), encrypted);
if (callback) callback();
});
}
function saveTransaction(ticketId, data) {
TS_encrypt(data, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("pagos").get(ticketId), encrypted);
// If this is from SuperCafé, update the order
if (data.Origen === 'SuperCafé' && data.OrigenID) {
handleSuperCafePayment(data);
}
// Check for promotional bonus on Ingreso transactions (Efectivo only)
if (data.Tipo === 'Ingreso' && data.Metodo === 'Efectivo') {
var bonusAmount = calculatePromoBonus(data.Monto);
if (bonusAmount > 0) {
createPromoBonusTransaction(data.Persona, bonusAmount, data.Monto);
}
}
toastr.success("¡Transacción completada!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("pagos," + ticketId);
}, 1500);
});
}
function calculatePromoBonus(monto) {
var amount = parseFloat(monto);
if (amount >= 5) {
return 0.20; // 20% bonus
} else if (amount >= 4) {
return 0.15; // 15% bonus
} else if (amount >= 3) {
return 0.10; // 10% bonus
} else if (amount >= 2) {
return 0.05; // 5% bonus
}
return 0; // No bonus for amounts under 2€
}
function createPromoBonusTransaction(personaId, bonusAmount, originalAmount) {
return
var bonusTicketId = safeuuid("");
var bonusData = {
Ticket: bonusTicketId,
Fecha: CurrentISOTime(),
Tipo: "Ingreso",
Monto: bonusAmount,
Persona: personaId,
Metodo: "Efectivo",
Notas: "Promo Bono - " + bonusAmount.toFixed(2) + "€ extra por recarga de " + originalAmount.toFixed(2) + "€",
Estado: "Completado",
Origen: "Promo Bono"
};
// Update wallet balance with bonus
var persona = SC_Personas[personaId];
if (persona) {
var currentBalance = parseFloat(persona.Monedero_Balance || 0);
var newBalance = currentBalance + bonusAmount;
persona.Monedero_Balance = newBalance;
TS_encrypt(persona, SECRET, (encrypted) => {
betterGunPut(gun.get(TABLE).get("personas").get(personaId), encrypted);
});
}
// Save bonus transaction
TS_encrypt(bonusData, SECRET, (encrypted) => {
betterGunPut(gun.get(TABLE).get("pagos").get(bonusTicketId), encrypted);
});
toastr.success("🎉 ¡Promo Bono aplicado! +" + bonusAmount.toFixed(2) + "€ extra");
}
function handleSuperCafePayment(transactionData) {
// Mark the SuperCafé order as paid and delete it
betterGunPut(gun.get(TABLE).get("supercafe").get(transactionData.OrigenID), null);
// Update persona points
var persona = SC_Personas[transactionData.Persona];
if (!persona) return;
// Check if paying with points or adding points
if (false && persona.Puntos >= 10) {
if (confirm("¿Pagar con Puntos? (10 puntos) - Cancela para pagar con dinero y ganar 1 punto.")) {
persona.Puntos = parseInt(persona.Puntos) - 10;
toastr.success("¡Comanda gratis para " + persona.Nombre + "!");
} else {
persona.Puntos = parseInt(persona.Puntos) + 1;
toastr.success("¡Comanda de pago! +1 punto");
}
} else {
persona.Puntos = parseInt(persona.Puntos) + 1;
toastr.success("¡Comanda de pago! +1 punto");
}
TS_encrypt(persona, SECRET, (encrypted) => {
betterGunPut(gun.get(TABLE).get("personas").get(transactionData.Persona), encrypted);
});
}
// Pre-fill if data provided
if (prefilledData.monto) {
displayValue = prefilledData.monto;
document.getElementById(numpad_display).value = displayValue;
}
if (prefilledData.tipo) {
document.getElementById(field_tipo).value = prefilledData.tipo;
}
if (prefilledData.notas) {
document.getElementById(field_notas).value = prefilledData.notas;
}
},
// Edit/view transaction
edit: function (tid) {
if (!checkRole("pagos")) {
setUrlHash("pagos");
return;
}
var tid2 = location.hash.split(",")
if (tid == "datafono") {
PAGES.pagos.datafono()
return
}
if (tid == "datafono_prefill") {
PAGES.pagos.datafono(JSON.parse(atob(tid2[2])))
return
}
if (tid == "scan_qr") {
PAGES.pagos.__scanQR()
return
}
if (tid == "edit_transaction") {
PAGES.pagos.__editTransaction(tid2[2])
return
}
var nameh1 = safeuuid();
var field_ticket = safeuuid();
var field_fecha = safeuuid();
var field_tipo = safeuuid();
var field_monto = safeuuid();
var field_persona = safeuuid();
var field_persona_destino = safeuuid();
var field_metodo = safeuuid();
var field_notas = safeuuid();
var field_estado = safeuuid();
var field_origen = safeuuid();
var div_persona_destino = safeuuid();
var div_origen = safeuuid();
var btn_volver = safeuuid();
var btn_volver2 = safeuuid();
var btn_edit = safeuuid();
var btn_delete = safeuuid();
var btn_revert = safeuuid();
container.innerHTML = `
<h1>Transacción <code id="${nameh1}"></code></h1>
${BuildQR("pagos," + tid, "Esta Transacción")}
<button id="${btn_volver}">← Volver a Pagos</button>
<button id="${btn_volver2}">← Volver a SuperCafé</button>
<fieldset>
<legend>Detalles de la Transacción</legend>
<label>
Ticket/ID<br>
<input type="text" id="${field_ticket}" readonly style="background: #f0f0f0;"><br><br>
</label>
<label>
Fecha y Hora<br>
<input type="text" id="${field_fecha}" readonly style="background: #f0f0f0;"><br><br>
</label>
<label>
Tipo<br>
<input type="text" id="${field_tipo}" readonly style="background: #f0f0f0;"><br><br>
</label>
<label>
Monto<br>
<input type="text" id="${field_monto}" readonly style="background: #f0f0f0; font-size: 24px; font-weight: bold;"><br><br>
</label>
<label>
Monedero (Persona)<br>
<input type="text" id="${field_persona}" readonly style="background: #f0f0f0;"><br><br>
</label>
<div id="${div_persona_destino}" style="display: none;">
<label>
Monedero Destino<br>
<input type="text" id="${field_persona_destino}" readonly style="background: #f0f0f0;"><br><br>
</label>
</div>
<label>
Método de Pago<br>
<input type="text" id="${field_metodo}" readonly style="background: #f0f0f0;"><br><br>
</label>
<label>
Estado<br>
<input type="text" id="${field_estado}" readonly style="background: #f0f0f0;"><br><br>
</label>
<div id="${div_origen}" style="display: none;">
<label>
Origen<br>
<input type="text" id="${field_origen}" readonly style="background: #f0f0f0;"><br><br>
</label>
</div>
<label>
Notas<br>
<textarea id="${field_notas}" readonly rows="4" style="background: #f0f0f0;"></textarea><br><br>
</label>
</fieldset>
<fieldset style="margin-top: 20px;">
<legend>Acciones</legend>
<button id="${btn_edit}" class="btn5" style="font-size: 16px; padding: 10px 20px; margin: 5px;">
✏️ Editar Transacción
</button>
<button id="${btn_revert}" class="btn6" style="font-size: 16px; padding: 10px 20px; margin: 5px;">
↩️ Revertir Transacción
</button>
<button id="${btn_delete}" class="rojo" style="font-size: 16px; padding: 10px 20px; margin: 5px;">
🗑️ Eliminar Transacción
</button>
</fieldset>
`;
document.getElementById(btn_volver).onclick = () => {
setUrlHash("pagos");
};
document.getElementById(btn_volver2).onclick = () => {
setUrlHash("supercafe");
};
gun.get(TABLE).get("pagos").get(tid).once((data, key) => {
function load_data(data) {
document.getElementById(nameh1).innerText = key;
document.getElementById(field_ticket).value = data.Ticket || key;
var fecha = data.Fecha || "";
if (fecha) {
var d = new Date(fecha);
document.getElementById(field_fecha).value = d.toLocaleString('es-ES');
}
document.getElementById(field_tipo).value = data.Tipo || "";
document.getElementById(field_monto).value = (data.Monto || 0).toFixed(2) + "€";
var persona = SC_Personas[data.Persona] || {};
document.getElementById(field_persona).value = persona.Nombre || data.Persona || "";
if (data.PersonaDestino) {
var personaDestino = SC_Personas[data.PersonaDestino] || {};
document.getElementById(field_persona_destino).value = personaDestino.Nombre || data.PersonaDestino || "";
document.getElementById(div_persona_destino).style.display = 'block';
}
document.getElementById(field_metodo).value = data.Metodo || "";
document.getElementById(field_estado).value = data.Estado || "";
document.getElementById(field_notas).value = data.Notas || "";
if (data.Origen) {
document.getElementById(field_origen).value = data.Origen + (data.OrigenID ? " (" + data.OrigenID + ")" : "");
document.getElementById(div_origen).style.display = 'block';
}
// Edit button - navigate to edit mode
document.getElementById(btn_edit).onclick = () => {
setUrlHash("pagos,edit_transaction," + key);
};
// Delete button
document.getElementById(btn_delete).onclick = () => {
if (!checkRole("pagos:edit")) {
alert("No tienes permisos para eliminar transacciones");
return;
}
if (confirm("¿Estás seguro de que quieres ELIMINAR esta transacción?\n\nEsta acción NO se puede deshacer y los cambios en los monederos NO se revertirán automáticamente.\n\nPara revertir los cambios en los monederos, usa el botón 'Revertir Transacción' en su lugar.")) {
betterGunPut(gun.get(TABLE).get("pagos").get(key), null);
toastr.success("Transacción eliminada");
setTimeout(() => {
setUrlHash("pagos");
}, 1000);
}
};
// Revert button - reverses wallet balance changes and deletes transaction
document.getElementById(btn_revert).onclick = () => {
if (!checkRole("pagos:edit")) {
alert("No tienes permisos para revertir transacciones");
return;
}
if (confirm("¿Estás seguro de que quieres REVERTIR esta transacción?\n\nEsto revertirá los cambios en los monederos y eliminará la transacción.")) {
// Reverse the wallet balance changes
var tipo = data.Tipo;
var monto = parseFloat(data.Monto || 0);
var personaId = data.Persona;
// For Ingreso, subtract from balance (reverse)
// For Gasto, add to balance (reverse)
// For Transferencia, reverse both sides
if (tipo === "Ingreso") {
revertWalletBalance(personaId, "Gasto", monto, () => {
deleteTransaction(key);
});
} else if (tipo === "Gasto") {
revertWalletBalance(personaId, "Ingreso", monto, () => {
deleteTransaction(key);
});
} else if (tipo === "Transferencia") {
var destinoId = data.PersonaDestino;
revertWalletBalance(personaId, "Ingreso", monto, () => {
revertWalletBalance(destinoId, "Gasto", monto, () => {
deleteTransaction(key);
});
});
}
}
};
function revertWalletBalance(personaId, tipo, monto, callback) {
var persona = SC_Personas[personaId];
if (!persona) {
toastr.error("Error: Persona no encontrada");
return;
}
var currentBalance = parseFloat(persona.Monedero_Balance || 0);
var newBalance = currentBalance;
if (tipo === "Ingreso") {
newBalance = currentBalance + monto;
} else if (tipo === "Gasto") {
newBalance = currentBalance - monto;
}
persona.Monedero_Balance = newBalance;
TS_encrypt(persona, SECRET, (encrypted) => {
betterGunPut(gun.get(TABLE).get("personas").get(personaId), encrypted);
if (callback) callback();
});
}
function deleteTransaction(transactionKey) {
betterGunPut(gun.get(TABLE).get("pagos").get(transactionKey), null);
toastr.success("Transacción revertida y eliminada");
setTimeout(() => {
setUrlHash("pagos");
}, 1000);
}
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, load_data);
} else {
load_data(data || {});
}
});
},
// Main index view with transaction log
index: function () {
if (!checkRole("pagos")) {
setUrlHash("index");
return;
}
var btn_new = safeuuid();
var btn_datafono = safeuuid();
var total_ingresos = safeuuid();
var total_gastos = safeuuid();
var balance_total = safeuuid();
container.innerHTML = `
<h1>💳 Pagos y Transacciones</h1>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px;">
<div style="background: linear-gradient(135deg, #2ed573, #26d063); padding: 20px; border-radius: 10px; text-align: center; color: white; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<h3 style="margin: 0;">Total Ingresos</h3>
<div id="${total_ingresos}" style="font-size: 32px; font-weight: bold; margin-top: 10px;">0.00€</div>
</div>
<div style="background: linear-gradient(135deg, #ff4757, #ff3838); padding: 20px; border-radius: 10px; text-align: center; color: white; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<h3 style="margin: 0;">Total Gastos</h3>
<div id="${total_gastos}" style="font-size: 32px; font-weight: bold; margin-top: 10px;">0.00€</div>
</div>
<div style="background: linear-gradient(135deg, #667eea, #764ba2); padding: 20px; border-radius: 10px; text-align: center; color: white; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<h3 style="margin: 0;">Balance Total</h3>
<div id="${balance_total}" style="font-size: 32px; font-weight: bold; margin-top: 10px;">0.00€</div>
</div>
</div>
<button id="${btn_datafono}" class="btn5" style="font-size: 18px; padding: 15px 30px;">
💳 Abrir Datafono
</button>
<button id="${btn_new}" style="font-size: 18px; padding: 15px 30px;">
Nueva Transacción Manual
</button>
<h2>Registro de Transacciones</h2>
<div id="tableContainer"></div>
`;
var totals = { ingresos: 0, gastos: 0 };
const config = [
{
key: "Fecha",
label: "Fecha/Hora",
type: "template",
template: (data, element) => {
if (data.Fecha) {
var d = new Date(data.Fecha);
element.innerText = d.toLocaleString('es-ES');
}
},
default: ""
},
{
key: "Tipo",
label: "Tipo",
type: "template",
template: (data, element) => {
var tipo = data.Tipo || "";
var icon = "";
var color = "";
if (tipo === "Ingreso") {
icon = "💵";
color = "#2ed573";
} else if (tipo === "Gasto") {
icon = "💸";
color = "#ff4757";
} else if (tipo === "Transferencia") {
icon = "🔄";
color = "#667eea";
}
element.innerHTML = `<span style="background: ${color}; color: white; padding: 5px 10px; border-radius: 5px; font-weight: bold;">${icon} ${tipo}</span>`;
},
default: ""
},
{
key: "Monto",
label: "Monto",
type: "template",
template: (data, element) => {
var monto = parseFloat(data.Monto || 0);
var color = data.Tipo === "Ingreso" ? "#2ed573" : "#ff4757";
element.innerHTML = `<span style="font-size: 20px; font-weight: bold; color: ${color};">${monto.toFixed(2)}€</span>`;
},
default: "0.00€"
},
{
key: "Persona",
label: "Monedero",
type: "persona",
default: ""
},
{
key: "Metodo",
label: "Método",
type: "text",
default: ""
},
{
key: "Estado",
label: "Estado",
type: "template",
template: (data, element) => {
var estado = data.Estado || "Pendiente";
var color = estado === "Completado" ? "#2ed573" : "#ffa502";
element.innerHTML = `<span style="background: ${color}; color: white; padding: 5px 10px; border-radius: 5px;">${estado}</span>`;
},
default: "Pendiente"
}
];
// Persistent totals object by ID
let totalData = {
ingresos: {}, // { id: monto }
gastos: {} // { id: monto }
};
var balance_real = 0;
setInterval(() => {
Object.values(SC_Personas).forEach(persona => {
balance_real += parseFloat(persona.Monedero_Balance || 0)
});
document.getElementById(balance_total).innerText = balance_real.toFixed(2) + "€";
document.getElementById(balance_total).style.color = balance_real >= 0 ? "white" : "#ffcccc";
}, 1000);
TS_IndexElement(
"pagos",
config,
gun.get(TABLE).get("pagos"),
document.getElementById("tableContainer"),
(data, new_tr) => {
var id = data._key
const monto = parseFloat(data.Monto || 0) || 0;
const tipo = data.Tipo;
const metodo = data.Metodo || "";
// Count all Ingresos and Gastos in totals (excluding Transferencias)
// Reset entries on every call for this ID
if (tipo === "Ingreso") {
if (data.Origen != "Promo Bono") {
totalData.gastos[id] = 0;
totalData.ingresos[id] = monto;
}
} else if (tipo === "Gasto") {
if (metodo != "Tarjeta") {
totalData.ingresos[id] = 0;
totalData.gastos[id] = monto;
}
} else {
// For Transferencias, don't count in totals
totalData.ingresos[id] = 0;
totalData.gastos[id] = 0;
}
// Compute totals by summing all objects
const totalIngresos = Object.values(totalData.ingresos).reduce((a, b) => a + b, 0);
const totalGastos = Object.values(totalData.gastos).reduce((a, b) => a + b, 0);
const balance = totalIngresos - totalGastos;
// Update UI
document.getElementById(total_ingresos).innerText = totalIngresos.toFixed(2) + "€";
document.getElementById(total_gastos).innerText = totalGastos.toFixed(2) + "€";
}
);
document.getElementById(btn_datafono).onclick = () => {
setUrlHash("pagos,datafono");
};
if (!checkRole("pagos:edit")) {
document.getElementById(btn_new).style.display = "none";
document.getElementById(btn_datafono).style.display = "none";
} else {
document.getElementById(btn_new).onclick = () => {
setUrlHash("pagos," + safeuuid(""));
};
}
},
// QR Scanner for selecting wallet/persona
__scanQR: function() {
if (!checkRole("pagos:edit")) {
setUrlHash("pagos");
return;
}
var qrscan = safeuuid();
var btn_cancel = safeuuid();
container.innerHTML = `
<div style="max-width: 600px; margin: 0 auto; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.3);">
<h1 style="color: white; text-align: center; margin-bottom: 20px;">
📷 Escanear QR de Monedero
</h1>
<div style="background: white; padding: 20px; border-radius: 10px; margin-bottom: 20px;">
<p style="text-align: center; color: #333; margin-bottom: 15px;">
Escanea el código QR del monedero de la persona para seleccionarlo automáticamente
</p>
<div style="max-width: 400px; margin: 0 auto;" id="${qrscan}"></div>
</div>
<button id="${btn_cancel}" style="width: 100%; padding: 20px; font-size: 18px; font-weight: bold;
background: #ff4757; color: white; border: none; border-radius: 8px; cursor: pointer;">
❌ Cancelar
</button>
</div>
`;
// Initialize QR scanner
var html5QrcodeScanner = new Html5QrcodeScanner(
qrscan, { fps: 10, qrbox: 250 }
);
function onScanSuccess(decodedText, decodedResult) {
html5QrcodeScanner.clear();
// Parse the QR code result
// Expected format: "personas,{personaId}" or just "{personaId}"
var personaId = decodedText;
// If it's a full URL hash, extract the persona ID
if (decodedText.includes("personas,")) {
var parts = decodedText.split(",");
if (parts.length > 1) {
personaId = parts[1];
}
}
// Verify the persona exists
if (SC_Personas[personaId]) {
toastr.success("✅ Monedero escaneado: " + SC_Personas[personaId].Nombre);
// Store the selected persona in sessionStorage and return to datafono
sessionStorage.setItem('pagos_scanned_persona', personaId);
// Navigate back to datafono
setUrlHash("pagos,datafono");
} else {
toastr.error("❌ Código QR no reconocido como un monedero válido");
setTimeout(() => {
setUrlHash("pagos,datafono");
}, 2000);
}
}
html5QrcodeScanner.render(onScanSuccess);
EventListeners.QRScanner.push(html5QrcodeScanner);
// Cancel button
document.getElementById(btn_cancel).onclick = () => {
html5QrcodeScanner.clear();
setUrlHash("pagos,datafono");
};
},
// Edit existing transaction
__editTransaction: function(transactionId) {
if (!checkRole("pagos:edit")) {
setUrlHash("pagos");
return;
}
var field_tipo = safeuuid();
var field_monto = safeuuid();
var field_persona = safeuuid();
var field_persona_destino = safeuuid();
var field_metodo = safeuuid();
var field_notas = safeuuid();
var field_estado = safeuuid();
var div_persona_destino = safeuuid();
var btn_save = safeuuid();
var btn_cancel = safeuuid();
var selectedPersona = "";
var selectedPersonaDestino = "";
var originalData = null;
container.innerHTML = `
<div style="max-width: 600px; margin: 0 auto; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.3);">
<h1 style="color: white; text-align: center; margin-bottom: 20px;">
✏️ Editar Transacción
</h1>
<div style="background: white; padding: 20px; border-radius: 10px; margin-bottom: 20px;">
<fieldset style="border: 2px solid #667eea; border-radius: 8px; padding: 15px;">
<legend style="color: #667eea; font-weight: bold;">Información de Transacción</legend>
<label style="display: block; margin-bottom: 15px;">
<b>Tipo de Transacción:</b><br>
<select id="${field_tipo}" style="width: 100%; padding: 10px; font-size: 16px; border: 2px solid #ddd; border-radius: 5px;">
<option value="Ingreso"> Ingreso (Depósito)</option>
<option value="Gasto"> Gasto (Retiro/Pago)</option>
<option value="Transferencia">🔄 Transferencia</option>
</select>
</label>
<label style="display: block; margin-bottom: 15px;">
<b>Método de Pago:</b><br>
<select id="${field_metodo}" style="width: 100%; padding: 10px; font-size: 16px; border: 2px solid #ddd; border-radius: 5px;">
<option value="Efectivo">💵 Efectivo</option>
<option value="Tarjeta">💳 Tarjeta Monedero</option>
<option value="Otro">❓ Otro</option>
</select>
</label>
<label style="display: block; margin-bottom: 15px;">
<b>Monto (€):</b><br>
<input type="number" id="${field_monto}" step="0.01" min="0"
style="width: calc(100% - 24px); padding: 15px; font-size: 24px; text-align: right;
border: 3px solid #667eea; border-radius: 5px; font-weight: bold;">
</label>
<label style="display: block; margin-bottom: 15px;">
<b>Estado:</b><br>
<select id="${field_estado}" style="width: 100%; padding: 10px; font-size: 16px; border: 2px solid #ddd; border-radius: 5px;">
<option value="Completado">✅ Completado</option>
<option value="Pendiente">⏳ Pendiente</option>
<option value="Cancelado">❌ Cancelado</option>
</select>
</label>
<label style="display: block; margin-bottom: 15px;">
<b>Monedero (Persona):</b>
<input type="hidden" id="${field_persona}">
<div id="personaSelector"></div>
</label>
<div id="${div_persona_destino}" style="display: none; margin-bottom: 15px;">
<b>Monedero Destino:</b>
<input type="hidden" id="${field_persona_destino}">
<div id="personaDestinoSelector"></div>
</div>
<label style="display: block; margin-bottom: 15px;">
<b>Notas:</b><br>
<textarea id="${field_notas}" rows="3"
style="width: calc(100% - 24px); padding: 10px; font-size: 14px; border: 2px solid #ddd; border-radius: 5px;"
placeholder="Notas adicionales..."></textarea>
</label>
</fieldset>
</div>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
<button id="${btn_cancel}" style="padding: 20px; font-size: 18px; font-weight: bold;
background: #ff4757; color: white; border: none; border-radius: 8px; cursor: pointer;">
❌ CANCELAR
</button>
<button id="${btn_save}" style="padding: 20px; font-size: 18px; font-weight: bold;
background: #2ed573; color: white; border: none; border-radius: 8px; cursor: pointer;">
💾 GUARDAR
</button>
</div>
</div>
`;
// Load transaction data
gun.get(TABLE).get("pagos").get(transactionId).once((data, key) => {
function loadTransactionData(data) {
originalData = data;
document.getElementById(field_tipo).value = data.Tipo || "Ingreso";
document.getElementById(field_metodo).value = data.Metodo || "Efectivo";
document.getElementById(field_monto).value = data.Monto || 0;
document.getElementById(field_estado).value = data.Estado || "Completado";
document.getElementById(field_notas).value = data.Notas || "";
selectedPersona = data.Persona || "";
selectedPersonaDestino = data.PersonaDestino || "";
loadPersonaSelector();
if (data.Tipo === "Transferencia") {
document.getElementById(div_persona_destino).style.display = 'block';
loadPersonaDestinoSelector();
}
}
if (typeof data == "string") {
TS_decrypt(data, SECRET, loadTransactionData);
} else {
loadTransactionData(data || {});
}
});
// Tipo change handler
document.getElementById(field_tipo).addEventListener('change', function() {
var tipo = this.value;
var divDestino = document.getElementById(div_persona_destino);
if (tipo === 'Transferencia') {
divDestino.style.display = 'block';
loadPersonaDestinoSelector();
} else {
divDestino.style.display = 'none';
}
});
function loadPersonaSelector() {
var container = document.querySelector('#personaSelector');
container.innerHTML = '';
document.getElementById(field_persona).value = selectedPersona;
addCategory_Personas(
container,
SC_Personas,
selectedPersona,
(personaId) => {
document.getElementById(field_persona).value = personaId;
selectedPersona = personaId;
},
"Monedero",
false,
"- No hay personas registradas -"
);
}
function loadPersonaDestinoSelector() {
var container = document.querySelector('#personaDestinoSelector');
container.innerHTML = '';
document.getElementById(field_persona_destino).value = selectedPersonaDestino;
addCategory_Personas(
container,
SC_Personas,
selectedPersonaDestino,
(personaId) => {
document.getElementById(field_persona_destino).value = personaId;
selectedPersonaDestino = personaId;
},
"Monedero Destino",
false,
"- No hay personas registradas -"
);
}
// Save button
document.getElementById(btn_save).onclick = () => {
var tipo = document.getElementById(field_tipo).value;
var monto = parseFloat(document.getElementById(field_monto).value);
var personaId = document.getElementById(field_persona).value;
var metodo = document.getElementById(field_metodo).value;
var notas = document.getElementById(field_notas).value;
var estado = document.getElementById(field_estado).value;
if (!personaId) {
alert("Por favor selecciona un monedero");
return;
}
if (isNaN(monto) || monto < 0) {
alert("Por favor ingresa un monto válido");
return;
}
if (tipo === 'Transferencia') {
var personaDestinoId = document.getElementById(field_persona_destino).value;
if (!personaDestinoId) {
alert("Por favor selecciona el monedero destino");
return;
}
if (personaId === personaDestinoId) {
alert("No puedes transferir al mismo monedero");
return;
}
}
if (!confirm("¿Estás seguro de que quieres guardar los cambios?\n\nNOTA: Los cambios en los monederos NO se ajustarán automáticamente. Si cambiaste el monto, tipo o persona, deberías revertir la transacción original y crear una nueva.")) {
return;
}
// Update transaction data
var updatedData = {
...originalData,
Tipo: tipo,
Monto: monto,
Persona: personaId,
Metodo: metodo,
Notas: notas,
Estado: estado
};
if (tipo === 'Transferencia') {
updatedData.PersonaDestino = document.getElementById(field_persona_destino).value;
} else {
delete updatedData.PersonaDestino;
}
TS_encrypt(updatedData, SECRET, (encrypted) => {
document.getElementById("actionStatus").style.display = "block";
betterGunPut(gun.get(TABLE).get("pagos").get(transactionId), encrypted);
toastr.success("¡Transacción actualizada!");
setTimeout(() => {
document.getElementById("actionStatus").style.display = "none";
setUrlHash("pagos," + transactionId);
}, 1500);
});
};
// Cancel button
document.getElementById(btn_cancel).onclick = () => {
if (confirm("¿Seguro que quieres cancelar? Los cambios no se guardarán.")) {
setUrlHash("pagos," + transactionId);
}
};
}
};