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ón
Cant.
Tarifa
Importe
Días de alquiler
0
S/ —
S/ 0.00
Kms adicional
0 kms
S/ —/kms
S/ 0.00
Valor de venta
S/ 0.00
IGV (18%)
S/ 0.00
Monto total
S/ 0.00
Detracción (10%) — BN
S/ 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 🛰)+kmsSin movimiento
Detalle por día ↕ desliza
Día
Odóm. ini
Odóm. fin
Kms
Estado
⚡ Último odómetro
—
—Actualizado
💰 Análisis Económico—
Costo total mes
—
—
Costo por kms
—
—
Cuota banco
—
—
Rubro
Costo/mes
Costo/kms
Base
Historial costos por mes ↕ desliza
Mes
Kms
Cuota
Mantos
Llantas
Seguro
Total
S//kms
Mantenimientos
Fecha
Manto
Odóm. real
Costo
Adic.
Total
Costo por tramo 5,000 kms
Tramo
Kms
Costo total
S//kms
Llantas
Fecha
Marca
Desde
Hasta (tope)
Tramo
Costo
S//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
—
—
📋 Historial
⚡ Status—
// ── 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='
';
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+='
';});
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+='
Días trabajados
Días con kms extra
Total kms extra
Total kms marcados
';
h+='
'+$('o-dias').textContent+'
'+$('o-kmd').textContent+'
'+$('o-kme').textContent+'
'+$('o-kmt').textContent+'
';
} else {
h+='
Descripción
Cant.
Tarifa
Importe
';
h+='
Días de alquiler
'+$('v-d').textContent+'
'+$('v-td').textContent+'
'+$('v-di').textContent+'
';
h+='
Kms adicional
'+$('v-k').textContent+'
'+$('v-tk').textContent+'
'+$('v-ki').textContent+'
';
h+='
Valor de venta
'+$('v-vt').textContent+'
';
h+='
IGV (18%)
'+$('v-ig').textContent+'
';
h+='
Monto total
'+$('v-tt').textContent+'
';
h+='
Detracción (10%) — BN
'+$('v-de').textContent+'
';
h+='
Monto a abonar (neto)
'+$('v-ne').textContent+'
';
h+='
';
// 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='
'+
'';
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='