hbox-docs/docs/tableros/actas.md

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>