Cargando configuración…
Titular / Emisor
Cliente
📋 Tareo y Valorización
Mes principal
Año
+ Otro mes
Año
🚛 —
⏳ Cargando datos…
Día trabajado (Excel) Día trabajado (GPS 🛰) Kms extra (+límite)
Días trabajados
0
Días con kms extra
0
Total kms extra
0 kms
Total kms marcados
0 kms
DescripciónCant.TarifaImporte
Días de alquiler0S/ —S/ 0.00
Kms adicional0 kmsS/ —/kmsS/ 0.00
Valor de ventaS/ 0.00
IGV (18%)S/ 0.00
Monto totalS/ 0.00
Detracción (10%) — BNS/ 0.00
Monto a abonar (neto)S/ 0.00
🗺 Actividad y Recorrido
Kms recorridos
Promedio diario
Días con movimiento
% Actividad
0%
Último odómetro
Actividad — días marcados para tareo

Kms por día ← desliza →

Cliente (Excel) Cliente (GPS 🛰) +kms Sin movimiento

Detalle por día ↕ desliza

DíaOdóm. iniOdóm. finKmsEstado
⚡ Último odómetro
Actualizado
💰 Análisis Económico
Costo total mes
Costo por kms
Cuota banco
RubroCosto/mesCosto/kmsBase

Historial costos por mes ↕ desliza

MesKmsCuotaMantosLlantasSeguroTotalS//kms

Mantenimientos

FechaMantoOdóm. realCostoAdic.Total

Costo por tramo 5,000 kms

TramoKmsCosto totalS//kms

Llantas

FechaMarcaDesdeHasta (tope)TramoCostoS//kms

Seguros

⚖ Ingresos vs Egresos

Egresos sobre · Ingresos sobre días marcados

↑ Ingresos
Valor de ventaS/ 0.00
IGV (18%)S/ 0.00
Total facturadoS/ 0.00
Ingreso/kmsS/ 0.00/kms
↓ Egresos
Egreso/kms
Resultado del mes
Resultado (S/)
Marca días en tareo
Por kms
// ── CONFIG GLOBAL (se llena desde config.json) ───────────────────────── var CFG = null; var VEH = null; // vehículo activo var VEH_IDX = 0; // ── ESTADO ───────────────────────────────────────────────────────────── var DRIVE_DATA={}, DIAS={}, DIAS2={}, ODO=0, KMS_MES=0, LAST_DAY=0, DIAS_N=31; var EXCEL_ODO_FIN=0, EXCEL_LAST_DATE=null, ODO_SRC='GPS'; var MANTOS=[], LLANTAS=[], MANTO_TRAMOS=[], LLANTA_TRAMOS=[]; var TOPE_LLANTA=60000, TRAMO_KM=5000; var EG_T=0, EG_KM=0; var marcados={}, ING={t:0,v:0,i:0}; // ── UTILIDADES ───────────────────────────────────────────────────────── function $(id){return document.getElementById(id);} function set(id,v){var e=$(id);if(e)e.textContent=v;} function fmt(v){return 'S/ '+v.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,',');} function fmtS(v){return 'S/'+Math.round(v).toLocaleString();} function getDIM(m,y){return new Date(y,m,0).getDate();} function getFD(m,y){return new Date(y,m-1,1).getDay();} function setStatus(st,txt){$('s-dot').className='s-dot '+st;set('s-txt',txt);} function parseFecha(s){var m=s.match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/);if(m)return new Date(parseInt(m[3]),parseInt(m[2])-1,parseInt(m[1]));return null;} var MNOM=['','Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre']; var MSHORT='EneFebMarAbrMayJunJulAgoSepOctNovDic'.match(/.{3}/g); // ── CARGAR CONFIG.JSON ───────────────────────────────────────────────── function cargarConfig(){ setStatus('loading','Cargando configuración…'); var urls=['config.json?t='+Date.now(), 'https://raw.githubusercontent.com/amaronovoaflores/dashboard-pedro/main/config.json?t='+Date.now()]; function tryUrl(idx){ if(idx>=urls.length){setStatus('error','No se encontró config.json');return;} var xhr=new XMLHttpRequest();xhr.open('GET',urls[idx],true);xhr.timeout=8000; xhr.onload=function(){ if(xhr.status===200){ try{ CFG=JSON.parse(xhr.responseText); inicializarConConfig(); }catch(e){setStatus('error','Error en config.json: '+e.message);} } else tryUrl(idx+1); }; xhr.onerror=function(){tryUrl(idx+1);}; xhr.ontimeout=function(){tryUrl(idx+1);}; xhr.send(); } tryUrl(0); } function inicializarConConfig(){ // Título y meta document.title='Dashboard '+CFG.empresa.nombre; $('app-header').style.borderLeftColor = CFG.empresa.color_acento||'#BA7517'; // Header set('h-empresa', CFG.empresa.nombre); set('h-ruc', CFG.empresa.ruc); set('h-cliente', CFG.clientes[0].nombre); set('h-cliente-r', CFG.clientes[0].ruc); // Selector titulares var selT=$('sel-t'); selT.innerHTML=''; CFG.titulares.forEach(function(t,i){ var o=document.createElement('option');o.value=i;o.textContent=t.nombre+' — '+t.ruc; selT.appendChild(o); }); // Selector clientes var selC=$('sel-c'); selC.innerHTML=''; CFG.clientes.forEach(function(c,i){ var o=document.createElement('option');o.value=i;o.textContent=c.nombre+' — '+c.ruc; selC.appendChild(o); }); // Botones de vehículos var bar=$('vehicle-bar'); bar.innerHTML=''; CFG.vehiculos.forEach(function(v,i){ var btn=document.createElement('button'); btn.className='v-btn'+(i===0?' active':''); btn.innerHTML='🚛 '+v.placa+''+v.descripcion+''; btn.onclick=function(){seleccionarVehiculo(i);}; bar.appendChild(btn); }); // Mostrar menú var menu=$('menubar');if(menu)menu.style.display='flex'; // Alertas SOAT (si están en config) var alertasBar=$('alertas-bar'); if(alertasBar && CFG.alertas && CFG.alertas.length>0){ alertasBar.style.display='flex'; CFG.alertas.forEach(function(a){ var chip=document.createElement('div'); chip.style.cssText='display:flex;align-items:center;gap:5px;padding:5px 11px;border-radius:99px;font-size:13px;font-weight:500;background:'+(a.tipo==='warn'?'#FAEEDA':'#E1F5EE')+';color:'+(a.tipo==='warn'?'#854F0B':'#085041')+';border:1px solid '+(a.tipo==='warn'?'#FAC775':'#5DCAA5')+';'; chip.innerHTML=(a.tipo==='warn'?'⚠ ':'✓ ')+a.texto; alertasBar.appendChild(chip); }); } // Inicializar páginas switchPage('tareo'); $('sel-m1').value=String(hoy.getMonth()+1); $('sel-a1').value=String(hoy.getFullYear()); // Eventos $('sel-m1').onchange=onPeriodChange;$('sel-a1').onchange=onPeriodChange; $('sel-m2').onchange=onPeriodChange;$('sel-a2').onchange=onPeriodChange; $('sel-t').onchange=updateHeader;$('sel-c').onchange=updateHeader; seleccionarVehiculo(0); } function seleccionarVehiculo(idx){ VEH_IDX=idx; VEH=CFG.vehiculos[idx]; // Actualizar botones document.querySelectorAll('.v-btn').forEach(function(b,i){b.classList.toggle('active',i===idx);}); // Reset datos DRIVE_DATA={};DIAS={};DIAS2={};ODO=0;KMS_MES=0;LAST_DAY=0;marcados={}; MANTOS=[];LLANTAS=[];MANTO_TRAMOS=[];LLANTA_TRAMOS=[]; // Actualizar pill $('v-pill').innerHTML='🚛 '+VEH.descripcion+' · '+VEH.placa+''; set('t1-sub',VEH.descripcion+' · '+VEH.placa); // Actualizar tarifas buildTarifaBar(); // Cargar datos setStatus('loading','Cargando datos del vehículo '+VEH.placa+'…'); cargarMantosJSON(function(){cargarHunterJSON();}); } function updateHeader(){ var ti=parseInt($('sel-t').value)||0; var ci=parseInt($('sel-c').value)||0; var t=CFG.titulares[ti]||CFG.titulares[0]; var c=CFG.clientes[ci]||CFG.clientes[0]; set('h-empresa',t.nombre);set('h-ruc',t.ruc); set('h-cliente',c.nombre);set('h-cliente-r',c.ruc); } function buildTarifaBar(){ var bar=$('tarifa-bar');bar.innerHTML=''; var items=[ ['Tarifa/día','S/ '+(VEH.tarifa_dia||270).toFixed(2)], ['Km adicional','S/ '+(VEH.tarifa_km||1.35).toFixed(2)+'/kms'], ['Límite/día',(VEH.limite_km||200)+' kms'], ['IGV','18%'],['Detracción','10%'] ]; items.forEach(function(it){ var d=document.createElement('div');d.className='tc'; d.innerHTML=''+it[0]+''+it[1]+''; bar.appendChild(d); }); // Actualizar tabla valorización set('v-td','S/ '+(VEH.tarifa_dia||270).toFixed(2)); set('v-tk','S/ '+(VEH.tarifa_km||1.35).toFixed(2)); } // ── CARGA DATOS ──────────────────────────────────────────────────────── function cargarMantosJSON(cb){ var urls=['datos_mantos_'+VEH.placa.replace('-','')+'.json?t='+Date.now(), 'datos_mantos.json?t='+Date.now(), 'https://raw.githubusercontent.com/amaronovoaflores/dashboard-pedro/main/datos_mantos.json?t='+Date.now()]; function tryM(idx){ if(idx>=urls.length){if(cb)cb();return;} var x=new XMLHttpRequest();x.open('GET',urls[idx],true);x.timeout=8000; x.onload=function(){ if(x.status===200){ try{ var d=JSON.parse(x.responseText); if(d&&d.mantos&&d.mantos.length>0){ MANTOS=d.mantos.map(function(m){return{f:m.f,n:m.n,o:m.o,c:m.c,a:m.a,t:0,cxkm:0};}); LLANTAS=d.llantas.map(function(l){return{f:l.f,m:l.m,o:l.o,c:l.c,t:0,cxkm:0};}); if(d.tope_llanta) TOPE_LLANTA=d.tope_llanta; calcularTramos(); console.log('Mantos cargados: '+MANTOS.length+' mantos, '+LLANTAS.length+' llantas'); } }catch(e){console.log('Error mantos:',e);} } if(cb)cb(); }; x.onerror=function(){tryM(idx+1);};x.ontimeout=function(){tryM(idx+1);};x.send(); } tryM(0); } function cargarHunterJSON(){ var urls=['datos_hunter.json?t='+Date.now(), 'https://raw.githubusercontent.com/amaronovoaflores/dashboard-pedro/main/datos_hunter.json?t='+Date.now()]; function tryUrl(idx){ if(idx>=urls.length){setStatus('idle','Sin datos GPS');renderAll();return;} var xhr=new XMLHttpRequest();xhr.open('GET',urls[idx],true);xhr.timeout=10000; xhr.onload=function(){ if(xhr.status===200){ try{ var datos=JSON.parse(xhr.responseText); if(datos&&datos.dias&&Object.keys(datos.dias).length>0){integrarJSONHunter(datos);return;} }catch(e){} } tryUrl(idx+1); }; xhr.onerror=function(){tryUrl(idx+1);};xhr.ontimeout=function(){tryUrl(idx+1);};xhr.send(); } tryUrl(0); } function integrarJSONHunter(datos){ var diasJSON=datos.dias||{}; var ultimaAct=datos.ultima_actualizacion||''; var nuevos=0; Object.keys(diasJSON).forEach(function(fechaStr){ var d=diasJSON[fechaStr]; var parts=fechaStr.split('-'); var y=parseInt(parts[0]),mo=parseInt(parts[1]),dia=parseInt(parts[2]); var yaEnExcel=DRIVE_DATA[y]&&DRIVE_DATA[y][mo]&&DRIVE_DATA[y][mo][dia]&&DRIVE_DATA[y][mo][dia][2]!=='hunter'; if(!yaEnExcel){ if(!DRIVE_DATA[y])DRIVE_DATA[y]={}; if(!DRIVE_DATA[y][mo])DRIVE_DATA[y][mo]={}; DRIVE_DATA[y][mo][dia]=[d.odo_ini||0,d.odo_fin||0,'hunter']; nuevos++; } }); var ultimoDia=Object.keys(diasJSON).sort().pop(); var ultimoDato=ultimoDia?diasJSON[ultimoDia]:null; var lastOdo=ultimoDato?ultimoDato.odo_fin:0; var lastStr=ultimoDia?(parseInt(ultimoDia.split('-')[2])+' '+MSHORT[parseInt(ultimoDia.split('-')[1])-1]+' '+ultimoDia.split('-')[0]):'—'; var actStr=ultimaAct?ultimaAct.split(' ')[1]:''; setStatus('ok','🛰 GPS hasta '+lastStr+' ('+lastOdo.toLocaleString()+' kms) · act. '+actStr); ODO_SRC='Hunter GPS'; // Actualizar odo en hitos si disponible if(lastOdo>0 && CFG.vehiculos[VEH_IDX]){ var lm=MANTOS[MANTOS.length-1]; if(lm && lastOdo>lm.o) calcularTramos(); } cargarMeses();renderAll(); } // ── PARSEAR EXCEL ────────────────────────────────────────────────────── function parsearXLSX(buf){ var wb=XLSX.read(new Uint8Array(buf),{type:'array'}); var ws=wb.Sheets[wb.SheetNames[0]]; var rows=XLSX.utils.sheet_to_json(ws,{header:1,defval:null}); var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); var m2=parseInt($('sel-m2').value),a2=parseInt($('sel-a2').value); var colOdo=VEH.col_odo||8; var lastOdo=0,lastDate=null; for(var i=2;ilastOdo){lastOdo=odo;lastDate=dt;} } EXCEL_ODO_FIN=lastOdo;EXCEL_LAST_DATE=lastDate; } // ── CARGAR MESES ─────────────────────────────────────────────────────── function cargarMeses(){ var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); var m2sel=parseInt($('sel-m2').value),a2sel=parseInt($('sel-a2').value); DIAS={};DIAS2={}; function procesar(data,destino){ var keys=Object.keys(data).map(Number).sort(function(a,b){return a-b;}); var allOdos=[]; keys.forEach(function(d){ var vs=data[d]; var nums=Array.isArray(vs)?vs.filter(function(v){return typeof v==='number'&&v>0;}):[]; if(!nums.length&&typeof vs[0]==='number'&&vs[0]>0)nums=vs.slice(0,2).filter(Number); allOdos=allOdos.concat(nums); }); var odoMax=allOdos.length?Math.max.apply(null,allOdos):0; var odoMin=allOdos.length?Math.min.apply(null,allOdos.filter(function(v){return v>0;})):0; keys.forEach(function(d){ var vs=data[d]; var src=(vs[vs.length-1]==='hunter')?'hunter':'excel'; var nums=src==='hunter'?[vs[0],vs[1]]:vs.filter(function(v){return typeof v==='number'&&v>0;}); var di=nums.length?Math.min.apply(null,nums):0; var df=nums.length?Math.max.apply(null,nums):0; destino[d]={km:Math.round((df-di)*10)/10,i:di,f:df,c:true,src:src}; }); return{max:odoMax,min:odoMin,last:keys[keys.length-1]||0,kmTotal:Math.round(odoMax-odoMin)}; } var d1=(DRIVE_DATA[a1]&&DRIVE_DATA[a1][m1])||{}; if(Object.keys(d1).length>0){ var r1=procesar(d1,DIAS); ODO=isNaN(r1.max)?0:r1.max; KMS_MES=isNaN(r1.kmTotal)?0:r1.kmTotal; LAST_DAY=r1.last; } DIAS_N=getDIM(m1,a1); if(m2sel>0){ var d2=(DRIVE_DATA[a2sel]&&DRIVE_DATA[a2sel][m2sel])||{}; if(Object.keys(d2).length>0)procesar(d2,DIAS2); } calcularTramos();calcEgresos(); var dias1=Object.keys(DIAS).filter(function(d){return DIAS[d].km>0;}).length; var msg='✓ '+MNOM[m1]+' '+a1+': '+dias1+' días · Odo día '+LAST_DAY+': '+ODO.toLocaleString()+' kms'; set('drive-txt',msg); } // ── MANTENIMIENTOS & TRAMOS ──────────────────────────────────────────── function calcularTramos(){ var LIM_KM=VEH?VEH.limite_km||200:200; // Calcular tramos mantenimiento (5,000 kms por odómetro real) var maxOdo=0;MANTOS.forEach(function(m){if(m.o>maxOdo)maxOdo=m.o;}); maxOdo=Math.ceil(maxOdo/TRAMO_KM)*TRAMO_KM; MANTO_TRAMOS=[]; for(var km=0;km=km&&m.o=MANTO_TRAMOS[i].ini&&op0)return MANTO_TRAMOS[i].cxkm;} var last=0;MANTO_TRAMOS.forEach(function(t){if(t.costo>0)last=t.cxkm;});return last; } function cxkmL(op){ for(var i=0;i=LLANTA_TRAMOS[i].odoIni&&op=ini&&now=0;i--){ var d=new Date(hoy.getFullYear(),hoy.getMonth()-i,1); meses.push([d.getFullYear(),d.getMonth()+1,MSHORT[d.getMonth()]+'-'+String(d.getFullYear()).slice(2)]); } return meses; } function kmsEnMes(y,m){ var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); if(y===a1&&m===m1)return KMS_MES; var d=(DRIVE_DATA[y]&&DRIVE_DATA[y][m])||{}; if(Object.keys(d).length>0){ var odos=[];Object.values(d).forEach(function(vs){ var nums=Array.isArray(vs)?vs.filter(function(v){return typeof v==='number'&&v>0;}):[]; odos=odos.concat(nums); }); if(odos.length)return Math.round(Math.max.apply(null,odos)-Math.min.apply(null,odos)); } return 200; // estimado si no hay datos } function odoProm(y,m){ var d=(DRIVE_DATA[y]&&DRIVE_DATA[y][m])||{}; if(Object.keys(d).length>0){ var odos=[];Object.values(d).forEach(function(vs){ var nums=Array.isArray(vs)?vs.filter(function(v){return typeof v==='number'&&v>0;}):[]; odos=odos.concat(nums); }); if(odos.length)return(Math.max.apply(null,odos)+Math.min.apply(null,odos))/2; } return ODO||0; } function calcEgresos(){ if(!VEH)return; var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); var op=odoProm(a1,m1),mr=cxkmM(op),lr=cxkmL(op),sg=segMes(a1,m1); var cuota=(VEH.cuota_usd||0)*(VEH.tc||3.50); EG_T=cuota+KMS_MES*mr+KMS_MES*lr+sg; EG_KM=KMS_MES>0?EG_T/KMS_MES:0; } // ── CALENDARIO ───────────────────────────────────────────────────────── function onPeriodChange(){marcados={};cargarMeses();renderAll();} function buildCalendar(){ var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); var m2sel=parseInt($('sel-m2').value),a2sel=parseInt($('sel-a2').value); var grid=$('cal-grid');grid.innerHTML=''; function addSec(label,color){ var h=document.createElement('div');h.className='cal-sec-hd'; h.style.cssText='color:'+color+';font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;'; h.textContent=label;grid.appendChild(h); ['Dom','Lun','Mar','Mié','Jue','Vie','Sáb'].forEach(function(d){var hd=document.createElement('div');hd.className='cal-hd';hd.textContent=d;grid.appendChild(hd);}); } function addDays(total,firstDay,mesKey,diasData,borderCol,isHunterSec){ for(var i=0;i0)kml.textContent=data.km.toFixed(0)+' kms'+(isHunter?' 🛰':''); else if(esC)kml.textContent='0 kms'; cell.appendChild(num);cell.appendChild(kml); if(esC)cell.onclick=function(){toggleDay(key,mesKey,day);}; grid.appendChild(cell); })(d); } } addSec('📅 '+MNOM[m1]+' '+a1,'#0F6E56'); addDays(getDIM(m1,a1),getFD(m1,a1),'m1',DIAS,'#5DCAA5',false); if(m2sel>0){ addSec('📅 '+MNOM[m2sel]+' '+a2sel+' (días adicionales)','#BA7517'); addDays(getDIM(m2sel,a2sel),getFD(m2sel,a2sel),'m2',DIAS2,'#FAC775',false); } } function toggleDay(key,mes,day){ marcados[key]=!marcados[key];paintCell(key,mes,day); updateVal();updateAct();buildChart();buildDaily();updateConcl(); } function paintCell(key,mes,day){ var cell=$('cell-'+key);if(!cell)return; var num=cell.children[0],kml=cell.children[1]; var data=(mes==='m1'?DIAS:DIAS2)[day]||{km:0,src:'excel'}; var km=data.km,LIM=VEH?VEH.limite_km||200:200,extra=km>LIM?km-LIM:0; var isHunter=data.src==='hunter'; if(marcados[key]){ if(extra>0){cell.style.background='#FAEEDA';cell.style.borderColor='#FAC775';num.style.color='#BA7517';kml.style.color='#BA7517';kml.textContent='+'+extra.toFixed(1)+' kms'+(isHunter?' 🛰':'');} else if(isHunter){cell.style.background='#dbeafe';cell.style.borderColor='#2563eb';num.style.color='#1d4ed8';kml.style.color='#1d4ed8';kml.textContent=km.toFixed(0)+' kms 🛰';} else{cell.style.background='#E1F5EE';cell.style.borderColor='#5DCAA5';num.style.color='#0F6E56';kml.style.color='#0F6E56';kml.textContent=km.toFixed(0)+' kms';} num.style.fontWeight='500'; } else { cell.style.background='white';cell.style.borderColor=mes==='m2'?'#FAC775':(isHunter?'#60a5fa':'#dee2e6'); num.style.color=mes==='m2'?'#BA7517':(isHunter?'#1d4ed8':'');num.style.fontWeight=''; kml.style.color=isHunter?'#1d4ed8':'#6c757d'; kml.textContent=km>0?km.toFixed(0)+' kms'+(isHunter?' 🛰':''):''; } } // ── VALORIZACIÓN ─────────────────────────────────────────────────────── function updateVal(){ if(!VEH)return; var LIM=VEH.limite_km||200,TARIFA=VEH.tarifa_dia||270,TARIFA_KM=VEH.tarifa_km||1.35; var IGV=CFG.igv||0.18,DET=CFG.detraccion||0.10; var dias=0,kmDias=0,kmExtra=0,kmTotal=0; for(var key in marcados){ if(!marcados[key])continue;dias++; var parts=key.split('_'),mes=parts[0],day=parseInt(parts[1]); var km=(mes==='m1'?DIAS:DIAS2)[day];km=km?km.km:0; kmTotal+=km;if(km>LIM){kmDias++;kmExtra+=(km-LIM);} } set('o-dias',String(dias));set('o-kmd',String(kmDias)); set('o-kme',kmExtra.toFixed(0)+' kms');set('o-kmt',kmTotal.toFixed(0)+' kms'); set('v-d',String(dias));set('v-k',kmExtra.toFixed(2)+' kms'); var vt=dias*TARIFA+kmExtra*TARIFA_KM,ig=vt*IGV,tt=vt+ig,de=tt*DET,ne=tt-de; set('v-di',fmt(dias*TARIFA));set('v-ki',fmt(kmExtra*TARIFA_KM)); set('v-vt',fmt(vt));set('v-ig',fmt(ig));set('v-tt',fmt(tt));set('v-de',fmt(de));set('v-ne',fmt(ne)); ING={t:tt,v:vt,i:ig}; } // ── ACTIVIDAD ────────────────────────────────────────────────────────── function updateAct(){ var n=Object.keys(marcados).filter(function(k){return marcados[k];}).length; var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); var pct=(n/DIAS_N*100).toFixed(1); set('kpi-act',pct+'%');set('kpi-act-s',n+' de '+DIAS_N+' días marcados'); set('act-lbl-txt',n+' de '+DIAS_N+' días para tareo'); $('act-fill').style.width=pct+'%'; set('kpi-km',KMS_MES.toLocaleString()+' kms'); set('kpi-km-s','Al día '+LAST_DAY+' de '+MNOM[m1]); var dck=Object.keys(DIAS).filter(function(d){return DIAS[d].km>0;}).length; set('kpi-prom',dck>0?(KMS_MES/dck).toFixed(1)+' kms':'—'); set('kpi-odo',ODO.toLocaleString()+' kms');set('kpi-odo-s','Al día '+LAST_DAY+' de '+MNOM[m1]); set('odo-v',ODO.toLocaleString()+' kms'); set('odo-f',LAST_DAY+' de '+MNOM[m1]+', '+a1+' · vía '+ODO_SRC); set('t2-sub',VEH?VEH.placa+' · '+MNOM[m1]+' '+a1:'—'); set('fn-kms',KMS_MES.toLocaleString()+' kms totales'); } // ── GRÁFICO ──────────────────────────────────────────────────────────── function buildChart(){ var ch=$('chart-bars');ch.innerHTML=''; var keys=Object.keys(DIAS).map(Number).sort(function(a,b){return a-b;}); var LIM=VEH?VEH.limite_km||200:200; var maxKm=0;keys.forEach(function(d){if(DIAS[d].km>maxKm)maxKm=DIAS[d].km;}); ch.style.minWidth=(keys.length*34)+'px'; keys.forEach(function(day){ var data=DIAS[day],km=data.km,pct=maxKm>0?(km/maxKm)*100:0; var mk=marcados['m1_'+day],exc=km>LIM,zer=km===0,isHun=data.src==='hunter'; var cls=zer?'zer':(exc?'exc':(mk?(isHun?'hun-cli':'cli'):(isHun?'hun':'nor'))); var col=document.createElement('div');col.className='chart-col'; col.innerHTML='
'+(km>0?km:'')+'
'+day+'
'; ch.appendChild(col); }); } // ── TABLA DIARIA ─────────────────────────────────────────────────────── function buildDaily(){ var tb=$('daily-tb');tb.innerHTML=''; var LIM=VEH?VEH.limite_km||200:200; Object.keys(DIAS).map(Number).sort(function(a,b){return a-b;}).forEach(function(day){ var data=DIAS[day],km=data.km,exc=km>LIM,extra=exc?km-LIM:0,mk=marcados['m1_'+day],isHun=data.src==='hunter'; var estado=mk?('✓ '+(isHun?'GPS 🛰':'Cliente')+''):(!data.c?'Previo':(km===0?'Sin movimiento':'Normal')); var tr=document.createElement('tr'); tr.innerHTML='Día '+day+''+data.i.toLocaleString()+' kms'+data.f.toLocaleString()+' kms'+km.toFixed(1)+' kms'+(exc?' (+'+extra.toFixed(0)+'extra)':'')+''+estado+''; tb.appendChild(tr); }); } // ── ANÁLISIS ECONÓMICO ───────────────────────────────────────────────── function buildEco(){ if(!VEH)return; calcEgresos(); var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); var cuota=(VEH.cuota_usd||0)*(VEH.tc||3.50); var op=odoProm(a1,m1),mr=cxkmM(op),lr=cxkmL(op),sg=segMes(a1,m1); var ms=KMS_MES*mr,ls=KMS_MES*lr; set('kpi-eg',fmtS(EG_T));set('kpi-eg-s','Base: '+KMS_MES.toLocaleString()+' kms'); set('kpi-ekm','S/ '+EG_KM.toFixed(2));set('kpi-ekm-s','Al día '+LAST_DAY+' de '+MNOM[m1]); set('kpi-cuota','$'+(VEH.cuota_usd||0));set('kpi-cuota-s','S/ '+cuota.toFixed(2)+'/mes'); set('t3-sub',VEH.placa+' · TC S/ '+(VEH.tc||3.50)); var rubros=[{l:'Cuota banco',v:cuota,col:'#A32D2D'},{l:'Mantenimientos',v:ms,col:'#0F6E56'},{l:'Llantas',v:ls,col:'#BA7517'},{l:'Seguro',v:sg,col:'#4A90A4'}]; var tr=$('cb-track'),lg=$('cb-leg');tr.innerHTML='';lg.innerHTML=''; if(EG_T>0)rubros.forEach(function(r){ var p=(r.v/EG_T*100).toFixed(1); var s=document.createElement('div');s.style.cssText='height:100%;width:'+p+'%;background:'+r.col+';';tr.appendChild(s); var it=document.createElement('div');it.className='cb-item';it.innerHTML='
'+r.l+' '+p+'%';lg.appendChild(it); }); var rb=$('rub-tb');rb.innerHTML=''; rubros.forEach(function(r){ var ck=KMS_MES>0?r.v/KMS_MES:0; var tr2=document.createElement('tr'); tr2.innerHTML=''+r.l+''+fmtS(r.v)+'S/ '+ck.toFixed(2)+''+KMS_MES+' kms'; rb.appendChild(tr2); }); var trT=document.createElement('tr');trT.className='tot'; trT.innerHTML='TOTAL'+fmtS(EG_T)+'S/ '+EG_KM.toFixed(2)+''+KMS_MES+' kms'; rb.appendChild(trT); // Nota manto if(MANTOS.length>0){var lm=MANTOS[MANTOS.length-1];set('fn-m','* Manto '+lm.n+' kms (odo: '+lm.o.toLocaleString()+'): S/'+(lm.c+lm.a).toLocaleString()+'. Tramo hasta próximo manto.');} // Historial mensual var hb=$('hist-tb');hb.innerHTML=''; getMesesH().forEach(function(mes){ var y=mes[0],m=mes[1],lbl=mes[2],ic=(y===a1&&m===m1); var k=kmsEnMes(y,m),op2=odoProm(y,m),mr2=cxkmM(op2),lr2=cxkmL(op2),sg2=segMes(y,m); var t=cuota+k*mr2+k*lr2+sg2,ck=k>0?t/k:0; var tr3=document.createElement('tr');if(ic)tr3.className='hl'; tr3.innerHTML=''+(ic?''+lbl+' ✓':lbl)+''+(ic?''+k+'':k+'~')+''+fmtS(cuota)+''+fmtS(k*mr2)+''+fmtS(k*lr2)+''+fmtS(sg2)+''+fmtS(t)+''+ck.toFixed(2)+''; hb.appendChild(tr3); }); // Mantenimientos var mb=$('mant-tb');mb.innerHTML=''; MANTOS.forEach(function(m){ var tr4=document.createElement('tr'); tr4.innerHTML=''+m.f+''+m.n+' kms'+m.o.toLocaleString()+''+fmtS(m.c)+''+(m.a>0?fmtS(m.a):'-')+''+fmtS(m.c+m.a)+''; mb.appendChild(tr4); }); // Tramos var tramosEl=$('tramo-tb');if(tramosEl)tramosEl.innerHTML=''; MANTO_TRAMOS.forEach(function(t){ if(t.costo===0&&t.ini>ODO+TRAMO_KM)return; var el=document.createElement('tr'); var activo=(ODO>=t.ini&&ODO'+TRAMO_KM.toLocaleString()+''+fmtS(t.costo)+'S/ '+t.cxkm.toFixed(3)+'/kms'; if(tramosEl)tramosEl.appendChild(el); }); // Llantas var lb=$('llan-tb');lb.innerHTML=''; LLANTA_TRAMOS.forEach(function(l,i){ var eu=i===LLANTA_TRAMOS.length-1; var tr5=document.createElement('tr'); tr5.innerHTML=''+l.f+''+l.m+''+l.odoIni.toLocaleString()+' kms'+l.odoFin.toLocaleString()+' kms'+(eu?' ⚡':'')+''+l.t.toLocaleString()+' kms'+fmtS(l.c)+'S/ '+l.cxkm.toFixed(3)+'/kms'; lb.appendChild(tr5); }); // Seguros var segGrid=$('seguros-grid');if(segGrid){segGrid.innerHTML=''; if(VEH.seguros){VEH.seguros.forEach(function(s){ var hoy=new Date(),ini=new Date(s.inicio),fin=new Date(s.fin); var cls=hoy>=ini&&hoy
$'+s.monto_usd+'
S/ '+monto.toFixed(2)+'/mes
'; segGrid.appendChild(d); });} } // Egresos en vs var egR=$('eg-rows');egR.innerHTML=''; rubros.forEach(function(r){var d=document.createElement('div');d.className='vsr';d.innerHTML=''+r.l+''+fmtS(r.v)+'';egR.appendChild(d);}); set('e-km','S/ '+EG_KM.toFixed(2)+'/kms'); } function updateConcl(){ var ixkm=KMS_MES>0?ING.t/KMS_MES:0,rs=ING.t-EG_T,rk=ixkm-EG_KM; set('i-vt',fmt(ING.v));set('i-ig',fmt(ING.i));set('i-tt',fmt(ING.t));set('i-km','S/ '+ixkm.toFixed(2)+'/kms'); var rsEl=$('res-s'); if(ING.t===0){rsEl.textContent='— Marca días';rsEl.style.color='#6c757d';} else{rsEl.textContent=(rs<0?'- ':'+ ')+'S/ '+Math.abs(rs).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g,',');rsEl.style.color=rs>=0?'#0F6E56':'#A32D2D';} set('res-ss','Ingreso '+fmt(ING.t)+' vs Egreso '+fmt(EG_T)); var rkEl=$('res-k');rkEl.textContent=(rk<0?'- ':'+ ')+'S/ '+Math.abs(rk).toFixed(2)+'/kms';rkEl.style.color=rk>=0?'#0F6E56':'#A32D2D'; set('res-ks','Ingreso S/'+ixkm.toFixed(2)+' vs Egreso S/'+EG_KM.toFixed(2)+'/kms'); } // ── PDF ──────────────────────────────────────────────────────────────── function abrirPDF(tipo){ if(!CFG||!VEH)return; var ti=parseInt($('sel-t').value)||0,ci=parseInt($('sel-c').value)||0; var t=CFG.titulares[ti],c=CFG.clientes[ci]; var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); var m2sel=parseInt($('sel-m2').value),a2sel=parseInt($('sel-a2').value); var periodo=MNOM[m1]+' '+a1+(m2sel>0?' + '+MNOM[m2sel]+' '+a2sel:''); var w=window.open('','_blank'); var h=''+tipo+' '+VEH.placa+''; h+=''; h+='
'+t.nombre+'
'+t.ruc+'
'; h+='
'+c.nombre+'
'+c.ruc+'
'; h+='

'+tipo+' · '+VEH.descripcion+' '+VEH.placa+' · '+periodo+'

'; if(tipo==='Tareo'){ h+='

Días marcados para facturar

'; h+='
'; ['Dom','Lun','Mar','Mié','Jue','Vie','Sáb'].forEach(function(d){h+='
'+d+'
';}); var tot=getDIM(m1,a1),fd=getFD(m1,a1); for(var i=0;i
'; var LIM=VEH.limite_km||200; for(var d=1;d<=tot;d++){ var data=DIAS[d]||{km:0,src:'excel'},mk=marcados['m1_'+d],km=data.km,extra=km>LIM?km-LIM:0,isHun=data.src==='hunter'; var cls=mk?(km>LIM?'e':(isHun?'mh':'m')):''; var sub=mk?(km>LIM?'+'+extra.toFixed(0)+' kms':(km>0?km.toFixed(0)+' kms'+(isHun?' 🛰':''):'0 kms')):''; h+='
'+d+'
'+sub+'
'; } h+='
'; h+=''; h+='
Días trabajadosDías con kms extraTotal kms extraTotal kms marcados
'+$('o-dias').textContent+''+$('o-kmd').textContent+''+$('o-kme').textContent+''+$('o-kmt').textContent+'
'; } else { h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+=''; h+='
DescripciónCant.TarifaImporte
Días de alquiler'+$('v-d').textContent+''+$('v-td').textContent+''+$('v-di').textContent+'
Kms adicional'+$('v-k').textContent+''+$('v-tk').textContent+''+$('v-ki').textContent+'
Valor de venta'+$('v-vt').textContent+'
IGV (18%)'+$('v-ig').textContent+'
Monto total'+$('v-tt').textContent+'
Detracción (10%) — BN'+$('v-de').textContent+'
Monto a abonar (neto)'+$('v-ne').textContent+'
'; // Cuentas bancarias desde config if(CFG.cuentas){ h+='
Cuentas para depósito'; if(CFG.cuentas.bcp_numero){ h+='
'; h+='
✓ Pago principal — BCP (Soles)
'; h+='
Cuenta Ahorros: '+CFG.cuentas.bcp_numero+'
'; if(CFG.cuentas.bcp_cci)h+='
CCI: '+CFG.cuentas.bcp_cci+'
'; h+='
'; } if(CFG.cuentas.bn_detraccion){ h+='
✓ Detracción — Banco de la Nación
'; h+='
Cuenta: '+CFG.cuentas.bn_detraccion+'
'; } h+='
'; } } h+=''; w.document.write(h);w.document.close(); } // ── HISTORIAL ────────────────────────────────────────────────────────── var HISTORIAL_KEY_T = 'hist_tareos'; var HISTORIAL_KEY_V = 'hist_vals'; function guardarTareo(){ if(!VEH) return; var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); var m2sel=parseInt($('sel-m2').value),a2sel=parseInt($('sel-a2').value); var dias=Object.keys(marcados).filter(function(k){return marcados[k];}).length; if(dias===0){alert('No hay días marcados para guardar.');return;} var registro={ id: Date.now(), fecha_guardado: new Date().toLocaleDateString('es-PE'), placa: VEH.placa, descripcion: VEH.descripcion, mes: MNOM[m1]+' '+a1+(m2sel>0?' + '+MNOM[m2sel]+' '+a2sel:''), dias: dias, km_extra: parseFloat($('o-kme').textContent)||0, km_total: parseFloat($('o-kmt').textContent)||0, marcados: JSON.parse(JSON.stringify(marcados)), m1:m1,a1:a1,m2:m2sel,a2:a2sel, veh_idx: VEH_IDX }; var hist=JSON.parse(localStorage.getItem(HISTORIAL_KEY_T)||'[]'); hist.unshift(registro); if(hist.length>50) hist=hist.slice(0,50); localStorage.setItem(HISTORIAL_KEY_T, JSON.stringify(hist)); alert('Tareo guardado correctamente.'); renderHistorial(); } function guardarValorizacion(){ if(!VEH||!CFG) return; var m1=parseInt($('sel-m1').value),a1=parseInt($('sel-a1').value); var m2sel=parseInt($('sel-m2').value),a2sel=parseInt($('sel-a2').value); var ci=parseInt($('sel-c').value)||0; var cliente=CFG.clientes[ci]; var registro={ id: Date.now(), fecha_guardado: new Date().toLocaleDateString('es-PE'), placa: VEH.placa, cliente: cliente.nombre, mes: MNOM[m1]+' '+a1+(m2sel>0?' + '+MNOM[m2sel]+' '+a2sel:''), total: $('v-tt').textContent, neto: $('v-ne').textContent, dias: $('v-d').textContent, km_extra: $('v-k').textContent }; var hist=JSON.parse(localStorage.getItem(HISTORIAL_KEY_V)||'[]'); hist.unshift(registro); if(hist.length>50) hist=hist.slice(0,50); localStorage.setItem(HISTORIAL_KEY_V, JSON.stringify(hist)); alert('Valorización guardada correctamente.'); renderHistorial(); } function cargarTareoDesdeHist(id){ var hist=JSON.parse(localStorage.getItem(HISTORIAL_KEY_T)||'[]'); var reg=hist.find(function(r){return r.id===id;}); if(!reg) return; // Seleccionar vehículo y período seleccionarVehiculo(reg.veh_idx||0); setTimeout(function(){ $('sel-m1').value=String(reg.m1); $('sel-a1').value=String(reg.a1); if(reg.m2>0){$('sel-m2').value=String(reg.m2);$('sel-a2').value=String(reg.a2);} marcados=JSON.parse(JSON.stringify(reg.marcados)); cargarMeses();renderAll(); // Ir a tareo switchPage('tareo'); },500); } function renderHistorial(){ // Tareos var histT=JSON.parse(localStorage.getItem(HISTORIAL_KEY_T)||'[]'); var elT=$('hist-tareos-list'); if(elT){ if(histT.length===0){ elT.innerHTML='
No hay tareos guardados aún.
'; } else { elT.innerHTML=''; histT.forEach(function(r){ var d=document.createElement('div'); d.style.cssText='display:flex;align-items:center;justify-content:space-between;padding:12px 0;border-bottom:0.5px solid var(--border);gap:10px;'; d.innerHTML='
'+ ''+r.placa+''+ '
'+r.mes+'
'+ '
'+r.dias+' días · '+r.km_extra+' kms extra · Guardado '+r.fecha_guardado+'
'+ ''; elT.appendChild(d); }); } } // Valorizaciones var histV=JSON.parse(localStorage.getItem(HISTORIAL_KEY_V)||'[]'); var elV=$('hist-vals-list'); if(elV){ if(histV.length===0){ elV.innerHTML='
No hay valorizaciones guardadas aún.
'; } else { elV.innerHTML=''; histV.forEach(function(r){ var d=document.createElement('div'); d.style.cssText='display:flex;align-items:center;justify-content:space-between;padding:12px 0;border-bottom:0.5px solid var(--border);gap:10px;'; d.innerHTML='
'+ ''+r.placa+''+ '
'+r.mes+' · '+r.cliente+'
'+ '
'+r.total+' · Neto '+r.neto+' · Guardado '+r.fecha_guardado+'
'+ ''; elV.appendChild(d); }); } } } // ── STATUS ───────────────────────────────────────────────────────────── var STATUS_MODE='placa'; var STATUS_PLACA_IDX=0; function renderStatus(){ if(!CFG||!VEH) return; var content=$('status-content'); if(!content) return; if(STATUS_MODE==='placa'){ // Vista detalle grande — una placa var v=CFG.vehiculos[STATUS_PLACA_IDX]||VEH; var daysData=DRIVE_DATA[new Date().getFullYear()]&&DRIVE_DATA[new Date().getFullYear()][new Date().getMonth()+1]; var hoy=new Date().getDate(); var kmHoy=daysData&&daysData[hoy]?Math.round((daysData[hoy][1]||0)-(daysData[hoy][0]||0)):0; var isHunter=daysData&&daysData[hoy]&&daysData[hoy][2]==='hunter'; var LIM=v.limite_km||200; content.innerHTML= '
'+ '
'+ '
'+v.placa+'
'+ '
'+v.descripcion+'
'+ 'Activa'+ '
'+ '
'+ '
'+ '
'+kmHoy+(isHunter?' 🛰':'')+'
'+ '
Kms hoy
'+ '
'+ '
'+KMS_MES.toLocaleString()+'
'+ '
Kms mes
'+ '
'+ '
'+ (kmHoy>LIM?'+'+Math.round(kmHoy-LIM):'—')+'
'+ '
Kms extra
'+ '
'+ '
'+ '
Odómetro actual
'+ '
'+ODO.toLocaleString()+' kms
'+ '
Al día '+LAST_DAY+'
'+ '
vía '+ODO_SRC+'
'+ '
'+ '
'; } else { // Vista lista compacta — todas las placas var html='
'; CFG.vehiculos.forEach(function(v,i){ html+='
'+ '
'+v.placa+'
'+ '
'+v.descripcion+'
'+ '
'+ '
'+Math.round(Math.random()*200)+'
Hoy
'+ '
'+Math.round(1000+Math.random()*2000).toLocaleString()+'
Mes
'+ '
'+Math.round(40000+Math.random()*10000).toLocaleString()+' km
'+ '
'+ '
'; }); html+='
'; content.innerHTML=html; } // Chips de placas para status var chipsEl=$('status-chips'); if(chipsEl&&STATUS_MODE==='placa'){ chipsEl.innerHTML=''; CFG.vehiculos.forEach(function(v,i){ var chip=document.createElement('div'); chip.className='chip'+(i===STATUS_PLACA_IDX?' sel':''); chip.textContent=v.placa; chip.onclick=function(){STATUS_PLACA_IDX=i;document.querySelectorAll('#status-chips .chip').forEach(function(c,j){c.classList.toggle('sel',j===i);});renderStatus();}; chipsEl.appendChild(chip); }); } else if(chipsEl){ chipsEl.innerHTML=''; } set('status-sub',STATUS_MODE==='placa'?'Vista detalle':'Vista relación · todas las placas'); } // ── NAVEGACIÓN PÁGINAS ───────────────────────────────────────────────── var CURRENT_PAGE='tareo'; function switchPage(page){ CURRENT_PAGE=page; // Ocultar todas las páginas var pages=['panel-tareo','panel-val','page-status','page-historial']; // Las páginas principales son las secciones .pw document.querySelectorAll('.pw').forEach(function(el){el.style.display='none';}); var ph=$('page-historial'),ps=$('page-status'); if(ph) ph.style.display='none'; if(ps) ps.style.display='none'; if(page==='tareo'||page==='val'){ document.querySelectorAll('.pw').forEach(function(el){el.style.display='block';}); if(page==='val'){ $('tab-val').click(); } else { $('tab-tareo').click(); } } else if(page==='status'){ if(ps){ps.style.display='block';renderStatus();} } else if(page==='historial'){ if(ph){ph.style.display='block';renderHistorial();} } } function renderAll(){ buildCalendar();buildChart();buildDaily();buildEco();updateVal();updateAct();updateConcl(); } // ── INIT ─────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded',function(){ $('tab-tareo').addEventListener('click',function(){ $('tab-tareo').classList.add('active');$('tab-val').classList.remove('active'); $('panel-tareo').classList.add('active');$('panel-val').classList.remove('active'); }); $('tab-val').addEventListener('click',function(){ $('tab-val').classList.add('active');$('tab-tareo').classList.remove('active'); $('panel-val').classList.add('active');$('panel-tareo').classList.remove('active'); }); $('btn-reload').addEventListener('click',function(){ if(VEH_IDX!==null)seleccionarVehiculo(VEH_IDX); }); $('btn-pdf-tareo').addEventListener('click',function(){abrirPDF('Tareo');}); $('btn-pdf-val').addEventListener('click',function(){abrirPDF('Valorización');}); // Botones guardar var btnGT=document.getElementById('btn-guardar-tareo'); if(btnGT) btnGT.addEventListener('click',guardarTareo); var btnGV=document.getElementById('btn-guardar-val'); if(btnGV) btnGV.addEventListener('click',guardarValorizacion); // Tabs historial var htT=$('htab-tareos'),htV=$('htab-vals'); if(htT) htT.addEventListener('click',function(){ htT.classList.add('active');htV.classList.remove('active'); $('hist-tareos-list').style.display='block'; $('hist-vals-list').style.display='none'; }); if(htV) htV.addEventListener('click',function(){ htV.classList.add('active');htT.classList.remove('active'); $('hist-vals-list').style.display='block'; $('hist-tareos-list').style.display='none'; }); // Tabs status var stP=$('stab-placa'),stC=$('stab-cliente'); if(stP) stP.addEventListener('click',function(){ STATUS_MODE='placa';stP.classList.add('active');stC.classList.remove('active');renderStatus(); }); if(stC) stC.addEventListener('click',function(){ STATUS_MODE='cliente';stC.classList.add('active');stP.classList.remove('active');renderStatus(); }); cargarConfig(); });