174 lines
9.3 KiB
Markdown
174 lines
9.3 KiB
Markdown
---
|
|
title: "Actas"
|
|
description: "Qué tarjeta, módulo o unidad pasó cada acta: series V5, ARD, RTK y V4, parejas y unidades."
|
|
template: tablero.html
|
|
hide:
|
|
- toc
|
|
---
|
|
|
|
<div class="tb tb-actas">
|
|
<header class="tb-cab">
|
|
<div class="tb-cab__t"><h1>Estado de actas</h1><span class="meta" id="meta">Cargando…</span></div>
|
|
<div class="tb-cab__acc"><button type="button" class="sec" id="refrescar"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M19.5 12a7.5 7.5 0 1 1-2.2-5.3M19.5 4.5v4h-4"/></svg>Actualizar</button></div>
|
|
</header>
|
|
|
|
<div id="cont"></div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
const STREAMS = [
|
|
{ tipo:'prueba-v5', prefijo:'V5', desde:1, n:30, pad:2, titulo:'Tarjetas EinarBoard — V5', form:'/formularios/v5.html' },
|
|
{ tipo:'modulo-ard', prefijo:'ARD', desde:1, n:30, pad:3, titulo:'Módulos simpleRTK2B Pro — ARD', form:'/formularios/ardusimple.html', usoClave:'ard' },
|
|
{ tipo:'modulo-4g', prefijo:'RTK', desde:1, n:30, pad:4, titulo:'Módems 4G NTRIP Master — RTK', form:'/formularios/modem4g.html', usoClave:'modem' },
|
|
{ tipo:'prueba-v4', prefijo:'V4', desde:1, n:15, pad:2, titulo:'Tarjetas EinarBoard — V4 (fw 0.6.x)', form:'/formularios/v4.html' },
|
|
];
|
|
const TABLAS = [
|
|
{ tipo:'integracion', titulo:'Integración ArduSimple + 4G (parejas)', form:'/formularios/integracion.html', cols:[['ard','ArduSimple'],['modem','4G (RTK-)']] },
|
|
{ tipo:'ensamble', titulo:'Unidades ensambladas — HBOX v5', form:'/formularios/ensamble.html', cols:[['einar','EinarBoard'],['ard','ArduSimple'],['modem','4G (RTK-)']], unidad:true },
|
|
];
|
|
const ICON = { apro: Kanban.icono('ok'), falla: Kanban.icono('cerrar'), pend: '·', ens: Kanban.icono('unidad') };
|
|
const cont = document.getElementById('cont');
|
|
const meta = document.getElementById('meta');
|
|
|
|
function fmt(iso){ return iso ? iso.replace('T',' ').slice(0,16) : ''; }
|
|
|
|
function celda(st, sc){
|
|
const cls = st.estado;
|
|
const a = document.createElement('a');
|
|
a.className = 'cel ' + cls;
|
|
a.href = (cls==='pend') ? sc.form : ('/formularios/ver.html?tipo='+encodeURIComponent(sc.tipo)+'&serie='+encodeURIComponent(st.serie));
|
|
a.title = st.serie + (st.tecnico? ' — '+st.tecnico : '');
|
|
const label = cls==='apro' ? 'Aprobada' : cls==='falla' ? 'Falla' : cls==='ens' ? 'Ensamblada' : 'Pendiente';
|
|
a.innerHTML = '<span class="s">'+st.serie+'</span>'
|
|
+ '<span class="st">'+ (ICON[cls]||'·') +' '+label+'</span>'
|
|
+ '<span class="who">'+ (st.tecnico? st.tecnico : '—') +'</span>';
|
|
return a;
|
|
}
|
|
|
|
function bloque(sc, actas){
|
|
const map = {};
|
|
actas.filter(a=>a.tipo===sc.tipo).forEach(a=>{ map[a.serie]=a; });
|
|
let apro=0, falla=0, pend=0, ens=0;
|
|
const cells=[];
|
|
const desde = sc.desde||1, pad = sc.pad||2;
|
|
for(let i=desde;i<desde+sc.n;i++){
|
|
const serie = sc.prefijo+'-'+String(i).padStart(pad,'0');
|
|
const a = map[serie];
|
|
let estado='pend', tecnico='';
|
|
if(a){ tecnico=a.tecnico||'';
|
|
estado = a.resultado==='FALLA' ? 'falla' : 'apro';
|
|
// módulo aprobado y ya montado en una unidad → 'ens' (ensamblado)
|
|
if(estado==='apro' && sc.usoClave && enUnidad[sc.usoClave] && enUnidad[sc.usoClave][serie]){ estado='ens'; tecnico='en '+enUnidad[sc.usoClave][serie]; }
|
|
}
|
|
if(estado==='apro')apro++; else if(estado==='falla')falla++; else if(estado==='ens')ens++; else pend++;
|
|
cells.push(celda({serie,estado,tecnico,recibido:a?a.recibido:''}, sc));
|
|
}
|
|
const hechas = sc.n - pend;
|
|
const sec = document.createElement('section'); sec.className='bloque';
|
|
sec.innerHTML = '<h2>'+sc.titulo+'</h2>'
|
|
+ '<p class="resumen">'+hechas+' de '+sc.n+' registradas · '+apro+' aprobadas'
|
|
+ (ens ? ' · '+ens+' ya en unidad' : '') + ' · '+falla+' con falla · '+pend+' pendientes</p>';
|
|
const barra = document.createElement('div'); barra.className='barra';
|
|
const seg=(c,n)=>{ if(!n)return; const s=document.createElement('span'); s.className=c; s.style.flex=n; barra.append(s); };
|
|
seg('a',apro); seg('e',ens); seg('f',falla);
|
|
const rest=document.createElement('span'); rest.style.flex=pend; barra.append(rest);
|
|
sec.append(barra);
|
|
const grid=document.createElement('div'); grid.className='grid'; cells.forEach(c=>grid.append(c));
|
|
sec.append(grid);
|
|
return sec;
|
|
}
|
|
|
|
let enUnidad = {}; // clave -> { serieModulo: serieUnidad }
|
|
function tabla(tc, actas){
|
|
const rows = actas.filter(a=>a.tipo===tc.tipo);
|
|
const sec=document.createElement('section'); sec.className='bloque';
|
|
const ok = rows.filter(a=>a.resultado!=='FALLA').length, bad = rows.length-ok;
|
|
sec.innerHTML='<h2>'+tc.titulo+'</h2><p class="resumen">'+rows.length+' registradas · '+ok+' aprobadas · '+bad+' con falla · <a href="'+tc.form+'">Nueva acta</a></p>';
|
|
const t=document.createElement('table');
|
|
t.innerHTML='<tr><th>'+(tc.unidad?'Unidad':'Pareja')+'</th>'+tc.cols.map(c=>'<th>'+c[1]+'</th>').join('')+'<th>Técnico</th><th>Resultado</th><th>Fecha</th></tr>';
|
|
rows.forEach(a=>{
|
|
const tr=document.createElement('tr');
|
|
const p = a.resultado==='FALLA'?'<span class="pill falla">FALLA</span>':'<span class="pill apro">OK</span>';
|
|
const link='/formularios/ver.html?tipo='+encodeURIComponent(tc.tipo)+'&serie='+encodeURIComponent(a.serie||'');
|
|
tr.innerHTML='<td><a href="'+link+'"><b>'+(a.serie||'')+'</b></a></td>'
|
|
+ tc.cols.map(c=>'<td>'+((a.componentes||{})[c[0]]||'—')+'</td>').join('')
|
|
+ '<td>'+(a.tecnico||'')+'</td><td>'+p+'</td><td>'+fmt(a.recibido)+'</td>';
|
|
t.append(tr);
|
|
});
|
|
sec.append(t); if(!rows.length) sec.innerHTML+='<p class="resumen">Aún no hay registros.</p>';
|
|
return sec;
|
|
}
|
|
function render(actas){
|
|
cont.innerHTML='';
|
|
// módulos ya montados en unidades (actas de ensamble reales)
|
|
enUnidad = {};
|
|
actas.filter(a=>a.tipo==='ensamble' && a.componentes && a.resultado!=='FALLA').forEach(a=>{
|
|
for(const k in a.componentes){ enUnidad[k]=enUnidad[k]||{}; enUnidad[k][a.componentes[k]]=a.serie; }
|
|
});
|
|
// KPIs
|
|
const v5 = actas.filter(a=>a.tipo==='prueba-v5');
|
|
const rtk = actas.filter(a=>a.tipo==='modulo-ard'), g4 = actas.filter(a=>a.tipo==='modulo-4g');
|
|
const integ = actas.filter(a=>a.tipo==='integracion');
|
|
const ens = actas.filter(a=>a.tipo==='ensamble');
|
|
const okN = l=>l.filter(a=>a.resultado!=='FALLA').length;
|
|
const kpis=document.createElement('div'); kpis.className='kpis';
|
|
const kpi=(n,l)=>{ const d=document.createElement('div'); d.className='kpi'; d.innerHTML='<div class="n">'+n+'</div><div class="l">'+l+'</div>'; return d; };
|
|
kpis.append(kpi(okN(v5),'EinarBoard V5 aprobadas'));
|
|
kpis.append(kpi(okN(rtk),'ArduSimple aprobados'));
|
|
kpis.append(kpi(okN(g4),'Módems 4G aprobados'));
|
|
kpis.append(kpi(okN(integ),'Parejas integradas'));
|
|
kpis.append(kpi(okN(ens),'Unidades HBOX listas'));
|
|
cont.append(kpis);
|
|
// unidades e integración primero, luego inventario de módulos
|
|
TABLAS.slice().reverse().forEach(tc=>cont.append(tabla(tc,actas)));
|
|
STREAMS.forEach(sc=>cont.append(bloque(sc,actas)));
|
|
// leyenda
|
|
const ley=document.createElement('div'); ley.className='leyenda';
|
|
ley.innerHTML='<span class="st" style="color:var(--apro)">'+ICON.apro+'<span style="color:var(--tinta2)">Aprobada</span></span>'
|
|
+'<span class="st" style="color:var(--falla)">'+ICON.falla+'<span style="color:var(--tinta2)">Falla</span></span>'
|
|
+'<span class="st" style="color:var(--ens)">'+ICON.ens+'<span style="color:var(--tinta2)">Ya montado en una unidad</span></span>'
|
|
+'<span><b>·</b> Pendiente</span>'
|
|
+'<span>· Toca una tarjeta para ver o llenar su acta.</span>';
|
|
cont.append(ley);
|
|
// actividad reciente + por técnico
|
|
const dos=document.createElement('div'); dos.className='dos';
|
|
const rec=document.createElement('section'); rec.className='bloque';
|
|
rec.innerHTML='<h2>Actividad reciente</h2>';
|
|
const tbl=document.createElement('table');
|
|
tbl.innerHTML='<tr><th>Serie</th><th>Técnico</th><th>Resultado</th><th>Fecha</th></tr>';
|
|
actas.slice(0,12).forEach(a=>{
|
|
const tr=document.createElement('tr');
|
|
const p = a.resultado==='FALLA'?'<span class="pill falla">FALLA</span>':'<span class="pill apro">OK</span>';
|
|
tr.innerHTML='<td><b>'+(a.serie||'')+'</b></td><td>'+(a.tecnico||'')+'</td><td>'+p+'</td><td>'+fmt(a.recibido)+'</td>';
|
|
tbl.append(tr);
|
|
});
|
|
rec.append(tbl); if(!actas.length) rec.innerHTML+='<p class="resumen">Aún no hay actas registradas.</p>';
|
|
const pt=document.createElement('section'); pt.className='bloque';
|
|
pt.innerHTML='<h2>Por técnico</h2>';
|
|
const cnt={}; actas.forEach(a=>{ const t=(a.tecnico||'—').trim()||'—'; cnt[t]=(cnt[t]||0)+1; });
|
|
const t2=document.createElement('table'); t2.innerHTML='<tr><th>Técnico</th><th>Actas</th></tr>';
|
|
Object.entries(cnt).sort((x,y)=>y[1]-x[1]).forEach(([t,c])=>{ const tr=document.createElement('tr'); tr.innerHTML='<td>'+t+'</td><td>'+c+'</td>'; t2.append(tr); });
|
|
pt.append(t2); if(!actas.length) pt.innerHTML+='<p class="resumen">—</p>';
|
|
dos.append(rec,pt); cont.append(dos);
|
|
}
|
|
|
|
async function cargar(){
|
|
meta.textContent='Actualizando…';
|
|
try{
|
|
const r=await fetch('/api/actas',{cache:'no-store'});
|
|
const j=await r.json();
|
|
if(!j.ok) throw new Error(j.error||'error');
|
|
render(j.actas||[]);
|
|
meta.textContent='Actualizado '+new Date().toLocaleTimeString('es-MX',{hour:'2-digit',minute:'2-digit'})+' · '+(j.total||0)+' actas';
|
|
}catch(e){
|
|
cont.innerHTML='<div class="err">No se pudo cargar el tablero: '+e.message+'</div>';
|
|
meta.textContent='Error';
|
|
}
|
|
}
|
|
document.getElementById('refrescar').addEventListener('click',cargar);
|
|
cargar();
|
|
setInterval(cargar, 30000);
|
|
})();
|
|
</script>
|