Campos completados con tus datos. Podés editarlos.
Ingresa solo números — podés editar cualquier campo
₲
₲
₲
₲
Resultado
Completa los campos para ver el análisis
Visualización del mes actual
Compará lo que necesitás vender para no perder dinero contra lo que vendiste este mes.
Completa los parámetros para ver el gráfico
Proyecciones
¿Cuánto necesitas vender para cubrir el punto de equilibrio en los próximos meses?
Reportes
Estado financiero de tu negocio
CALCULANDO...
Registra ventas y gastos para ver el análisis
Estado de Resultados
📈 Total Ventas₲ 0
🛒 Costo de Compras₲ 0
= Ganancia Bruta₲ 0
📋 Gastos Fijos₲ 0
📦 Gastos Variables₲ 0
= GANANCIA NETA₲ 0
Análisis Rápido
Registra movimientos para ver el análisis
Productos más rentables
Sin datos
Plan de Negocio
Envía tus datos financieros reales a Planifica · Código Creativa
Tu situación financiera actual
Ventas este mes
₲ 0
Costos este mes
₲ 0
Ganancia neta
₲ 0
Historial
Top productos
Planifica
por Código Creativa
El archivo exportado incluye tu semáforo financiero, top 5 productos y tendencia de los últimos 3 meses — todo listo para construir proyecciones reales en Planifica.
Tu panel de control financiero simplificado. Muestra cuánto vendiste, cuánto gastaste, si tu negocio está ganando o perdiendo, y cuánto necesitas vender para no perder dinero. Sin contabilidad compleja.
📌 Cómo empezar en 5 pasos
1Agrega tus productos al Inventario — nombre, stock, precio de costo y precio de venta.
2Registra cada venta — el stock se descuenta automáticamente si el producto existe.
3Registra tus gastos — sepáralos en "fijos" y "variables". Clave para el Punto de Equilibrio.
4Revisa Reportes — el semáforo GANANCIAS/ZONA AMARILLA/PÉRDIDAS te dice el estado de un vistazo.
5Punto de Equilibrio — ingresa tus gastos fijos y precio promedio para saber cuántas ventas necesitas.
🚦 ¿Qué significa el semáforo?
GANANCIAS (verde): tus ventas superan todos tus gastos en más de 20% de margen. ZONA AMARILLA: margen bajo, revisa tus gastos. PÉRDIDAS (rojo): tus gastos superan las ventas — acción inmediata.
📊 Excel analizado
El botón "Excel" genera un reporte con tablas pivot de ventas por producto, gastos por categoría e inventario completo. Ábrelo directamente en Excel o cualquier navegador.
💾 Exportar / Cargar sesión
Usa "Exportar sesión" para guardar todos tus datos en un archivo. Si cambias de dispositivo, carga ese archivo con "Cargar sesión". La app también guarda automáticamente cada 60 segundos.
¿Qué es el gasto variable por venta?
La idea principal
Son todos los costos que existen solo porque vendiste. Si no vendiste nada en el mes, este gasto no existe. Cambia directamente según cuánto vendes.
📦 Productos físicos
Materia prima e insumos · Empaque y etiquetas · Costo del producto que revendés
Ejemplo: Vendés empanadas a ₲ 5.000 cada una. Para hacer cada empanada gastás ₲ 2.000 en harina, carne y aceite. Esos ₲ 2.000 son el gasto variable.
🛠 Servicios
Si para dar el servicio necesitás comprar algo → ese costo va acá.
Si solo usás tu tiempo y conocimiento → el gasto variable es 0.
🧭 La regla simple
Preguntate: "¿Este gasto existe si no vendo nada este mes?"
NO existe → es variable, va acá SÍ existe igual → es fijo, va en Gastos fijos mensuales
';
var w=window.open('','_blank');
if(!w){alert('Permite ventanas emergentes para exportar el reporte.');return;}
w.document.write(h);w.document.close();
w.focus();
setTimeout(function(){try{w.print();}catch(e){}},800);
}
/* ── Excel ── */
function exportarExcelHTML(){
_xlsxOk(function(){
var periodo=gv('reportePeriodo')||'mes';
var filterFn=getReporteFilterFn(periodo);
var periodoLabel=periodo==='semana'?'Esta semana':periodo==='mes'?'Este mes':
periodo==='trimestre'?'Últimos 3 meses':periodo==='semestre'?'Últimos 6 meses':
periodo==='anio'?'Este año':'Todo el tiempo';
var negocio=gv('planNombre')||'Mi Negocio';
var hoy=new Date();var sym=moneda.symbol;
var vFilt=ventas.filter(function(v){return filterFn(v.fecha);});
var gFilt=gastos.filter(function(g){return filterFn(g.fecha);});
var cFilt=compras.filter(function(c){return filterFn(c.fecha);});
var vTotal=vFilt.reduce(function(a,v){return a+v.total;},0);
var cTotal=cFilt.reduce(function(a,c){return a+c.total;},0);
var gFijos=gFilt.filter(function(g){return g.tipo==='fijo';}).reduce(function(a,g){return a+g.monto;},0);
var gVarTotal=gFilt.filter(function(g){return g.tipo==='variable';}).reduce(function(a,g){return a+g.monto;},0);
var gNeta=vTotal-cTotal-gFijos-gVarTotal;var margen=vTotal>0?Math.round(gNeta/vTotal*100):0;
var wb=XLSX.utils.book_new();
var ws1Data=[['REPORTE FINANCIERO — '+negocio,'',''],['Período: '+periodoLabel,'',''],['','',''],['ESTADO DE RESULTADOS','MONTO','%'],['Total Ventas',vTotal,1],['Costo Compras',cTotal,vTotal>0?cTotal/vTotal:0],['Ganancia Bruta',vTotal-cTotal,vTotal>0?(vTotal-cTotal)/vTotal:0],['Gastos Fijos',gFijos,vTotal>0?gFijos/vTotal:0],['Gastos Variables',gVarTotal,vTotal>0?gVarTotal/vTotal:0],[gNeta>=0?'GANANCIA NETA':'PÉRDIDA NETA',Math.abs(gNeta),Math.abs(margen)/100]];
XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(ws1Data),'Resumen');
var ws2Data=[['Fecha','Producto/Servicio','Cantidad','Precio Unit.','Total','Método','Estado']];
vFilt.forEach(function(v){ws2Data.push([v.fecha,v.nombre,v.cantidad,v.precio,v.total,v.metodo,v.estado]);});
XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(ws2Data),'Ventas');
var ws3Data=[['Fecha','Descripción','Categoría','Tipo','Monto']];
gFilt.forEach(function(g){ws3Data.push([g.fecha,g.descripcion,g.categoria,g.tipo,g.monto]);});
XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(ws3Data),'Gastos');
if(productos.length){
var ws4Data=[['Producto','Categoría','Cód. Barra','Stock','Stock Mín','Unidad','Precio Costo','Precio Público','Precio Mayorista','Margen']];
productos.forEach(function(p){ws4Data.push([p.nombre,p.categoria,p.codBarra||'',p.stock,p.stockMin,p.unidad,p.costo,p.venta,p.ventaMay||0,p.margen]);});
XLSX.utils.book_append_sheet(wb,XLSX.utils.aoa_to_sheet(ws4Data),'Inventario');
}
var filename='Reporte_'+negocio.replace(/\s+/g,'_')+'_'+hoy.getFullYear()+(hoy.getMonth()+1)+hoy.getDate()+'.xlsx';
XLSX.writeFile(wb,filename);
toast('Excel exportado ✓','ok');
});
}
/* ── Plan de Negocio ── */
function renderPlanSection(){
var sym=moneda.symbol;
var vm=totalVentasMes();
var gm=totalGastosMes();
var cm=compras.filter(function(c){return isThisMonth(c.fecha);}).reduce(function(a,c){return a+c.total;},0);
var gn=vm-gm-cm;
var margen=vm>0?Math.round(gn/vm*100):0;
var semaforo=gn>0&&margen>=20?'verde':gn>=0?'amarillo':'rojo';
var semColors={verde:'#3d7010',amarillo:'#92600a',rojo:'#9f1f1f'};
var semBgs={verde:'#edf6e1',amarillo:'#fef3cd',rojo:'#fee2e2'};
var semBorders={verde:'rgba(105,170,45,.3)',amarillo:'rgba(245,158,11,.35)',rojo:'rgba(239,68,68,.3)'};
var semEmojis={verde:'🟢',amarillo:'🟡',rojo:'🔴'};
var semTexts={verde:'GANANCIAS',amarillo:'ZONA AMARILLA',rojo:'PÉRDIDAS'};
var semEl=_ge('planSemResumen');
if(semEl){
semEl.style.cssText='background:'+semBgs[semaforo]+';border:2px solid '+semBorders[semaforo]+';border-radius:12px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between';
semEl.innerHTML='
'+semEmojis[semaforo]+' '+semTexts[semaforo]+'
'
+'
Margen: '+margen+'% \xb7 Ventas este mes: '+fmtGs(vm)+'
'
+'
'+margen+'%
';
}
sv2('planKpiVentas',fmtGs(vm));sv2('planKpiCostos',fmtGs(gm+cm));sv2('planKpiGanancia',fmtGs(gn));
var ganCard=_ge('planKpiGananciaCard');if(ganCard)ganCard.style.background=gn>=0?'var(--green-light)':'var(--red-light)';
var ganEl=_ge('planKpiGanancia');if(ganEl)ganEl.style.color=gn>=0?'var(--green)':'var(--red)';
// Historial dinámico según selector
var nMeses=parseInt(gv('planHistPeriodo')||'3',10);
var hoy=new Date();var hist=[];
var mNames=['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'];
for(var mi=nMeses;mi>=1;mi--){
var dh=new Date(hoy.getFullYear(),hoy.getMonth()-mi,1);
var yh=dh.getFullYear(),mh=dh.getMonth();
var nm=mNames[mh]+' '+yh;
var hvv=ventas.filter(function(v){var dt=new Date(v.fecha+'T12:00:00');return dt.getFullYear()===yh&&dt.getMonth()===mh;}).reduce(function(a,v){return a+v.total;},0);
var hgc=gastos.filter(function(g){var dt=new Date(g.fecha+'T12:00:00');return dt.getFullYear()===yh&&dt.getMonth()===mh;}).reduce(function(a,g){return a+g.monto;},0);
var hcc=compras.filter(function(c){var dt=new Date(c.fecha+'T12:00:00');return dt.getFullYear()===yh&&dt.getMonth()===mh;}).reduce(function(a,c){return a+c.total;},0);
hist.push({mes:nm,ventas:hvv,ganancia:hvv-hgc-hcc});
}
var nmAct=mNames[hoy.getMonth()]+' '+hoy.getFullYear();
hist.push({mes:nmAct,ventas:vm,ganancia:gn});
var maxH=Math.max.apply(null,hist.map(function(h){return h.ventas||0;}))||1;
var tendEl=_ge('planTendencia3m');var tendBars=_ge('planTendenciaBarras');
var histLbl=_ge('planHistLabel');if(histLbl)histLbl.textContent='Historial';
if(tendEl&&tendBars&&hist.some(function(h){return h.ventas>0;})){
var gap=nMeses<=3?12:nMeses<=6?6:3;
tendBars.style.gap=gap+'px';
tendBars.innerHTML=hist.map(function(hh){
var pct=Math.max(6,Math.round((hh.ventas/maxH)*100));
var col=hh.ganancia>=0?'var(--green)':'var(--red)';
var isAct=hh.mes===nmAct;
return'
';
}).join('');
tendEl.style.display='block';
}else if(tendEl){tendEl.style.display='none';}
// Top del período seleccionado — usa selector planTopLimit
var limPlanTop=parseInt(((_ge('planTopLimit')||{}).value)||5);
var dTop=new Date(hoy.getFullYear(),hoy.getMonth()-nMeses,1);
var topMap={};
ventas.filter(function(v){return new Date(v.fecha+'T12:00:00')>=dTop;}).forEach(function(v){topMap[v.nombre]=(topMap[v.nombre]||0)+v.total;});
var topArr=Object.keys(topMap).map(function(k){return[k,topMap[k]];}).sort(function(a,b){return b[1]-a[1];}).slice(0,limPlanTop);
var topEl=_ge('planTopProds'),topList=_ge('planTopProdsList');
if(topEl&&topList&&topArr.length){
var totalTop=topArr.reduce(function(a,e){return a+e[1];},0)||1;
topList.innerHTML=topArr.map(function(e,i){
var pct=Math.round(e[1]/totalTop*100);
return'
';
}).join('');
topEl.style.display='block';
}else if(topEl){topEl.style.display='none';}
}
/* ── Enviar a Planifica ── */
function exportarParaPlanifica(){
var negocio=gv('planNombre')||'Mi Negocio';
var hoy=new Date();
var vMes=totalVentasMes();
var gFijosMes=gastos.filter(function(g){return isThisMonth(g.fecha)&&g.tipo==='fijo';}).reduce(function(a,g){return a+g.monto;},0);
var gVarMes=gastos.filter(function(g){return isThisMonth(g.fecha)&&g.tipo==='variable';}).reduce(function(a,g){return a+g.monto;},0);
var cMes=compras.filter(function(c){return isThisMonth(c.fecha);}).reduce(function(a,c){return a+c.total;},0);
var gNetaMes=vMes-gFijosMes-gVarMes-cMes;
var margenMes=vMes>0?Math.round(gNetaMes/vMes*100):0;
// Top productos — historial completo
var topProds={};
ventas.forEach(function(v){topProds[v.nombre]=(topProds[v.nombre]||0)+v.total;});
var topSorted=Object.keys(topProds).map(function(k){return{nombre:k,total:topProds[k]};}).sort(function(a,b){return b.total-a.total;}).slice(0,5);
// Historial 6 meses (5 anteriores + mes actual)
var mNombres=['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'];
var historial6=[];
for(var mi=5;mi>=1;mi--){
var dh=new Date(hoy.getFullYear(),hoy.getMonth()-mi,1);
var hy=dh.getFullYear(),hm=dh.getMonth();
var hvv=ventas.filter(function(v){var dt=new Date(v.fecha+'T12:00:00');return dt.getFullYear()===hy&&dt.getMonth()===hm;}).reduce(function(a,v){return a+v.total;},0);
var hgf=gastos.filter(function(g){var dt=new Date(g.fecha+'T12:00:00');return dt.getFullYear()===hy&&dt.getMonth()===hm&&g.tipo==='fijo';}).reduce(function(a,g){return a+g.monto;},0);
var hgv=gastos.filter(function(g){var dt=new Date(g.fecha+'T12:00:00');return dt.getFullYear()===hy&&dt.getMonth()===hm&&g.tipo==='variable';}).reduce(function(a,g){return a+g.monto;},0);
var hcc=compras.filter(function(c){var dt=new Date(c.fecha+'T12:00:00');return dt.getFullYear()===hy&&dt.getMonth()===hm;}).reduce(function(a,c){return a+c.total;},0);
var hNeta=hvv-hgf-hgv-hcc;
historial6.push({mes:mNombres[hm]+' '+hy,ventas:hvv,costos:hgf+hgv+hcc,gastosFijos:hgf,gastosVariables:hgv,compras:hcc,ganancia:hNeta,margenPct:hvv>0?Math.round(hNeta/hvv*100):0});
}
var nmAct=mNombres[hoy.getMonth()]+' '+hoy.getFullYear();
historial6.push({mes:nmAct,ventas:vMes,costos:gFijosMes+gVarMes+cMes,gastosFijos:gFijosMes,gastosVariables:gVarMes,compras:cMes,ganancia:gNetaMes,margenPct:margenMes});
// Promedios 3 y 6 meses (solo meses con datos)
function promCampo(arr,field){var c=arr.filter(function(h){return h.ventas>0;});return c.length?Math.round(c.reduce(function(a,h){return a+h[field];},0)/c.length):0;}
var h3=historial6.slice(2,5); // 3 meses anteriores al actual
var h6=historial6.slice(0,5); // 5 meses anteriores al actual
var prom3V=promCampo(h3,'ventas');var prom6V=promCampo(h6,'ventas');
var prom3M=promCampo(h3,'margenPct');var prom6M=promCampo(h6,'margenPct');
// Tendencia: segunda mitad vs primera mitad del historial de 6 meses
var vHist=h6.map(function(h){return h.ventas;});
var mid=Math.floor(vHist.length/2);
var p1=vHist.slice(0,mid).filter(Boolean),p2=vHist.slice(mid).filter(Boolean);
var a1=p1.length?p1.reduce(function(a,b){return a+b;},0)/p1.length:0;
var a2=p2.length?p2.reduce(function(a,b){return a+b;},0)/p2.length:0;
var tendenciaPct=a1>0?Math.round(((a2-a1)/a1)*100):0;
// Categorías de gastos
var catGastos={};
gastos.forEach(function(g){catGastos[g.categoria]=(catGastos[g.categoria]||0)+g.monto;});
// Semáforo
var semaforo=gNetaMes>0&&margenMes>=20?'verde':gNetaMes>=0?'amarillo':'rojo';
var data={
_source:'pulso360',_version:'v3.6',
exportedAt:hoy.toISOString(),
negocio:{nombre:negocio,moneda:{code:moneda.code,symbol:moneda.symbol,name:moneda.name}},
semaforo:semaforo,
financiero:{
mesActual:{ventas:vMes,gastosFijos:gFijosMes,gastosVariables:gVarMes,compras:cMes,gananciaNeta:gNetaMes,margenPct:margenMes},
promedioVentas3Meses:prom3V||Math.round(promedioVentas3Meses()),
promedioVentas6Meses:prom6V,
margenPromedio3Meses:prom3M,
margenPromedio6Meses:prom6M,
tendenciaPct:tendenciaPct,
historial3Meses:historial6.slice(-4),
historial6Meses:historial6,
topProductos:topSorted,
categoriaGastos:catGastos,
puntoEquilibrio:{costosFijos:gv('peCostosFijos'),precioVenta:gv('pePrecioVenta'),costoVariable:gv('peCostoVariable')}
}
};
var blob=new Blob([JSON.stringify(data,null,2)],{type:'application/json'});
var url=URL.createObjectURL(blob);
var a=document.createElement('a');
a.href=url;
a.download='Pulso_para_Planifica_'+negocio.replace(/\s+/g,'_')+'_'+hoy.toISOString().slice(0,10)+'.json';
document.body.appendChild(a);a.click();
setTimeout(function(){URL.revokeObjectURL(url);if(a.parentNode)a.parentNode.removeChild(a);},300);
toast('Exportado para Planifica \u2713','ok');
}
/* ── Stock alert views ── */
function renderStockPills(sinStock,bajoStock){
var sym=moneda.symbol;
var ps=_ge('pillsSinStock');var pb=_ge('pillsBajoStock');
if(!ps||!pb)return;
ps.innerHTML=sinStock.map(function(p){
return''
+''
+p.nombre
+'0/'+p.stockMin+'';
}).join('')||'—';
pb.innerHTML=bajoStock.map(function(p){
return''
+''
+p.nombre
+''+p.stock+'/'+p.stockMin+'';
}).join('')||'—';
}
function renderStockCats(sinStock,bajoStock){
var cSin=_ge('catsSinStock');var cBaj=_ge('catsBajoStock');
if(!cSin||!cBaj)return;
function buildCatGrid(prods){
var cats={};
prods.forEach(function(p){
var cat=p.categoria||'General';
if(!cats[cat])cats[cat]=[];
cats[cat].push(p);
});
if(!Object.keys(cats).length)return'
'
+cats[cat].map(function(p){
var isZero=p.stock===0;
var chip=''+p.stock+' / '+p.stockMin+'';
return'
'
+''+p.nombre+''
+chip+'
';
}).join('')
+'
';
}).join('');
}
cSin.innerHTML=buildCatGrid(sinStock);
cBaj.innerHTML=buildCatGrid(bajoStock);
}
function renderStockTabla(sinStock,bajoStock){
var tbody=_ge('stockTablaBody');if(!tbody)return;
var all=sinStock.concat(bajoStock);
tbody.innerHTML=all.map(function(p){
var isZero=p.stock===0;
var pct=p.stockMin>0?Math.round((p.stock/p.stockMin)*100):0;
var pctDisplay=Math.min(pct,100);
var chipStyle=isZero?'background:var(--red-light);color:var(--red)':'background:var(--amber-light);color:#92600a';
var chipText=isZero?'Sin stock':'Stock bajo';
var barColor=isZero?'var(--red)':'var(--amber)';
return'
'
+'
'+p.nombre+'
'
+'
'+chipText+'
'
+'
'
+'
'
+''+p.stock+' / '+p.stockMin+' '+p.unidad+''
+'
'
+'
'+pct+'%
'
+'
';
}).join('');
}
var _stockSortAsc=true;
function sortStockTable(col){
var tbody=_ge('stockTablaBody');if(!tbody)return;
var rows=Array.from(tbody.querySelectorAll('tr'));
rows.sort(function(a,b){
if(col===3){var av=parseInt(a.dataset.pct),bv=parseInt(b.dataset.pct);return _stockSortAsc?av-bv:bv-av;}
var av=a.cells[0].textContent.trim().toLowerCase(),bv=b.cells[0].textContent.trim().toLowerCase();
return _stockSortAsc?av.localeCompare(bv,'es'):bv.localeCompare(av,'es');
});
_stockSortAsc=!_stockSortAsc;
rows.forEach(function(r){tbody.appendChild(r);});
}
var _stockViewNames={pills:'Resumen',cats:'Categoría',tabla:'Tabla'};
function switchStockView(id){
['pills','cats','tabla'].forEach(function(v){
var el=_ge('stockView'+v.charAt(0).toUpperCase()+v.slice(1));
if(el)el.style.display=v===id?'block':'none';
});
var lbl=_ge('stockViewName');if(lbl)lbl.textContent=_stockViewNames[id]||id;
}
/* ── Paginación helpers — exported to global scope ── */
function pagVentasPrev(){if(ventasPagina>1){ventasPagina--;renderVentas();}}
function pagVentasNext(){var ps=parseInt(((_ge('ventasPageSize')||{value:'50'}).value));var tp=Math.max(1,Math.ceil(ventas.length/ps));if(ventasPagina1){comprasPagina--;renderCompras();}}
function pagComprasNext(){var ps=parseInt(((_ge('comprasPageSize')||{value:'50'}).value));var tp=Math.max(1,Math.ceil(compras.length/ps));if(comprasPagina1){gastosPagina--;renderGastos();}}
function pagGastosNext(){var ps=parseInt(((_ge('gastosPageSize')||{value:'50'}).value));var tp=Math.max(1,Math.ceil(gastos.length/ps));if(gastosPagina1){invPagina--;renderInventario();}}
function pagInvNext(){var ps=parseInt(((_ge('invPageSize')||{value:'50'}).value));var tp=Math.max(1,Math.ceil(productos.length/ps));if(invPagina fecha > texto
function _cmpVals(a,b,asc){
var na=parseFloat(String(a).replace(/[^\d,.-]/g,'').replace(/\./g,'').replace(',','.'));
var nb=parseFloat(String(b).replace(/[^\d,.-]/g,'').replace(/\./g,'').replace(',','.'));
if(!isNaN(na)&&!isNaN(nb)) return asc?na-nb:nb-na;
var da=new Date(a),db=new Date(b);
if(!isNaN(da)&&!isNaN(db)) return asc?da-db:db-da;
return asc?String(a).localeCompare(String(b),'es'):String(b).localeCompare(String(a),'es');
}
// Extrae el valor de sort de cada objeto según tabla y columna
function _getSortVal(obj,tableId,col){
if(tableId==='tablaVentas'){
return [obj.fecha,obj.fecha,obj.nombre,obj.cantidad,obj.precio,obj.total,obj.metodo,obj.estado][col]||'';
}
if(tableId==='tablaCompras'){
return [obj.fecha,obj.nombre,obj.proveedor,obj.cantidad,obj.costo,obj.total,obj.estado][col]||'';
}
if(tableId==='tablaGastos'){
return [obj.fecha,obj.descripcion,obj.categoria,obj.tipo,obj.monto][col]||'';
}
if(tableId==='tablaInventario'){
return [obj.nombre,obj.categoria,obj.codBarra||'',obj.stock,obj.stockMin,obj.unidad,obj.costo,obj.venta,obj.ventaMay||0,obj.margen][col]||'';
}
return '';
}
function sortTabla(tableId,colIdx){
var key=tableId+'-'+colIdx;
var asc=_sortState[key]!==true;
_sortState[key]=asc;
// Reset all previous sort indicators, set current
var table=_ge(tableId);
if(table){
table.querySelectorAll('th').forEach(function(th,i){
th.classList.remove('sort-asc','sort-desc');
if(i===colIdx&&th.classList.contains('sortable'))th.classList.add(asc?'sort-asc':'sort-desc');
});
}
// Sort the source array and re-render (works with pagination)
var arr, renderFn, resetPage;
if(tableId==='tablaVentas'){arr=ventas;renderFn=renderVentas;resetPage=function(){ventasPagina=1;};}
else if(tableId==='tablaCompras'){arr=compras;renderFn=renderCompras;resetPage=function(){comprasPagina=1;};}
else if(tableId==='tablaGastos'){arr=gastos;renderFn=renderGastos;resetPage=function(){gastosPagina=1;};}
else if(tableId==='tablaInventario'){arr=productos;renderFn=renderInventario;resetPage=function(){invPagina=1;};}
else return;
arr.sort(function(a,b){
return _cmpVals(_getSortVal(a,tableId,colIdx),_getSortVal(b,tableId,colIdx),asc);
});
resetPage();
renderFn();
// Restore sort indicator after re-render
var t=_ge(tableId);
if(t){t.querySelectorAll('th').forEach(function(th,i){
if(i===colIdx&&th.classList.contains('sortable'))th.classList.add(asc?'sort-asc':'sort-desc');
});}
}
function renderAll(){
// Always render dashboard (KPIs always visible) and update storage
renderDashboard();
updateStorageIndicator();
// Find active section to prioritize it
var activeEl=_d.querySelector('.section.active');
var activeId=activeEl?activeEl.id:'';
// Render active section immediately
if(activeId==='sec-ventas')renderVentas();
else if(activeId==='sec-compras')renderCompras();
else if(activeId==='sec-gastos')renderGastos();
else if(activeId==='sec-inventario')renderInventario();
else if(activeId==='sec-reporte')renderReporte();
else if(activeId==='sec-plan')renderPlanSection();
// Defer all other sections to next tick — prevents UI freeze with large datasets
setTimeout(function(){
if(activeId!=='sec-ventas')renderVentas();
if(activeId!=='sec-compras')renderCompras();
if(activeId!=='sec-gastos')renderGastos();
if(activeId!=='sec-inventario')renderInventario();
if(activeId!=='sec-reporte')renderReporte();
if(activeId!=='sec-plan')renderPlanSection();
},50);
}
/* ── Storage indicator ── */
function updateStorageIndicator(){
try{
var data=_ls.getItem('cc_pulso360_v3')||'';
var usedBytes=new Blob([data]).size;
var limitBytes=5*1024*1024;
var pct=Math.min(100,Math.round(usedBytes/limitBytes*100));
var usedKB=usedBytes/1024;
var usedStr=usedKB<1000?(usedKB.toFixed(1)+' KB'):((usedKB/1024).toFixed(2)+' MB');
var totalRecs=ventas.length+compras.length+gastos.length+productos.length;
var colorClass=pct<60?'ok':pct<85?'warn':'danger';
var bar=_ge('storageBarFill');var pctEl=_ge('storagePct');var detailEl=_ge('storageDetail');
if(bar){bar.style.width=Math.max(2,pct)+'%';bar.className='storage-bar-fill '+colorClass;}
if(pctEl){pctEl.textContent=(pct<1?'<1':pct)+'%';pctEl.style.color=pct>=85?'#f87171':pct>=60?'#fbbf24':'rgba(255,255,255,.8)';}
if(detailEl){detailEl.textContent=usedStr+' de 5 MB · '+totalRecs.toLocaleString('es')+' registros';}
}catch(e){}
}
/* ── Nueva sesión ── */
function nuevaSesion(){
if(!confirm('¿Limpiar todos los datos y empezar una nueva sesión? Esta acción no se puede deshacer.'))return;
ventas=[];compras=[];gastos=[];productos=[];nextVid=1;nextCid=1;nextGid=1;nextPid=1;
try{_ls.removeItem('cc_pulso360_v3');}catch(e){}
renderAll();showSection('dashboard');toast('Sesión limpiada ✓','ok');
}
/* ── Guardar / Cargar sesión ── */
function autoSave(){
try{
var peData={costosFijos:gv('peCostosFijos'),precioVenta:gv('pePrecioVenta'),costoVariable:gv('peCostoVariable'),unidad:gv('peUnidad')||'monetario',negocio:gv('planNombre')};
_ls.setItem('cc_pulso360_v3',_jP.stringify({ventas:ventas,compras:compras,gastos:gastos,productos:productos,planData:planData,peData:peData,nextVid:nextVid,nextCid:nextCid,nextGid:nextGid,nextPid:nextPid}));
}catch(e){}
updateStorageIndicator();
}
function autoLoad(){
try{
var m=_ls.getItem('cc_pulso360_moneda');
if(m){moneda=_jP.parse(m);var tm=_ge('topbarMoneda');if(tm)tm.textContent=moneda.symbol+' '+moneda.code;}
}catch(e){}
var raw=_ls.getItem('cc_pulso360_v3')||_ls.getItem('cc_pulso360_v1');
try{
if(!raw)return;
var d=_jP.parse(raw);
if(d.ventas)ventas=d.ventas;if(d.compras)compras=d.compras;if(d.gastos)gastos=d.gastos;if(d.productos)productos=d.productos;
if(d.planData)planData=d.planData;
if(d.nextVid)nextVid=d.nextVid;if(d.nextCid)nextCid=d.nextCid;if(d.nextGid)nextGid=d.nextGid;if(d.nextPid)nextPid=d.nextPid;
if(d.peData){
if(_ge('peCostosFijos')&&d.peData.costosFijos)_ge('peCostosFijos').value=d.peData.costosFijos;
if(_ge('pePrecioVenta')&&d.peData.precioVenta)_ge('pePrecioVenta').value=d.peData.precioVenta;
if(_ge('peCostoVariable')&&d.peData.costoVariable)_ge('peCostoVariable').value=d.peData.costoVariable;
if(_ge('peUnidad')&&d.peData.unidad)_ge('peUnidad').value=d.peData.unidad;
if(_ge('planNombre')&&d.peData.negocio)_ge('planNombre').value=d.peData.negocio;
}
}catch(e){}
}
function guardarSesion(){
var negocio=(gv('planNombre')||'MiNegocio').replace(/[^a-zA-Z0-9áéíóúÁÉÍÓÚñÑ\s]/g,'').trim().replace(/\s+/g,'_');
autoSave();
var data={cc_pulso360_v3:_jP.parse(_ls.getItem('cc_pulso360_v3')||'{}'),cc_pulso360_moneda:moneda,exportedAt:new Date().toISOString(),version:'v3'};
var blob=new Blob([_jP.stringify(data,null,2)],{type:'application/json'});
var url=URL.createObjectURL(blob);
var a=document.createElement('a');a.href=url;a.download='Sesion_Pulso_'+negocio+'_'+new Date().toISOString().slice(0,10)+'.json';
document.body.appendChild(a);a.click();
setTimeout(function(){URL.revokeObjectURL(url);if(a.parentNode)a.parentNode.removeChild(a);},300);
toast('Sesión exportada ✓','ok');
}
function cargarSesion(){
var input=document.createElement('input');input.type='file';input.accept='.json';
input.onchange=function(e){
var file=e.target.files[0];if(!file)return;
var reader=new FileReader();
reader.onload=function(ev){
try{
var data=_jP.parse(ev.target.result);
// ── Detectar archivo de Mi·Dinero ──
if(data._source==='mi_dinero'){
var neg=data.negocio||{};
var movNeg=neg.movimientos||data.movimientos&&data.movimientos.filter(function(m){return m.uso==='negocio';})||[];
var invNeg=neg.inversiones||[];
var sym=data.moneda&&data.moneda.symbol||moneda.symbol;
var nGastos=movNeg.filter(function(m){return m.tipo==='gasto';});
var nIngresos=movNeg.filter(function(m){return m.tipo==='ingreso';});
var totalG=nGastos.reduce(function(a,m){return a+m.monto;},0);
var totalI=nIngresos.reduce(function(a,m){return a+m.monto;},0);
if(!movNeg.length&&!invNeg.length){
toast('No hay movimientos de Negocio en Mi·Dinero','err');return;
}
var msg='Archivo de Mi\u00b7Dinero detectado.\n\n'
+(nGastos.length?'\u2022 '+nGastos.length+' gasto(s) de Negocio: '+sym+' '+Math.round(totalG).toLocaleString('es')+'\n':'')
+(nIngresos.length?'\u2022 '+nIngresos.length+' ingreso(s) de Negocio (retiro del due\u00f1o): '+sym+' '+Math.round(totalI).toLocaleString('es')+'\n':'')
+(invNeg.length?'\u2022 '+invNeg.length+' inversi\u00f3n(es) del negocio\n':'')
+'\n\u00bfImportar estos movimientos a Pulso\u00b7360?';
if(!confirm(msg))return;
var usuario=data.usuario||'Due\u00f1o';
// Gastos de negocio → gastos en Pulso
nGastos.forEach(function(m){
gastos.push({id:nextGid++,descripcion:m.desc,monto:m.monto,categoria:m.categoria||'Operaciones',tipo:'variable',fecha:m.fecha,nota:'Importado de Mi\u00b7Dinero'});
});
// Ingresos de negocio → gastos tipo "Retiro del dueño"
nIngresos.forEach(function(m){
gastos.push({id:nextGid++,descripcion:'Retiro '+usuario+': '+m.desc,monto:m.monto,categoria:'Retiro Due\u00f1o',tipo:'variable',fecha:m.fecha,nota:'Importado de Mi\u00b7Dinero'});
});
autoSave();renderAll();
toast('Mi\u00b7Dinero importado: '+(nGastos.length+nIngresos.length)+' movimientos \u2713','ok');
return;
}
// ── Sesión normal de Pulso ──
var d=data.cc_pulso360_v3||data.cc_pulso360_v1;
if(d){
if(d.ventas)ventas=d.ventas;if(d.compras)compras=d.compras;if(d.gastos)gastos=d.gastos;if(d.productos)productos=d.productos;
if(d.planData)planData=d.planData;
if(d.nextVid)nextVid=d.nextVid;if(d.nextCid)nextCid=d.nextCid;if(d.nextGid)nextGid=d.nextGid;if(d.nextPid)nextPid=d.nextPid;
}
if(data.cc_pulso360_moneda&&data.cc_pulso360_moneda.code){
moneda=data.cc_pulso360_moneda;
var tm=_ge('topbarMoneda');if(tm)tm.textContent=moneda.symbol+' '+moneda.code;
_ls.setItem('cc_pulso360_moneda',_jP.stringify(moneda));
}
autoSave();renderAll();toast('Sesi\u00f3n cargada \u2713','ok');
}catch(err){toast('Archivo inv\u00e1lido','err');}
};
reader.readAsText(file);
};
input.click();
}
/* ── Mobile ── */
function toggleMobileMore(){
var m=_ge('mobileMore');var o=_ge('mobileMoreOverlay');if(!m||!o)return;
var isOpen=m.classList.contains('open');
m.classList.toggle('open',!isOpen);o.style.display=isOpen?'none':'block';
}
/* ── Init ── */
window.addEventListener('load',function(){
autoLoad();renderAll();
setInterval(autoSave,60000);
// Precargar XLSX en segundo plano para Todo en uno
_loadXlsx(function(){_xlsxLoaded=true;});
// Keyboard scroll for tables
document.addEventListener('keydown',function(e){
if(e.key!=='ArrowLeft'&&e.key!=='ArrowRight')return;
var active=document.activeElement;
if(active&&(active.tagName==='INPUT'||active.tagName==='TEXTAREA'||active.tagName==='SELECT'))return;
_qs('.table-wrap').forEach(function(w){
var rect=w.getBoundingClientRect();
if(rect.top0&&w.scrollWidth>w.clientWidth){
w.scrollLeft+=e.key==='ArrowRight'?120:-120;
}
});
});
// Modal ayuda drag
(function(){
var overlay=_ge('modalAyuda');if(!overlay)return;
var panel=overlay.querySelector('.modal');
var handle=_ge('ayudaDragHandle');if(!handle)return;
var mx=0,my=0,dragging=false;
handle.addEventListener('mousedown',function(e){if(e.target.closest('button'))return;dragging=true;var r=panel.getBoundingClientRect();if(!panel.style.left){panel.style.left=r.left+'px';panel.style.top=r.top+'px';}mx=e.clientX-parseInt(panel.style.left||r.left);my=e.clientY-parseInt(panel.style.top||r.top);e.preventDefault();});
document.addEventListener('mousemove',function(e){if(!dragging)return;panel.style.left=(e.clientX-mx)+'px';panel.style.top=(e.clientY-my)+'px';});
document.addEventListener('mouseup',function(){dragging=false;});
overlay.addEventListener('click',function(e){if(e.target===overlay)closeModal('modalAyuda');});
})();
// Footer año
var fy=_ge('footerYear');if(fy)fy.textContent=new Date().getFullYear();
// Init inline currency csel
initMonedaPulso();
});
// Footer móvil WP/Divi hide
(function(){
function _hf(){
if(window.innerWidth>768)return;
['.et-l.et-l--footer','.et-l--footer','#colophon','#footer','#main-footer','.site-footer','#site-footer'].forEach(function(s){
_qs(s).forEach(function(el){el.style.cssText='display:none!important;height:0!important;overflow:hidden!important;visibility:hidden!important';});
});
}
document.addEventListener('DOMContentLoaded',_hf);
window.addEventListener('resize',_hf,{passive:true});
setTimeout(_hf,600);setTimeout(_hf,1500);
})();
// Public API
['totalGastosMes','eliminarVenta','renderDashboard','selectMoneda','sv2','isToday','renderInventario',
'editarVenta','editarCompra','editarGasto','editarProducto','eliminarCompra','eliminarGasto','eliminarProducto',
'calcPE','openModal','closeModal','showSection','showImportTab','previewImport','confirmarImport',
'descargarPlantilla','guardarVenta','guardarCompra','guardarGasto','guardarProducto','calcVentaTotal',
'calcCompraTotal','calcMargen','fmtPeInput','peEnter','cambiarMoneda','toggleDS','filterDS','selectMoneda','initMonedaPulso',
'verReportePDF','exportarExcelHTML','renderReporte','nuevaSesion','guardarSesion','cargarSesion',
'exportarParaPlanifica','autocompletarPE','toggleMobileMore','procesarTodo','sortTabla',
'setPeriodo','setPeriodoCustom','buildMesPicker','renderPlanSection','setSemaforoReporte','getReporteFilterFn',
'switchStockView','sortStockTable','renderVentas','renderCompras','renderGastos',
'pagVentasPrev','pagVentasNext','pagComprasPrev','pagComprasNext',
'pagGastosPrev','pagGastosNext','pagInvPrev','pagInvNext',
'pagSizeVentas','pagSizeCompras','pagSizeGastos','pagSizeInv'
].forEach(function(name){
if(typeof eval(name)!=='undefined')_w[name]=eval(name);
});
})(window,document,localStorage,JSON,navigator);