SINCRONIZANDO...
Dashboard
--:--:--

,

Tareas
0 pend.
0 hechas
Patrimonio
$0
0 activos
Balance mes
$0
ingresos - gastos
Notas
0
capturadas
💰 Disponible
$0
ingresos - fijos
Tareas recientes

Sin tareas

Top activos
📊

Sin activos

Últimas finanzas
💸

Sin movimientos

Distribución patrimonio
Finanzas — últimos 6 meses
$0
$0
$0
$0
$0
$0
Últimos 6 meses
Gastos por categoría
Movimientos
DescripciónCategoríaFechaMonto
$0
0 activos
$0
0.00%
$0
Distribución
📈 Evolución del patrimonio (90 días)
🎯 Foco del día
⚡ Acciones pendientes
🔁 Hábitos
✅ Tareas pendientes
💸 Gastos de hoy
💡 Insights del día
Secciones
Tareas
📝Notas
📅Calendario
🎯Metas
📚Biblioteca
✈️Viajes
🔁Hábitos
💸Gastos Fijos
📅Pantalla Hoy
Cuenta
?
✏️
...
● ONLINE
📊Resumen del mes
🌐Moneda y tipo de cambio
Moneda rápida
Cerrar sesión
Mind Nox — Patrimonio
Sistema de Control Personal
REPORTE PATRIMONIO
${today}
${username?`
${username}
`:''}
${assets.length} activo${assets.length!==1?'s':''}
${formatCurrency(totalVal)}
${formatCurrency(totalCost)}
${totalPL>=0?'+':''}${formatCurrency(Math.abs(totalPL))} (${totalPL>=0?'+':''}${plPct}%)
${rows}
ActivoTipoCantidadCompraValor ActualP&LP&L %
`; const win=window.open('','_blank'); if(!win){showToast('Permitir popups para exportar','warn',4000);return;} win.document.write(html);win.document.close(); setTimeout(()=>{win.focus();win.print();},500); showToast(`✅ ${assets.length} activos — imprimí como PDF`,'success',4000); } // ---------------------------------------------------------------- // EXPORT PDF FINANZAS // ---------------------------------------------------------------- function exportFinanzasPDF() { const now=new Date(); const ck=`${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}`; const monthName=['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'][now.getMonth()]; const fins=state.finances.filter(f=>f.date&&f.date.slice(0,7)===ck); if(!fins.length){showToast('Sin movimientos este mes','warn',2500);return;} const ing=fins.filter(f=>f.type==='ingreso').reduce((s,f)=>s+Number(f.amount),0); const gas=fins.filter(f=>f.type==='gasto').reduce((s,f)=>s+Number(f.amount),0); const bal=ing-gas; const today=new Date().toLocaleDateString('es-AR',{year:'numeric',month:'long',day:'numeric'}); const username=state.username||''; const rows=[...fins].sort((a,b)=>b.date>a.date?1:-1).map(f=>{ const color=f.type==='ingreso'?'#22c55e':'#ff4757'; const sign=f.type==='ingreso'?'+':'-'; const orig=f.currencyOrig&&f.currencyOrig!=='USD'?`
${Number(f.amountOrig||0).toLocaleString('es-AR')} ${f.currencyOrig}`:''; return ` ${f.date||'—'} ${f.desc||''} ${f.cat||'—'} ${sign}${formatCurrency(Number(f.amount))}${orig} `; }).join(''); const html=` Mind Nox — Finanzas ${monthName} ${now.getFullYear()}
Reporte Financiero — ${monthName} ${now.getFullYear()}
FINANZAS DEL MES
${today}
${username?`
${username}
`:''}
${fins.length} movimiento${fins.length!==1?'s':''}
+${formatCurrency(ing)}
-${formatCurrency(gas)}
${bal>=0?'+':''}${formatCurrency(Math.abs(bal))}
${rows}
FechaDescripciónCategoríaMonto
`; const win=window.open('','_blank'); if(!win){showToast('Permitir popups para exportar','warn',4000);return;} win.document.write(html);win.document.close(); setTimeout(()=>{win.focus();win.print();},500); showToast(`✅ ${fins.length} movimientos — imprimí como PDF`,'success',4000); } // Redirigir exportación CSV → PDF // Init state setTimeout(() => { if (state && !state._customCats) state._customCats = []; if (state && !state._customAssetTypes) state._customAssetTypes = []; }, 200); console.log('✅ MindNox v6.0 — custom cats/tipos persistentes, wear badge, presupuesto moneda, export PDF'); function handleCatChange(val, containerId) { if (val !== '__new_custom__') return; // Mostrar mini-form inline const container = document.getElementById(containerId); if (!container) return; container.innerHTML += `
`; setTimeout(() => document.getElementById('new-cat-emoji')?.focus(), 50); } function createNewCat(containerId) { const emoji = document.getElementById('new-cat-emoji')?.value?.trim() || '📌'; const name = document.getElementById('new-cat-name')?.value?.trim(); if (!name) { showToast('Escribí un nombre para la categoría', 'warn'); return; } const cats = addCustomCat(name, emoji); const newCat = cats[cats.length - 1]; // Re-render el selector con la nueva categoría seleccionada const container = document.getElementById(containerId); if (!container) return; const select = container.querySelector('select'); const defaultCats = ['Comida','Transporte','Entretenimiento','Gaming','Salud','Trabajo','Ahorro','Inversión','Otro']; const allOpts = [ ...defaultCats.map(c => ``), '', ...cats.map(c => ``), '' ]; container.innerHTML = ``; showToast(`✅ Categoría "${emoji} ${name}" creada`, 'success', 2000); } // Resolver el valor de categoría (puede ser id de custom o nombre directo) function resolveCatValue(val) { if (!val || val === '__new_custom__') return 'Otro'; const customCats = getCustomCats(); const custom = customCats.find(c => c.id === val); if (custom) return `${custom.emoji} ${custom.name}`; return val; } // ---------------------------------------------------------------- // MODAL ACTIVO v2: campo categoría contextual + moneda en precios // ---------------------------------------------------------------- const ASSET_HINTS_V2 = { crypto: '🪙 Ingresá símbolo (BTC, ETH, SOL...) y cantidad. Precio se actualiza auto.', skin: '🎮 Ingresá el nombre exacto del skin. Podés buscar en Steam tal cual como este.', banco: '🏦 Ingresá el nombre del banco y el saldo como valor actual.', efectivo: '💵 Ingresá el monto total como valor actual.', accion: '📈 Ingresá el ticker (AAPL, MSFT...), cantidad y precios.', inversion: '💼 FCI, bonos, etc. Capital invertido como precio de compra.', otro: '📦 Cualquier activo. Nombre, valor y precio de compra.' }; // Reemplazar modal forms para activo function updateAssetModalForType(tipo) { const hint = document.getElementById('m-asset-hint'); if (hint) hint.textContent = ASSET_HINTS_V2[tipo] || ASSET_HINTS_V2.otro; // Mostrar/ocultar símbolo const simGrp = document.getElementById('m-simbolo-group'); if (simGrp) simGrp.style.display = (tipo === 'crypto' || tipo === 'accion') ? 'block' : 'none'; // Campo categoría: SOLO para skins → wear selector // Para otros tipos: ocultar categoría (no tiene sentido) const catGrp = document.getElementById('m-categoria-group'); if (!catGrp) return; if (tipo === 'skin') { catGrp.style.display = 'block'; catGrp.innerHTML = `
${Object.entries(CS2_WEAR).map(([k,v]) => ` `).join('')}
`; } else { // No mostrar categoría para otros tipos catGrp.style.display = 'none'; catGrp.innerHTML = ''; } } // Override openModal para el activo con nuevo layout // Override modal de finanzas con categoría mejorada // Parchamos el body directamente en modalForms const _finModalBody = `
`; if (typeof modalForms !== 'undefined') { modalForms.fin.body = _finModalBody; } // ---------------------------------------------------------------- // OVERRIDE saveModal para manejar nuevo asset modal // ---------------------------------------------------------------- const _______origSaveModal = saveModal; // ---------------------------------------------------------------- // ASSET ROW v2: mostrar wear badge para skins, precio en moneda orig // ---------------------------------------------------------------- function renderAssetRow(a) { const val = getAssetValue(a), cost = getAssetCost(a), pl = cost > 0 ? val - cost : null; const plPct = (cost > 0 && pl !== null) ? ((pl / cost) * 100) : null; let plHtml = ''; if (pl !== null) { const cls = pl > 0 ? 'profit-pos' : pl < 0 ? 'profit-neg' : 'profit-zero'; plHtml = `${pl >= 0 ? '+' : ''}${formatCurrency(Math.abs(pl))}
${plPct >= 0 ? '+' : ''}${plPct !== null ? plPct.toFixed(2) : '0.00'}%
`; } const priceDisplay = a._priceLoading ? `actualizando...` : `${formatCurrency(Number(a.precio_actual || 0))}`; let actions = ''; if (a.tipo === 'crypto') actions += ` `; if (a.tipo === 'skin') { const q = encodeURIComponent(a.nombre || ''); actions += `🔍 Steam `; } actions += ` `; const rowImg = a.steam_img ? `` : ''; // Wear badge para skins let wearBadge = ''; if (a.tipo === 'skin' && a.categoria && CS2_WEAR[a.categoria]) { const w = CS2_WEAR[a.categoria]; wearBadge = `${w.label}`; } // Mostrar precio en moneda original si existe const precioOrigDisplay = (a.moneda_orig && a.moneda_orig !== 'USD' && a.precio_actual_orig) ? `(${Number(a.precio_actual_orig).toLocaleString('es-AR')} ${a.moneda_orig})` : ''; return `
${rowImg}
${esc(a.nombre)}${wearBadge} ${a.simbolo ? `${esc(a.simbolo.toUpperCase())}` : ''}
${esc(a.tipo || 'otro')} ${a.cantidad || '—'} ${a.precio_compra ? formatCurrency(Number(a.precio_compra)) : '—'} ${priceDisplay} ${precioOrigDisplay} ${formatCurrency(val)} ${plHtml} ${actions} `; } // ---------------------------------------------------------------- // Cargar customCats desde settings al iniciar // ---------------------------------------------------------------- const _origLoadSettings = window._fbSave; // Hook en onSnapshot de settings ya existente — // cuando se carguen settings, incluir _customCats const _origFinananceSnapshot = null; // Parchar el onSnapshot handler (simplificado: cargar al inicio) setTimeout(() => { if (state && state._customCats === undefined) state._customCats = []; }, 500); console.log('✅ MindNox v5.0 — asset modal mejorado, wear CS2, moneda en activos, categorías custom finanzas'); // ---------------------------------------------------------------- // ALIASES Y FUNCIONES FALTANTES // ---------------------------------------------------------------- // Alias: buildCatSelect → renderCatSelector // Alias: detectWear → parseWearFromName function detectWear(asset) { if (asset.categoria && WEAR_COLORS && WEAR_COLORS[asset.categoria]) return asset.categoria; return typeof parseWearFromName === 'function' ? parseWearFromName(asset.nombre) : null; } // Badge HTML para wear function wearBadgeHTML(wear) { if (!wear) return ''; const COLORS = { 'Factory New':'#22c55e','Minimal Wear':'#3b82f6','Field-Tested':'#ffa502','Well-Worn':'#ff6b35','Battle-Scarred':'#ff4757' }; const LABELS = { 'Factory New':'FN','Minimal Wear':'MW','Field-Tested':'FT','Well-Worn':'WW','Battle-Scarred':'BS' }; const color = COLORS[wear], label = LABELS[wear]; if (!color) return ''; return `${label}`; } // ================================================================ // FEATURES FALTANTES — categorías custom, export Word, modal activo // ================================================================ // ---------------------------------------------------------------- // SISTEMA CATEGORÍAS CUSTOM (por usuario, privado en Firestore) // ---------------------------------------------------------------- function getCats(scope) { return (state._customCats || []).filter(c => c.scope === scope); } function addCat(name, emoji, scope) { if (!state._customCats) state._customCats = []; const id = 'cat_' + Date.now().toString(36); state._customCats.push({ id, name: name.trim(), emoji: emoji || '📌', scope }); if (window._saveSettings) window._saveSettings(); return id; } function resolveCat(val, scope) { if (!val || val === '__new__') return 'Otro'; const cats = getCats(scope); const found = cats.find(c => c.id === val); if (found) return `${found.emoji} ${found.name}`; return val; } function buildCatSelect(id, scope, defaults, selected) { const cats = getCats(scope); const opts = [ ...defaults.map(d => ``), cats.length ? `` : '', ...cats.map(c => ``), `` ].filter(Boolean).join(''); return ``; } function handleNewCat(sel, scope, selId) { if (sel.value !== '__new__') return; sel.parentElement.querySelector('.new-cat-form')?.remove(); const form = document.createElement('div'); form.className = 'new-cat-form'; form.style.cssText = 'display:flex;gap:6px;margin-top:8px;align-items:center'; form.innerHTML = ` `; sel.parentElement.appendChild(form); setTimeout(() => document.getElementById('nce_emoji')?.focus(), 50); } function confirmNewCat(scope, selId) { const emoji = document.getElementById('nce_emoji')?.value?.trim() || '📌'; const name = document.getElementById('nce_name')?.value?.trim(); if (!name) { showToast('Escribí un nombre', 'warn', 2000); return; } const id = addCat(name, emoji, scope); const sel = document.getElementById(selId); if (!sel) return; const defaults = Array.from(sel.options) .filter(o => !o.disabled && o.value !== '__new__' && !o.value.startsWith('cat_')) .map(o => o.value); sel.parentElement.innerHTML = buildCatSelect(selId, scope, defaults, id); showToast(`✅ "${emoji} ${name}" guardada`, 'success', 2000); } // ---------------------------------------------------------------- // TIPO DE ACTIVO PERSONALIZADO // ---------------------------------------------------------------- function confirmNewAssetType() { const emoji = document.getElementById('nt_emoji')?.value?.trim() || '📦'; const name = document.getElementById('nt_name')?.value?.trim(); if (!name) { showToast('Escribí un nombre para el tipo', 'warn', 2000); return; } if (!state._customAssetTypes) state._customAssetTypes = []; const key = 'custom_' + Date.now().toString(36); state._customAssetTypes.push({ key, label: name.trim(), icon: emoji }); if (window._saveSettings) window._saveSettings(); // Reconstruir modal con nuevo tipo seleccionado document.getElementById('modal-body').innerHTML = buildAssetModalHTML(); const sel = document.getElementById('m-asset-tipo'); if (sel) { sel.value = key; onAssetTypeChange(key); } showToast(`✅ Tipo "${emoji} ${name}" guardado`, 'success', 2000); } // ---------------------------------------------------------------- // MODAL ACTIVO v3 — tipo personalizado, skin hint útil, wear visual // ---------------------------------------------------------------- const WEAR_COLORS = { 'Factory New': { label:'FN', color:'#22c55e' }, 'Minimal Wear': { label:'MW', color:'#3b82f6' }, 'Field-Tested': { label:'FT', color:'#ffa502' }, 'Well-Worn': { label:'WW', color:'#ff6b35' }, 'Battle-Scarred':{ label:'BS', color:'#ff4757' }, }; var ASSET_MODAL_HINTS = { crypto: '🪙 Ingresá el símbolo en el campo SÍMBOLO (BTC, ETH, SOL...) y la cantidad.\n✅ El precio actual se obtiene automáticamente de CoinGecko — dejalo en 0.', skin: '🎮 Escribí el nombre exacto tal como aparece en Steam Market.\nEj: M4A4 | Neo-Noir · AK-47 | Neon Rider · AWP | Asiimov\nElegí el desgaste con los botones de abajo.\n✅ Precio se actualiza con el botón 🔄 Steam.', banco: '🏦 Nombre del banco/cuenta. Ingresá el saldo como precio actual.', efectivo: '💵 Ingresá el monto total como precio actual.', accion: '📈 Ticker de la acción (AAPL, MSFT...), cantidad y precios.', inversion: '💼 FCI, bonos, etc. Capital invertido como precio de compra.', otro: '📦 Cualquier activo. Nombre, valor y precio de compra.', }; function buildAssetModalHTML() { const customTypes = state._customAssetTypes || []; const curSel = _baseCurrency || 'USD'; const typeOpts = [ ``, ``, ``, ``, ``, ``, ``, customTypes.length ? `` : '', ...customTypes.map(t => ``), `` ].filter(Boolean).join(''); return `
`; } function onAssetTypeChange(val) { if (val === '__new_type__') { document.getElementById('new-type-form').style.display = 'block'; const hint = document.getElementById('m-asset-hint'); if (hint) hint.textContent = ''; return; } document.getElementById('new-type-form').style.display = 'none'; // Hint principal const hint = document.getElementById('m-asset-hint'); if (hint) hint.textContent = ASSET_MODAL_HINTS[val] || ASSET_MODAL_HINTS.otro; // Mostrar/ocultar campos según tipo const simGrp = document.getElementById('m-simbolo-group'); if (simGrp) simGrp.style.display = (val==='crypto'||val==='accion') ? 'block' : 'none'; const wearGrp = document.getElementById('m-wear-group'); if (wearGrp) wearGrp.style.display = val==='skin' ? 'block' : 'none'; // Placeholder del nombre const placeholders = {skin:'M4A4 | Neo-Noir',crypto:'Bitcoin',banco:'Banco Galicia',efectivo:'Efectivo ARS',accion:'Ej: Apple Inc. (opcional)',inversion:'FCI Sigma',otro:'Mi activo'}; const inp = document.getElementById('m-title'); if (inp) inp.placeholder = placeholders[val] || 'Nombre del activo'; // Label nombre dinámico const nomLabel = document.getElementById('m-nombre-label'); if (nomLabel) nomLabel.textContent = val==='accion' ? 'Nombre de la empresa (opcional)' : 'Nombre'; // Ticker: label + hint + placeholder dinámico const simLabel = document.getElementById('m-sim-label'); const simHint = document.getElementById('m-sim-hint'); const simInp = document.getElementById('m-simbolo'); if (simLabel) { if (val==='accion') { simLabel.innerHTML = 'Ticker *'; if (simHint) simHint.innerHTML = 'USA: AAPL · TSLA · MSFT  ·  Argentina: GGAL.BA · YPFD.BA  ·  Cedears: MELI · GLOB'; if (simInp) simInp.placeholder = 'Ej: AAPL, GGAL.BA, MELI...'; } else if (val==='crypto') { simLabel.innerHTML = 'Símbolo *'; if (simHint) simHint.textContent = ''; if (simInp) simInp.placeholder = 'BTC, ETH, SOL...'; } else { simLabel.textContent = 'Símbolo'; if (simHint) simHint.textContent = ''; } } // Precio actual: placeholder según tipo const precActual = document.getElementById('m-precio-actual'); if (precActual) { if (val==='crypto') precActual.placeholder = '0 — se obtiene automáticamente'; else if (val==='accion') precActual.placeholder = '0 — se actualiza con 🔄 Acciones'; else if (val==='skin') precActual.placeholder = '0 — actualizar con 🔄 Steam'; else precActual.placeholder = '0.00'; } } function updateAssetHint() { onAssetTypeChange(document.getElementById('m-asset-tipo')?.value||'crypto'); } function selectWear(btn, wear) { document.querySelectorAll('#wear-btns .wear-btn').forEach(b => { const w = WEAR_COLORS[b.dataset.wear]; if (w) { b.style.opacity='0.4'; b.style.boxShadow='none'; } }); btn.style.opacity = '1'; btn.style.boxShadow = `0 0 8px ${WEAR_COLORS[wear]?.color||'#fff'}70`; const cat = document.getElementById('m-categoria'); if (cat) cat.value = wear; } // ---------------------------------------------------------------- // OPENMODAL con categorías custom en fin, presupuesto, activo // ---------------------------------------------------------------- const BUDGET_DEFAULT_CATS = ['Comida','Transporte','Entretenimiento','Gaming','Salud','Trabajo','Ahorro','Otro']; function openModal(type, prefillDate) { const form = modalForms[type]; if (!form) return; state.currentModal = type; document.getElementById('modal-title').innerHTML = form.title + ' '; document.getElementById('modal-body').innerHTML = form.body; if (prefillDate) { const de=document.getElementById('m-date'); if(de) de.value=prefillDate; } if (type === 'asset') { document.getElementById('modal-body').innerHTML = buildAssetModalHTML(); setTimeout(() => onAssetTypeChange('crypto'), 10); } if (type === 'fin') { setTimeout(() => { const wrap = document.getElementById('fin-cat-wrap'); if (wrap) wrap.innerHTML = buildCatSelect('m-cat','fin',FIN_DEFAULT_CATS,''); const cur=document.getElementById('m-fin-cur'), lbl=document.getElementById('m-cur-label'); if (cur&&_baseCurrency!=='USD') { cur.value=_baseCurrency; if(lbl) lbl.textContent=_baseCurrency; } }, 30); } if (type === 'budget') { setTimeout(() => { const wrap = document.getElementById('budget-cat-wrap'); if (wrap) wrap.innerHTML = buildCatSelect('m-bcat','budget',BUDGET_DEFAULT_CATS,''); const cur=document.getElementById('m-budget-cur'), lbl=document.getElementById('budget-cur-lbl'); if (cur&&_baseCurrency!=='USD') { cur.value=_baseCurrency; if(lbl) lbl.textContent=_baseCurrency; } }, 30); } if (type === 'recurring') { setTimeout(() => { updateRecurringPreview(); const cur=document.getElementById('m-rec-cur'), lbl=document.getElementById('rec-cur-label'); if (cur&&_baseCurrency!=='USD') { cur.value=_baseCurrency; if(lbl) lbl.textContent=_baseCurrency; } }, 30); } document.getElementById('modal-overlay').classList.add('open'); setTimeout(() => { const f=document.querySelector('#modal-body input,#modal-body textarea'); if(f) f.focus(); }, 80); } // ---------------------------------------------------------------- // SAVEMODAL con resolveCat y activo mejorado // ---------------------------------------------------------------- async function saveModal() { const type=state.currentModal; const id=Date.now().toString(36)+Math.random().toString(36).slice(2,5); const today=new Date().toISOString().split('T')[0]; if (type==='task') { const t=document.getElementById('m-title')?.value?.trim(); if(!t){showFieldError('m-title','Escribí un título');return;} state.tasks.unshift({id,title:t,desc:document.getElementById('m-desc')?.value||'',category:document.getElementById('m-cat')?.value||'personal',priority:document.getElementById('m-priority')?.value||'low',due:document.getElementById('m-due')?.value||'',done:false}); save('tasks'); } else if (type==='fin') { const d=document.getElementById('m-title')?.value?.trim(), a=document.getElementById('m-amount')?.value; if(!d){showFieldError('m-title','Escribí una descripción');return;} if(!a||Number(a)<=0){showFieldError('m-amount','Ingresá un monto válido');return;} const rawAmt=Number(a), finCur=document.getElementById('m-fin-cur')?.value||'USD'; const amtUSD=finCur==='USD'?rawAmt:finCur==='ARS'?rawAmt/(_exchangeRates.ARS||1):rawAmt/(_exchangeRates.UYU||1); const date=document.getElementById('m-date')?.value||today; const catRaw=document.getElementById('m-cat')?.value||'Otro'; const cat=resolveCat(catRaw,'fin'); state.finances.unshift({id,desc:d,amount:amtUSD,amountOrig:rawAmt,currencyOrig:finCur,type:document.getElementById('m-type')?.value||'gasto',cat,date}); save('finances'); showToast(`${document.getElementById('m-type')?.value==='ingreso'?'Ingreso':'Gasto'}: ${formatCurrency(amtUSD)}${finCur!=='USD'?` (${rawAmt.toLocaleString('es-AR')} ${finCur})`:''}`, 'success'); } else if (type==='asset') { const nombre=document.getElementById('m-title')?.value?.trim(); if(!nombre){showFieldError('m-title','Escribí un nombre');return;} const tipo=document.getElementById('m-asset-tipo')?.value||'otro'; if(tipo==='__new_type__'){showToast('Primero creá el tipo de activo','warn',2500);return;} const assetCur=document.getElementById('m-asset-cur')?.value||'USD'; const rate=assetCur==='ARS'?(_exchangeRates.ARS||1):assetCur==='UYU'?(_exchangeRates.UYU||1):1; const rawCompra=parseFloat(document.getElementById('m-precio-compra')?.value)||0; const rawActual=parseFloat(document.getElementById('m-precio-actual')?.value)||0; const newAsset={id,nombre,tipo, simbolo:document.getElementById('m-simbolo')?.value?.trim()||'', categoria:document.getElementById('m-categoria')?.value?.trim()||'', cantidad:document.getElementById('m-cantidad')?.value?Number(document.getElementById('m-cantidad').value):null, precio_compra:rawCompra?rawCompra/rate:null,precio_compra_orig:rawCompra||null, precio_actual:rawActual?rawActual/rate:0,precio_actual_orig:rawActual||null, moneda_orig:assetCur,fecha:document.getElementById('m-date')?.value||today}; state.assets.push(newAsset); save('assets'); closeModal(); renderPage(document.querySelector('.page.active')?.id?.replace('page-','')||'patrimonio'); updateStats(); if(tipo==='crypto'&&(newAsset.simbolo||newAsset.nombre)){ newAsset._priceLoading=true; renderPatrimonio(); const p=await fetchCryptoPrice(newAsset.simbolo||newAsset.nombre); newAsset._priceLoading=false; if(p!==null){newAsset.precio_actual=p;state._patLastUpdate=Date.now();save('assets');} renderPatrimonio(); updateStats(); } if(tipo==='skin'&&newAsset.nombre) fetchSkinImg(newAsset); return; } else if (type==='note') { const t=document.getElementById('m-title')?.value?.trim(); if(!t) return; state.notes.unshift({id,title:t,body:document.getElementById('m-body')?.value||'',date:today}); save('notes'); } else if (type==='event') { const t=document.getElementById('m-title')?.value?.trim(); if(!t) return; state.events.push({id,title:t,date:document.getElementById('m-date')?.value||today,desc:document.getElementById('m-desc')?.value||''}); save('events'); } else if (type==='goal') { const t=document.getElementById('m-title')?.value?.trim(); if(!t) return; state.goals.push({id,title:t,current:Number(document.getElementById('m-current')?.value)||0,target:Number(document.getElementById('m-target')?.value)||100,unit:document.getElementById('m-unit')?.value||'',due:document.getElementById('m-due')?.value||''}); save('goals'); } else if (type==='book') { const t=document.getElementById('m-title')?.value?.trim(); if(!t) return; state.books.push({id,title:t,author:document.getElementById('m-author')?.value||'',status:document.getElementById('m-status')?.value||'pendiente',notes:document.getElementById('m-notes')?.value||''}); save('books'); } else if (type==='trip') { const d=document.getElementById('m-dest')?.value?.trim(); if(!d) return; state.trips.push({id,dest:d,from:document.getElementById('m-from')?.value||'',to:document.getElementById('m-to')?.value||'',notes:document.getElementById('m-notes')?.value||''}); save('trips'); } else if (type==='budget') { const catRaw=document.getElementById('m-bcat')?.value||'Otro'; const cat=resolveCat(catRaw,'budget'); const rawLim=Number(document.getElementById('m-blimit')?.value)||0; const budgetCur=document.getElementById('m-budget-cur')?.value||'USD'; const rate=budgetCur==='ARS'?(_exchangeRates.ARS||1):budgetCur==='UYU'?(_exchangeRates.UYU||1):1; const limUSD=rawLim/rate; if(!limUSD) return; const ex=state.budgets.findIndex(b=>b.cat===cat); if(ex>=0){state.budgets[ex].limit=limUSD;state.budgets[ex].limitOrig=rawLim;state.budgets[ex].currency=budgetCur;} else state.budgets.push({id,cat,limit:limUSD,limitOrig:rawLim,currency:budgetCur}); save('budgets'); showToast(`Presupuesto ${cat}: ${formatCurrency(limUSD)}/mes`,'success'); } else if (type==='recurring') { const t=document.getElementById('m-title')?.value?.trim(),a=document.getElementById('m-amount')?.value; if(!t||!a) return; const rawAmt=Number(a),recCur=document.getElementById('m-rec-cur')?.value||'USD'; const amtUSD=recCur==='USD'?rawAmt:recCur==='ARS'?rawAmt/(_exchangeRates.ARS||1):rawAmt/(_exchangeRates.UYU||1); const freq=document.getElementById('m-freq')?.value||'monthly',day=parseInt(document.getElementById('m-day')?.value)||1; let cat=document.getElementById('m-cat')?.value||'Otro'; if(cat==='__custom__'){cat=document.getElementById('m-cat-custom')?.value?.trim()||'Otro';} if(!state.recurring) state.recurring=[]; state.recurring.push({id,name:t,amount:amtUSD,amountOrig:rawAmt,currencyOrig:recCur,day,freq,cat,active:true,paid:false,createdAt:today}); save('recurring'); showToast(`✅ "${t}" — ${formatCurrency(amtUSD)} agregado`,'success'); closeModal(); renderPage(document.querySelector('.page.active')?.id?.replace('page-','')||'recurring'); updateStats(); return; } else if (type==='habit') { const t=document.getElementById('m-title')?.value?.trim(); if(!t) return; if(!state.habits) state.habits=[]; const habitType=document.getElementById('m-habit-type')?.value||'daily'; const weekdays=habitType==='weekly'?Array.from(document.querySelectorAll('.wd-btn.selected')).map(b=>Number(b.dataset.day)):[]; state.habits.push({id,name:t,type:habitType,weekdays,goal:document.getElementById('m-habit-goal')?.value||'',key:document.getElementById('m-habit-key')?.checked||false,streak:0,log:[],createdAt:today}); save('habits'); showToast(`Hábito "${t}" creado`,'success'); closeModal(); renderPage(document.querySelector('.page.active')?.id?.replace('page-','')||'habitos'); updateStats(); return; } else if (type==='fin_edit') { await saveFinanceEdit(); return; } else if (type==='task_edit') { saveTaskEdit(); return; } showToast('Guardado','success',2000); closeModal(); renderPage(document.querySelector('.page.active')?.id?.replace('page-','')||'dashboard'); updateStats(); } // ---------------------------------------------------------------- // EXPORT PATRIMONIO — Word (.doc) con diseño // ---------------------------------------------------------------- function exportPatrimonioDocx() { const assets=state.assets; if(!assets.length){showToast('Sin activos para exportar','warn',2500);return;} const now=new Date(), fecha=now.toLocaleDateString('es-AR',{day:'2-digit',month:'2-digit',year:'numeric'}); const totalVal=assets.reduce((s,a)=>s+getAssetValue(a),0); const totalCost=assets.reduce((s,a)=>s+getAssetCost(a),0); const totalPL=totalVal-totalCost; const plPct=totalCost>0?((totalPL/totalCost)*100).toFixed(2):'0.00'; const byType={}; assets.forEach(a=>{const t=a.tipo||'otro';if(!byType[t])byType[t]=[];byType[t].push(a);}); const docHtml=`
Sistema de Control Personal
Reporte de Patrimonio
Generado el ${fecha}

${formatCurrency(totalVal)}
${formatCurrency(totalCost)}
${totalPL>=0?'+':''}${formatCurrency(totalPL)}
${totalPL>=0?'+':''}${plPct}%
${Object.entries(byType).map(([tipo,items])=>{ const catTotal=items.reduce((s,a)=>s+getAssetValue(a),0); const icons={crypto:'🪙',skin:'🎮',banco:'🏦',efectivo:'💵',accion:'📈',inversion:'💼',otro:'📦'}; return `
${icons[tipo]||'📦'} ${tipo.charAt(0).toUpperCase()+tipo.slice(1)} — ${formatCurrency(catTotal)}
${items.map(a=>{ const val=getAssetValue(a),cost=getAssetCost(a),pl=cost>0?val-cost:null,plp=cost>0&&pl!==null?(pl/cost*100):null; const wear=a.tipo==='skin'?detectWear(a):null; const wt=wear&&WEAR_COLORS[wear]?` [${WEAR_COLORS[wear].label}]`:''; return ``; }).join('')}
ActivoCant.P. CompraP. ActualTotalP&LRend.
${a.nombre}${wt}${a.simbolo?` ${a.simbolo.toUpperCase()}`:''} ${a.cantidad||'—'}${a.precio_compra?formatCurrency(Number(a.precio_compra)):'—'} ${formatCurrency(Number(a.precio_actual||0))}${formatCurrency(val)} ${pl!==null?(pl>=0?'+':'')+formatCurrency(Math.abs(pl)):'—'} ${plp!==null?(plp>=0?'+':'')+plp.toFixed(2)+'%':'—'}
`; }).join('')}
Mind Nox · Reporte generado el ${fecha} · mind.noxbase.space
`; const blob=new Blob(['\uFEFF'+docHtml],{type:'application/msword;charset=utf-8'}); const url=URL.createObjectURL(blob);const a=document.createElement('a'); a.href=url;a.download=`mindnox_patrimonio_${now.toISOString().slice(0,10)}.doc`; document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url); showToast('✅ Patrimonio exportado como Word','success',3000); } function exportPatrimonioCSV() { exportPatrimonioDocx(); } // ---------------------------------------------------------------- // EXPORT FINANZAS — Word (.doc) con diseño // ---------------------------------------------------------------- function exportFinancesDocx() { const fins=[...state.finances].sort((a,b)=>a.date>b.date?1:-1); if(!fins.length){showToast('Sin movimientos para exportar','warn',2500);return;} const now=new Date(), fecha=now.toLocaleDateString('es-AR',{day:'2-digit',month:'2-digit',year:'numeric'}); const monLabel=`${MONTH_NAMES[state.finMonth]} ${state.finYear}`; const ck=finMonthStr(state.finYear,state.finMonth); const monthFins=fins.filter(f=>getFinMonthKey(f)===ck); const ing=monthFins.filter(f=>f.type==='ingreso').reduce((s,f)=>s+Number(f.amount),0); const gas=monthFins.filter(f=>f.type==='gasto').reduce((s,f)=>s+Number(f.amount),0); const bal=ing-gas; const byCat={}; monthFins.filter(f=>f.type==='gasto').forEach(f=>{const c=f.cat||'Otro';if(!byCat[c])byCat[c]=0;byCat[c]+=Number(f.amount);}); const topCats=Object.entries(byCat).sort((a,b)=>b[1]-a[1]); const docHtml=`
Sistema de Control Personal
Reporte de Finanzas — ${monLabel}
Generado el ${fecha}

${formatCurrency(ing)}
${formatCurrency(gas)}
${bal>=0?'+':''}${formatCurrency(bal)}
${topCats.length?`
Top categorías: ${topCats.slice(0,5).map(([c,v])=>`${c} ${formatCurrency(v)}`).join(' · ')}
`:''}
📋 Movimientos — ${monLabel}
${monthFins.map(f=>{ const orig=f.currencyOrig&&f.currencyOrig!=='USD'?` (${Number(f.amountOrig||0).toLocaleString('es-AR')} ${f.currencyOrig})`:''; return ``; }).join('')}
DescripciónCategoríaFechaTipoMonto
${f.desc||'—'}${f.cat||'—'}${f.date||'—'} ${f.type==='ingreso'?'Ingreso':'Gasto'} ${f.type==='ingreso'?'+':'-'}${formatCurrency(Number(f.amount))}${orig}
Mind Nox · Reporte de ${monLabel} · mind.noxbase.space
`; const blob=new Blob(['\uFEFF'+docHtml],{type:'application/msword;charset=utf-8'}); const url=URL.createObjectURL(blob);const a=document.createElement('a'); a.href=url;a.download=`mindnox_finanzas_${MONTH_NAMES[state.finMonth]}_${state.finYear}.doc`; document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url); showToast('✅ Finanzas exportadas como Word','success',3000); } function exportFinancesCSV() { exportFinancesDocx(); } // Actualizar modal presupuesto con moneda + custom if (typeof modalForms !== 'undefined') { modalForms.budget = { title: 'Nuevo Presupuesto', body: `
` }; modalForms.fin.body = `
`; } // Cargar customCats desde Firestore al iniciar window._loadCustomCatsFromSettings = function(d) { if (d.customCats) state._customCats = d.customCats; if (d.customAssetTypes) state._customAssetTypes = d.customAssetTypes; }; console.log('✅ MindNox — categorías custom, wear CS2, activos custom, export Word');