Mis Finanzas V4 — Código completo Copiar desde hasta y reemplazar todo el contenido de index.html. Mis Finanzas
MI CONTROL

Mis Finanzas

Disponible$0
Ingresos$0

Últimos movimientos

Movimientos

■ Tarjetas

■ Cuotas

Total comprometido$0
<

■ Presupuesto

Ingr

■ Recurrentes

■ Recordatorios

Próximos 30 días:

■ Mi Auto

Ve

■ Precios

■ Resumen

Movimientos0

■■ Ajustes

Los datos se guardan automáticamente en este dispositivo.

({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m])); let db=load(), movementType='expense'; function load(){try{let x=JSON.parse(localStorage.getItem(K));return x?{...empty,...x,car:{...empty.car,...x.car},budget:{...empty.budget,...x.budget}}:structuredCl function save(){try{localStorage.setItem(K,JSON.stringify(db));return true}catch{toast('No hay espacio para guardar');return false}} function toast(t){$('toast').textContent=t;$('toast').classList.add('show');clearTimeout(window.tt);window.tt=setTimeout(()=>$('toast').classList.remove('show'),180 function val(i){return $(i)?.value?.trim()||''}function num(i){return Number(val(i))||0} function totals(){return db.movements.reduce((r,x)=>(r[x.type]+=Number(x.amount)||0,r),{income:0,expense:0})} function go(s){if(s==='more'){more();return}document.querySelectorAll('.section').forEach(x=>x.classList.toggle('active',x.id===s));document.querySelectorAll('nav b function more(){document.querySelectorAll('.section').forEach(x=>x.classList.remove('active'));let old=$('more');if(old)old.remove();let s=document.createElement('s function modal(title,body){$('modalBody').innerHTML='

'+title+'

'+body;$('modal').classList.remove('hidden')} function close(){ $('modal').classList.add('hidden');$('modalBody').innerHTML=''} const sub=t=>''; function form(t){ let h=''; if(t==='movement')h='ProductoProductoNombreLímite mensual'+sub(t); if(t==='fixed')h='DescripciónVehículoConcepto
'+esc(x.description)+''+esc(x.date)+(x.note?' · '+esc(x.note):'')+'
'+emptyText+'
')} function render(){ let t=totals();$('balance').textContent=money(t.income-t.expense);$('incomeTotal').textContent=money(t.income);$('expenseTotal').textContent=money(t.expense);$('mov let ms=[...db.movements].sort((a,b)=>b.date.localeCompare(a.date));list('recent',ms.slice(0,5),'Todavía no hay movimientos.',movement);list('allMovements',ms,'No ha list('cardsList',db.cards,'No hay tarjetas.',x=>'
'+esc(x.name)+'•••• '+esc(x.last4)+'
< $('installmentTotal').textContent='$0';$('installmentList').innerHTML='
No hay cuotas registradas todavía.
'; $('budgetIncome').textContent=money(t.income);$('budgetSpent').textContent=money(t.expense);$('budgetAvailable').textContent=money(t.income-t.expense);$('budgetLimi list('fixedList',db.fixed,'No hay gastos fijos.',x=>'
'+esc(x.name)+'Día '+esc(x.day||'—')+''
'+esc(x.name)+'Día '+esc(x.day)+'a.date.localeCompare(b.date));$('reminderCount').textContent=rem.filter(x=>x.date>=today()).length;list('reminderList',rem,'No $('carName').textContent=db.car.name||'Sin configurar';$('carPlate').textContent=db.car.plate||'—';$('carKm').textContent=db.car.km||'—';$('carInsurance').textConte $('productCount').textContent=db.products.length;$('productDate').textContent=db.products.length?[...db.products].map(x=>x.updated).sort().pop():'—';list('products' $('reportMov').textContent=db.movements.length;$('reportBal').textContent=money(t.income-t.expense);$('reportIncome').textContent=money(t.income);$('reportExpense') } document.addEventListener('click',e=>{let b=e.target.closest('button');if(!b)return;if(b.dataset.s)return go(b.dataset.s);if(b.dataset.f)return form(b.dataset.f);le $('modal').addEventListener('click',e=>{if(e.target.id==='modal')close()}); $('importFile').addEventListener('change',e=>{let f=e.target.files[0];if(!f)return;let r=new FileReader();r.onload=()=>{try{let x=JSON.parse(r.result);db={...empty, render();