gpt51

가계부 · Wealth Ledger (VND)
Libro mayor de riqueza
Libro de cuentas domésticas en tiempo real (VND)
Entrada rápida Integridad de los datos normal
VND
Activos totales
₫0
Ahorros de este mes: ₫0
dinero
₫0
Cuenta corporativa
₫0
cuenta personal
₫0

Plan de gastos

Plan de gastos semanal
₫0 / ₫0
Plan de gastos mensuales
₫0 / ₫0
VND
VND
Intenta aumentar tus ahorros reduciendo tus gastos reales en comparación con lo que tenías planeado.

Balance inicial

VND
VND
VND
El saldo inicial se refleja inmediatamente en el total acumulado.
No se puede revertir. Recomendamos hacer una copia de seguridad.

Ingresos/Gastos de los últimos 12 meses

Ahorros mensuales

Historial de transacciones

fechacuentacategoríaCategoríamemorándumcantidadtrabajar
0
importar: ₫0
gasto: ₫0
ahorros: ₫0
Los hábitos generan riqueza. ¡Regístralo hoy también! ✨
VND
`; const blob = new Blob([html], {type:'application/vnd.ms-excel'}); downloadBlob(blob, `ledger_vnd_${todayStr()}.xls`, 'application/vnd.ms-excel'); } function backupJSON(){ const payload = { settings, tx }; const json = '\uFEFF'+JSON.stringify(payload); downloadBlob(json, `ledger_vnd_backup_${todayStr()}.json`, 'application/json;charset=utf-8;'); } function restoreJSON(e){ const f = e.target.files[0]; if(!f) return; const reader = new FileReader(); reader.onload = function(){ try{ const obj = JSON.parse(reader.result); if(!obj || !Array.isArray(obj.tx) || !obj.settings) throw new Error('bad file'); // minimal validation settings = { ...defaultSettings(), ...obj.settings, budgets: { weekly: toInt(obj.settings?.budgets?.weekly||0), monthly: toInt(obj.settings?.budgets?.monthly||0) }, initBalances:{ cash: toInt(obj.settings?.initBalances?.cash||0), biz: toInt(obj.settings?.initBalances?.biz||0), personal: toInt(obj.settings?.initBalances?.personal||0), } }; tx = obj.tx.map(n=>({ id: n.id || uid(), type: n.type==='in'?'in':'out', account: ['cash','biz','personal'].includes(n.account)?n.account:'cash', amount: toInt(n.amount||0), date: n.date || fmtDateInput(new Date()), category: n.category||'', memo: n.memo||'', createdAt: n.createdAt || Date.now() })); saveSettings(); saveTx(); // Refill inputs els.langSelect.value = settings.lang; applyI18N(); applyTheme(settings.theme); setAmtInput(els.inpWeeklyPlan, settings.budgets.weekly); setAmtInput(els.inpMonthlyPlan, settings.budgets.monthly); setAmtInput(els.initCash, settings.initBalances.cash); setAmtInput(els.initBiz, settings.initBalances.biz); setAmtInput(els.initPersonal, settings.initBalances.personal); drawAll(); showToast(t('imported')); els.restoreFile.value = ''; }catch(err){ alert('Invalid backup file.'); } }; reader.readAsText(f); }// Helpers function escCsv(s){ return (s||'').replace(/[\r\n]+/g,' '); } function csvCell(s){ if(s==null) return ''; const str = String(s); if(/[",\n]/.test(str)) return `"${str.replace(/"/g,'""')}"`; return str; } function downloadBlob(content, filename, mime){ let blob = content instanceof Blob ? content : new Blob([content], {type:mime}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); setTimeout(()=>{ URL.revokeObjectURL(url); a.remove(); }, 100); } function todayStr(){ const d = new Date(); return `${d.getFullYear()}${String(d.getMonth()+1).padStart(2,'0')}${String(d.getDate()).padStart(2,'0')}`; } function uid(){ return 'id_'+Math.random().toString(36).slice(2,10)+Date.now().toString(36); } function escapeHtml(s){ return (s||'').replace(/[&<>"']/g, c=>({ '&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } function debounce(fn,ms){ let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a),ms); }; } function alertI18n(key){ alert(t(key)); }// -------------- Lightweight self-test (doesn't change user data) -------------- // Ensures integrity math and formatter work. Runs on isolated data. (function selfTest(){ try{ // pure compute with synthetic tx const init = {cash: 1000, biz: 0, personal: 0}; const tempTx = [ {type:'in', account:'cash', amount: 2000, date:'2025-01-10'}, {type:'out', account:'cash', amount: 500, date:'2025-01-11'}, {type:'in', account:'biz', amount: 300, date:'2025-01-12'}, ]; // compute let c = {...init}; for(const r of tempTx){ c[r.account] += (r.type==='in'?1:-1)*r.amount; } if(c.cash!==2500 || c.biz!==300 || c.personal!==0) throw new Error('balance mismatch'); // parse/format if(parseAmt('1,234,567 VND')!==1234567) throw new Error('parseAmt failed'); formatVND(123); // should not throw // ok }catch(e){ console.warn('Self-test failed', e); } })();})();