Personal
🔒 Salir

Lista de personal

Cargando...

Nuevo empleado
Total empleados
Aluminio
Madera
Melamina
#ID RelojNombreSectorValor / FijoCobra extrasAcciones

Importar fichadas

Subí el archivo o pegá el contenido del Excel

Clic o arrastrá el archivo aquí

Archivo .xlsx del reloj marcador

Liquidación

✓ Semana guardada en el historial.
Empleados
Total horas
Total neto
Con extras
Empleado Sector $/hr Lun Mar Mié Jue Vie Total hs Xtra⚡ $Xtra Bruto 💵 Adel. Neto Est. No cobra / Desest.

Recibos

EmpleadoSectorHorasBrutoAdelantoNeto

Historial de semanas

Cargando...

Comparar semanas

SEMANA A
SEMANA B

Acumulado total

EmpleadoSectorSemanasTotal horasTotal pagadoAdelantosPromedio/sem

Exportar datos

Descargá la información en distintos formatos

📊
Excel — Semana actual
Liquidación completa con horas, extras, bruto, adelanto y neto.
📋
Excel — Historial completo
Todas las semanas en una planilla con resumen y acumulado.
🧾
PDF — Todos los recibos
5 recibos por hoja A4, listos para imprimir o guardar como PDF.
🔄
Sincronizar con tu compañera
Para pasar los datos de una PC a otra:
1. En la PC que tiene los datos → Exportar backup
2. Mandá el archivo .json por WhatsApp / USB
3. En la otra PC → Importar backup
📋
Nómina — Nombre + Monto
Excel con nombre, sector, horas/tipo y total a cobrar.
Opciones

Personal — RRHH

Cargando...

Activos
Total bajas
Sin motivo ⚠
Reingresaron
N° RelojRubroNombreDNITeléfonoF. IngresoSeguroObservacionesVacacionesÚltimo SAC

Aguinaldos y Vacaciones

Fórmula SAC (Aguinaldo):  Semestre completo: valor_hora × 85hs |  Proporcional: valor_hora × 85hs × (días trabajados ÷ días del semestre)  — Junio: 181 días · Diciembre: 184 días — La columna Manual permite ingresar un monto específico por empleado
Empleados
Total SAC
Proporcionales
Completos
Empleado Sector F. Ingreso RRHH Días trabajados Valor hora Tipo Cuenta detallada Manual Monto final
TOTAL SAC:

Gestión de Usuarios

Usuarios que tienen acceso a la app

Usuarios con acceso
Math.round(n).toLocaleString('es-AR'); const fh=h=>{if(!h&&h!==0)return null;const hh=Math.floor(h),mm=Math.round((h-hh)*60);return`${String(hh).padStart(2,'0')}:${String(mm).padStart(2,'0')}`;}; const c1k=n=>Math.ceil(n/1000)*1000; // Convierte horas decimales a notación HH.MM (como usa la calculadora: 11h37m → 11.37) const toHHMM=h=>{if(!h&&h!==0)return 0;const hh=Math.floor(h),mm=Math.round((h-hh)*60);return hh+mm/100;}; function toast(m,t='ok'){const e=document.getElementById('toast');e.textContent=m;e.className='toast-el '+t;clearTimeout(e._t);e._t=setTimeout(()=>e.className='toast-el',2500);} function lsG(k){try{return localStorage.getItem(k);}catch{return null;}} function lsS(k,v){try{localStorage.setItem(k,v);}catch{}} function init(){ // 1. Carga inmediata desde localStorage (sin esperar al servidor) try{const r=lsG(SKP);const _ls=r?JSON.parse(r):null;P=(_ls&&_ls.length>0)?_ls:D0.map(d=>({...d}));}catch{P=D0.map(d=>({...d}));} try{const r=lsG(SKH);H=r?JSON.parse(r):[];}catch{H=[];} try{const r=lsG(SKM);MAPEO=r?JSON.parse(r):{};}catch{MAPEO={};} rP();rH(); // 2. Sync silencioso con servidor en background (actualiza si hay cambios) apiGet('personal').then(data=>{ if(data&&data.length){ const remoto=JSON.stringify(data); const local=JSON.stringify(P); if(remoto!==local){ P=data; lsS(SKP,JSON.stringify(P)); rP(); } } }).catch(()=>{}); // 3. Asegurar titulo del topbar al cargar const _tb=document.getElementById('topbar-title'); if(_tb)_tb.textContent='Personal'; // 4. Si hay liquidación guardada, habilitar botones Liquidación y Recibos apiGet('liq-actual').then(data=>{ if(data&&data.rows&&data.rows.length){ LQ=data; const _nl=document.getElementById('nb-liq'); const _nr=document.getElementById('nb-recibos'); if(_nl){_nl.style.opacity='';_nl.style.pointerEvents='';} if(_nr){_nr.style.opacity='';_nr.style.pointerEvents='';} // Mostrar indicador de semana disponible const _as=document.getElementById('liq-autosave'); if(_as)_as.textContent='💾 '+data.wl; } }).catch(()=>{}); } function sP(n=true){ lsS(SKP,JSON.stringify(P)); apiPost('personal',P).catch(()=>{}); if(n)toast('Guardado ✓'); } function sH(){lsS(SKH,JSON.stringify(H));} function sM(){lsS(SKM,JSON.stringify(MAPEO));} const gPe=nom=>P.find(p=>p.n.toUpperCase()===nom.toUpperCase())||null; function resolverNombre(nr){ const raw=nr.trim().toUpperCase().replace(/\s*\(.*?\)/g,'').trim(); if(MAPEO[raw])return MAPEO[raw]; const exacto=P.find(p=>p.n===raw);if(exacto)return raw; const partes=raw.split(' '); if(partes.length>=2){ const inv=partes.slice(1).join(' ')+' '+partes[0]; const m=P.find(p=>p.n===inv);if(m)return inv; const m2=P.find(p=>{const pp=p.n.split(' ');return partes.every(w=>pp.includes(w))&&pp.every(w=>partes.includes(w));}); if(m2)return m2.n; } return null; } function resolverPorId(empId){ if(empId===null||empId===undefined||empId==='')return null; const clean=String(empId).replace(/^'+|'+$/g,'').trim(); const id=parseInt(clean); if(isNaN(id)||id===0)return null; const p=P.find(p=>p.id===id); return p?p.n:null; } function gp(p){ document.querySelectorAll('.page').forEach(e=>e.classList.remove('active')); document.querySelectorAll('.nb').forEach(e=>e.classList.remove('active')); document.getElementById('page-'+p).classList.add('active'); // Activar botón del sidebar por id const nbBtn=document.getElementById('nb-'+p); if(nbBtn){nbBtn.classList.add('active');nbBtn.style.opacity='';nbBtn.style.pointerEvents='';} // Actualizar título del topbar const tbar=document.getElementById('topbar-title'); if(tbar)tbar.textContent=PAGE_TITLES[p]||p; if(p==='exportar')setTimeout(actualizarEstadoExport,50); if(p==='rrhh'){ if(!RRHH.length)loadRRHH(); if(!SV_DATA)loadSACVAC(); } if(p==='liq'){ if(!LQ){ cargarLiqActual(); } else { // Refrescar tabla al volver a la pestaña rL();rR(); } } if(p==='sacvac'){if(!SV_DATA)loadSACVAC();else calcularSAC();} if(p==='usuarios')loadUsuarios(); } window.gp=gp;window.toggleSidebar=toggleSidebar; // ── PERSONAL ── function rP(){ const q=document.getElementById('ps').value.toLowerCase(),f=document.getElementById('pf').value; const arr=P.map((p,i)=>({...p,_i:i})).filter(p=>(!q||p.n.toLowerCase().includes(q))&&(!f||p.s===f)); document.getElementById('spt').textContent=P.length; document.getElementById('spa').textContent=P.filter(p=>p.s==='ALUMINIO').length; document.getElementById('spm').textContent=P.filter(p=>p.s==='MADERA').length; document.getElementById('spme').textContent=P.filter(p=>p.s==='MELAMINA').length; document.getElementById('p-sub').textContent=P.length+' empleados — cambios guardados automáticamente'; const tb=document.getElementById('ptb'); if(!arr.length){tb.innerHTML='';return;} tb.innerHTML=arr.map((p,fi)=>{ const i=p._i,val=p.fijo?`Fijo $${fm(p.fijo)}/${p.fijo_tipo==='mensual'?'mes':'sem'}`:`$${fm(p.vh)}/hr`; const idBadge=p.id?`${p.id}`:``; return``; }).join(''); } function tEx(i,v){P[i].ex=v;sP(false);} let _editIdx=-1; function eP(i){editP(i);} function eId(i){const p=P[i];const v=window.prompt(`ID del reloj para ${p.n}:\n(0 = sin ID asignado)`,p.id||0);if(v===null)return;const n=parseInt(v);if(isNaN(n))return;P[i].id=n;sP();rP();toast('✓ ID actualizado para '+p.n);} function dP(i){if(!confirm(`¿Eliminar a ${P[i].n}?`))return;P.splice(i,1);sP();rP();} function taf(){ _editIdx=-1; const f=document.getElementById('aform');f.classList.toggle('open'); document.getElementById('btn-add').textContent=f.classList.contains('open')?'× Cancelar':'+ Agregar empleado'; if(f.classList.contains('open')){ document.getElementById('aform-title').textContent='Nuevo empleado'; document.getElementById('nn').value='';document.getElementById('nv').value='';document.getElementById('nvf').value=''; document.getElementById('nt').value='hora';tnt();document.getElementById('nn').focus(); } } function editP(i){ const p=P[i];_editIdx=i; const f=document.getElementById('aform');f.classList.add('open'); document.getElementById('btn-add').textContent='× Cancelar'; document.getElementById('aform-title').textContent='Editar: '+p.n; document.getElementById('nn').value=p.n; document.getElementById('ns').value=p.s; if(p.fijo!==null){document.getElementById('nt').value='fijo';document.getElementById('nvf').value=p.fijo;} else{document.getElementById('nt').value='hora';document.getElementById('nv').value=p.vh;} tnt();f.scrollIntoView({behavior:'smooth'});document.getElementById('nn').focus(); } function tnt(){const t=document.getElementById('nt').value;document.getElementById('nfh').style.display=t==='hora'?'flex':'none';document.getElementById('nff').style.display=t==='fijo'?'flex':'none';document.getElementById('nfft').style.display=t==='fijo'?'flex':'none';} function addP(){ const n=document.getElementById('nn').value.trim().toUpperCase(),s=document.getElementById('ns').value; const t=document.getElementById('nt').value,vh=parseFloat(document.getElementById('nv').value)||0,fijo=parseFloat(document.getElementById('nvf').value)||0; const err=document.getElementById('aerr'); if(!n){err.textContent='Ingresá el nombre';err.style.display='inline';return;} err.style.display='none'; if(_editIdx>=0){ if(n!==P[_editIdx].n&&P.find((p,i)=>i!==_editIdx&&p.n===n)){err.textContent='Ya existe';err.style.display='inline';return;} P[_editIdx].n=n;P[_editIdx].s=s;P[_editIdx].vh=t==='hora'?vh:0;P[_editIdx].fijo=t==='fijo'?fijo:null; P.sort((a,b)=>a.n.localeCompare(b.n));_editIdx=-1;taf();sP();rP();toast('✓ '+n+' actualizado'); } else { if(P.find(p=>p.n===n)){err.textContent='Ya existe';err.style.display='inline';return;} P.push({n,s,vh:t==='hora'?vh:0,fijo:t==='fijo'?fijo:null,fijo_tipo:t==='fijo'?document.getElementById('nft').value:'semanal',ex:false,id:0}); P.sort((a,b)=>a.n.localeCompare(b.n)); document.getElementById('nn').value='';document.getElementById('nv').value='';document.getElementById('nvf').value=''; taf();sP();rP();toast('✓ '+n+' agregado'); } } // ── FICHADAS ── function switchTab(tab){ document.getElementById('tab-content-archivo').style.display=tab==='archivo'?'block':'none'; document.getElementById('tab-content-pegar').style.display=tab==='pegar'?'block':'none'; document.getElementById('tab-archivo').style.cssText=tab==='archivo'?'font-size:12.5px;padding:8px 16px;border:none;border-bottom:2px solid #111;background:none;cursor:pointer;font-weight:600;color:#111':'font-size:12.5px;padding:8px 16px;border:none;border-bottom:2px solid transparent;background:none;cursor:pointer;color:#666'; document.getElementById('tab-pegar').style.cssText=tab==='pegar'?'font-size:12.5px;padding:8px 16px;border:none;border-bottom:2px solid #111;background:none;cursor:pointer;font-weight:600;color:#111':'font-size:12.5px;padding:8px 16px;border:none;border-bottom:2px solid transparent;background:none;cursor:pointer;color:#666'; } function hDrop(e){e.preventDefault();document.getElementById('udrop').classList.remove('drag');const f=e.dataTransfer.files[0];if(f)hFile(f);} function sus(m,t){const e=document.getElementById('ustat');e.textContent=m;e.className='sbar '+t;} function limpiarStr(v){ if(v===null||v===undefined)return''; return String(v).replace(/^['"]+|['"]+$/g,'').trim(); } function hFile(file){ if(!file)return; sus('Procesando el archivo... por favor esperá','inf2'); const fd=new FormData(); fd.append('file',file); fetch('/api/upload',{method:'POST',body:fd}) .then(r=>{ if(!r.ok && r.status===401){ sus('⚠️ Sesión expirada — cerrá y volvé a abrir la app','err'); setTimeout(()=>window.location.href='/login-page',2000); return null; } return r.json(); }) .then(data=>{ if(!data)return; if(data.error){ let msg='❌ Error del servidor: '+data.error; if(data.detalle)console.error('DETALLE:',data.detalle); if(data.debug_tipos){ msg+='\n\nColumna tipo (col 4) tiene: '+data.debug_tipos.slice(0,8).join(' | '); msg+='\n\nFila muestra: '+(data.debug_rows&&data.debug_rows[0]?data.debug_rows[0].join(' | '):'?'); } sus(msg,'err'); alert(msg); return; } if(!data.rows||!data.rows.length){ let msg='⚠️ El archivo no tiene filas de Entrada/Salida válidas.'; if(data.debug_tipos&&data.debug_tipos.length) msg+='\nColumna tipo encontrada: '+data.debug_tipos.slice(0,5).join(' | '); if(data.debug_rows&&data.debug_rows.length) msg+='\nPrimera fila: '+data.debug_rows[0].join(' | '); sus(msg,'err'); alert(msg); return; } sus('✓ '+data.total+' fichadas leídas, '+data.empleados+' empleados','ok2'); const rows=data.rows.map(r=>({ fecha:r.fecha, hora:r.hora, tipo:r.tipo, nombreReloj:r.nombre, empId:null })); if(data.no_resueltos&&data.no_resueltos.length>0){ pendingRows=rows; mostrarMapeo(data.no_resueltos); sus('⚠️ '+rows.length+' fichadas. '+data.no_resueltos.length+' nombre(s) no reconocido(s) — asignálós abajo.','inf2'); } else { pendingRows=rows; procesarYMostrar(rows); } }) .catch(e=>{ sus('❌ Error de conexión: '+e.message,'err'); alert('Error de conexión con el servidor:\n'+e.message+'\n\nAsegurate de que la app esté corriendo.'); }); } function previewPaste(txt){ if(!txt.trim()){document.getElementById('paste-preview').textContent='';return;} const rows=parsePasteTxt(txt); document.getElementById('paste-preview').innerHTML=rows.length>0 ?`✓ ${rows.length} fichadas detectadas` :`No se detectaron fichadas.`; } function procesarPaste(){ const txt=document.getElementById('paste-area').value; if(!txt.trim()){sus('Pegá el contenido del Excel primero.','err');return;} const rows=parsePasteTxt(txt); if(!rows.length){sus('No se encontraron fichadas. Verificá el formato.','err');return;} procesarConMapeo(rows); } function parsePasteTxt(txt){ const rows=[]; const lines=txt.split(/\r?\n/); let ultimoNombre=null; for(let li=0;lic.trim().replace(/^['"]+|['"]+$/g,'').trim()); const noEmpty=vals.filter(v=>v); if(noEmpty.length===1){ const v=noEmpty[0]; if(/^[A-Za-zÀ-ž]{3,}(\s[A-Za-zÀ-ž]{2,})*$/.test(v)&&!['Entrada','Salida','local'].includes(v)){ if(li+1c.trim().replace(/^['"]+|['"]+$/g,'').trim()).filter(v=>v); if(nextVals.length===1&&/^[A-Za-zÀ-ž]{3,}$/.test(nextVals[0])&&!nextVals[0].includes('/')){ ultimoNombre=(v+' '+nextVals[0]).toUpperCase();li++;continue; } } ultimoNombre=v.toUpperCase();continue; } } let fe=null,ho=null,ti=null,no=ultimoNombre,empId=null; for(let ci=0;ci0&&parseInt(v)<300&&fe&&ti)empId=parseInt(v); if(!no&&v.length>4&&/^[A-Z][A-Z\s]{3,}$/.test(v)&&v!=='Entrada'&&v!=='Salida'&&v!=='local')no=v; } if(!fe||!ho||!ti)continue; const nombrePorId=resolverPorId(empId); const nombreFinal=nombrePorId||(no?no.trim().toUpperCase():null); if(!nombreFinal)continue; const p=ho.split(':');const hd=parseInt(p[0])+(parseInt(p[1])/60); if(isNaN(hd))continue; rows.push({fecha:fe,hora:hd,tipo:ti,nombreReloj:nombreFinal,empId}); } return rows; } function procesarConMapeo(rows){ const nombresEnReloj=[...new Set(rows.map(r=>r.nombreReloj))]; const noReconocidos=[]; for(const nr of nombresEnReloj){if(!resolverNombre(nr))noReconocidos.push(nr);} if(noReconocidos.length>0){ pendingRows=rows;mostrarMapeo(noReconocidos); sus(`⚠️ ${rows.length} fichadas leídas. ${noReconocidos.length} nombre(s) no reconocido(s).`,'inf2'); } else { pendingRows=rows;procesarYMostrar(rows); } } function mostrarMapeo(noReconocidos){ const wrap=document.getElementById('mapeo-wrap'); const opciones=P.map(p=>``).join(''); document.getElementById('mapeo-list').innerHTML=noReconocidos.map(nr=>`
📌 ${nr}
`).join(''); wrap.style.display='block'; wrap._pendingNames=noReconocidos; } function aplicarMapeo(){ const wrap=document.getElementById('mapeo-wrap'); const noReconocidos=wrap._pendingNames||[]; for(const nr of noReconocidos){ const key=btoa(unescape(encodeURIComponent(nr))).replace(/[^a-zA-Z0-9]/g,'').substring(0,20); const sel=document.getElementById('map-'+key); if(!sel||!sel.value||sel.value==='__IGNORAR__')continue; const raw=nr.trim().toUpperCase().replace(/\s*\(.*?\)/g,'').trim(); MAPEO[raw]=sel.value; } sM(); wrap.style.display='none'; procesarYMostrar(pendingRows); rP(); } function procesarYMostrar(rows){ if(!P||!P.length){P=D0.map(d=>({...d}));console.warn('P vacío, usando D0');} const rowsRes=rows.map(r=>{ const esExacto=P.find(p=>p.n===r.nombreReloj); const nombre=esExacto?r.nombreReloj:resolverNombre(r.nombreReloj); return nombre?{...r,nombre}:null; }).filter(Boolean); if(!rowsRes.length){ const dbg='⚠️ '+rows.length+' fichadas recibidas pero 0 empleados matchearon.\nNombres del reloj: '+[...new Set(rows.map(r=>r.nombreReloj))].slice(0,6).join(', ')+'\n\nPersonal cargado (primeros 4): '+P.slice(0,4).map(p=>p.n).join(', '); sus(dbg,'err');alert(dbg);return; } const res=procesar(rowsRes); const DI=['Lun','Mar','Mié','Jue','Vie']; for(const pe of P){ if(res.rows.find(r=>r.nombre===pe.n))continue; const ddVacio=res.weekDays.map((fecha,i)=>({dia:DI[i],fecha,e:null,s:null,h:null})); res.rows.push({nombre:pe.n,s:pe.s,vh:pe.vh,eF:pe.fijo!==null,cE:pe.ex, dd:ddVacio,th:0,hx:0,bruto:pe.fijo||0,red:pe.fijo||0,adel:0,neto:pe.fijo||0, obs:[],infractions:[{dia:'semana',tipo:'manual',msg:'No registró fichadas — cargá las horas a mano'}], wd:res.weekDays,esManual:true}); } res.rows.sort((a,b)=>a.nombre.localeCompare(b.nombre)); LQ=res; const _nbl=document.getElementById('nb-liq');const _nbr=document.getElementById('nb-recibos');if(_nbl){_nbl.style.opacity='';_nbl.style.pointerEvents='';}if(_nbr){_nbr.style.opacity='';_nbr.style.pointerEvents='';} document.getElementById('cbar').style.display='none'; sus(`✓ ${rowsRes.length} fichadas → ${res.rows.length} empleados. Semana: ${res.wl}`,'ok2'); gp('liq');rL();rR(); guardarLiqActual();rTotalFila(); } function crearManual(){ const semana=window.prompt('Ingresá la fecha de inicio de la semana (ej: 19/05/26):',''); if(!semana)return; const m=semana.match(/(\d{1,2})\/(\d{1,2})\/(\d{2,4})/); if(!m){alert('Formato incorrecto. Usá dd/mm/aa');return;} const yy=m[3].length===4?m[3].slice(-2):m[3]; const lunes=`${m[1].padStart(2,'0')}/${m[2].padStart(2,'0')}/${yy}`; const dt=new Date(2000+parseInt(yy),parseInt(m[2])-1,parseInt(m[1])); const wd=[];const DI=['Lun','Mar','Mié','Jue','Vie']; for(let i=0;i<5;i++){const d=new Date(dt);d.setDate(dt.getDate()+i);wd.push(`${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}/${yy}`);} const wl=`${wd[0].substring(0,5)} al ${wd[4].substring(0,5)}`; const rows=P.map(pe=>{ const ddVacio=wd.map((fecha,i)=>({dia:DI[i],fecha,e:null,s:null,h:null})); return{nombre:pe.n,s:pe.s,vh:pe.vh,eF:pe.fijo!==null,cE:pe.ex, dd:ddVacio,th:0,hx:0,bruto:pe.fijo||0,red:pe.fijo||0,adel:0,neto:pe.fijo||0, obs:[],infractions:[{dia:'semana',tipo:'manual',msg:'Carga manual'}],wd,esManual:true}; }); LQ={rows,weekDays:wd,wl}; const _nbl=document.getElementById('nb-liq');const _nbr=document.getElementById('nb-recibos');if(_nbl){_nbl.style.opacity='';_nbl.style.pointerEvents='';}if(_nbr){_nbr.style.opacity='';_nbr.style.pointerEvents='';} document.getElementById('cbar').style.display='none'; gp('liq');rL();rR();rTotalFila(); toast('✓ Liquidación manual creada para '+wl); } const parseFechaStr=s=>{const[d,m,y]=s.split('/').map(Number);return new Date(2000+y,m-1,d);}; function procesar(rows){ const byP={}; for(const r of rows){if(!byP[r.nombre])byP[r.nombre]=[];byP[r.nombre].push(r);} const fcs=[...new Set(rows.map(r=>r.fecha))].sort((a,b)=>parseFechaStr(a)-parseFechaStr(b)); const first=parseFechaStr(fcs[0]),dow=first.getDay(),mon=new Date(first);mon.setDate(first.getDate()-(dow===0?6:dow-1)); const wd=[]; for(let i=0;i<5;i++){const d=new Date(mon);d.setDate(mon.getDate()+i);wd.push(`${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}/${String(d.getFullYear()).slice(-2)}`);} const DI=['Lun','Mar','Mié','Jue','Vie'],result=[]; for(const[nombre,fi]of Object.entries(byP)){ const pe=gPe(nombre),s=pe?pe.s:'',vh=pe?pe.vh:0,eF=pe&&pe.fijo!==null,cE=pe?pe.ex:false; const dias={}; for(const f of fi){if(!dias[f.fecha])dias[f.fecha]={e:[],s:[]};f.tipo==='Entrada'?dias[f.fecha].e.push(f.hora):dias[f.fecha].s.push(f.hora);} const SALIDA_VIE=nombre==='SALDIVAR RODRIGO'?17.0:16.5; const dd=wd.map((fecha,i)=>{ const d=dias[fecha];if(!d||!d.e.length)return{dia:DI[i],fecha,e:null,s:null,h:null}; const e=Math.min(...d.e); let s=d.s.length?Math.max(...d.s):(i===4?SALIDA_VIE:null); return{dia:DI[i],fecha,e,s,h:s!==null?s-e:null,autoSal:d.s.length===0&&i===4}; }); const th=dd.reduce((s,d)=>s+(d.h||0),0); const hx=cE?Math.max(0,th-HS_MIN):0; // Sin extras: TODAS las horas a tarifa normal (sin cap 42.5) // Con extras: hasta 42.5 normal, por encima x1.5 const thPay=cE?Math.min(th,HS_MIN):th; const thMM=toHHMM(thPay); const hxMM=toHHMM(hx); // Fijo: si es mensual, dividir por 4 semanas const fijoSem=eF?(pe.fijo_tipo==='mensual'?Math.round(pe.fijo/4):pe.fijo):0; const bruto=eF?fijoSem:(thMM*vh+hxMM*vh*1.5); const red=c1k(bruto); const obs=[],infractions=[]; for(const d of dd){ if(!d.e&&!d.s){infractions.push({dia:d.dia,tipo:'ausente',msg:`${d.dia}: ausente`});obs.push(`${d.dia}: ausente`);} else{ if(d.e&&!d.s){infractions.push({dia:d.dia,tipo:'sin_salida',msg:`${d.dia}: sin salida`});obs.push(`${d.dia}: sin salida`);} if(d.e&&d.e>LIMITE_E){const min=Math.round((d.e-LIMITE_E)*60);infractions.push({dia:d.dia,tipo:'tarde',msg:`${d.dia}: llegó tarde (${fh(d.e)}, ${min}min)`});obs.push(`${d.dia}: tarde`);} if(d.h&&d.h<3){infractions.push({dia:d.dia,tipo:'jornada_corta',msg:`${d.dia}: jornada corta`});obs.push(`${d.dia}: jornada corta`);} } } if(th>0&&tha.nombre.localeCompare(b.nombre)); return{rows:result,weekDays:wd,wl:`${wd[0].substring(0,5)} al ${wd[4].substring(0,5)}`}; } function calcInfractions(e){ const obs=[],infractions=[]; for(const d of e.dd){ if(!d.e&&!d.s){infractions.push({dia:d.dia,tipo:'ausente',msg:`${d.dia}: ausente`});obs.push(`${d.dia}: ausente`);} else{ if(d.e&&!d.s){infractions.push({dia:d.dia,tipo:'sin_salida',msg:`${d.dia}: sin salida`});obs.push(`${d.dia}: sin salida`);} if(d.e&&d.e>LIMITE_E){const min=Math.round((d.e-LIMITE_E)*60);infractions.push({dia:d.dia,tipo:'tarde',msg:`${d.dia}: tarde (${fh(d.e)}, ${min}min)`});obs.push(`${d.dia}: tarde`);} if(d.h&&d.h<3){infractions.push({dia:d.dia,tipo:'jornada_corta',msg:`${d.dia}: jornada corta`});} } } if(e.th>0&&e.th(!q||e.nombre.toLowerCase().includes(q))&&(!f||e.s===f)); o==='neto'?arr.sort((a,b)=>b.neto-a.neto):o==='hs'?arr.sort((a,b)=>b.th-a.th):arr.sort((a,b)=>a.nombre.localeCompare(b.nombre)); document.getElementById('lt').textContent='Liquidación — '+LQ.wl; document.getElementById('ls2').textContent=LQ.rows.length+' empleados'; const _activos=arr.filter(e=>!e.desestimar); document.getElementById('lse').textContent=_activos.length; document.getElementById('lsh').textContent=fh(_activos.reduce((s,e)=>s+e.th,0))||'0'; document.getElementById('lsn').textContent='$'+fm(_activos.reduce((s,e)=>s+(e.noCobra?0:e.neto),0)); document.getElementById('lsx').textContent=_activos.filter(e=>e.hx>0).length; document.getElementById('ltb').innerHTML=arr.map(e=>{ const idx=LQ.rows.indexOf(e); const esSaldivar=e.nombre==='SALDIVAR RODRIGO'; const salVieStr=esSaldivar?'17:00':'16:30'; const iStyle='font-size:10px;padding:2px 3px;border:1px solid #aaa;border-radius:3px;width:52px;background:#fff'; const dc=e.dd.map((d,di)=>{ const salDefault=di===4?(d.s?fh(d.s):salVieStr):(fh(d.s)||''); const inpBlock=``; if(!d.e&&!d.s){ const vieTag=di===4?`
→${salVieStr}
`:''; return`
`; } const tarde=d.e&&d.e>LIMITE_E,minTarde=tarde?Math.round((d.e-LIMITE_E)*60):0; const extra=d.h&&d.h>JN; const bgColor=(!d.s)?'#fff3e0':tarde?'#fff3e0':extra?'#f1f8e9':''; const labelColor=(!d.s)?'#e65100':tarde?'#e65100':extra?'#2e7d32':'#555'; const label=(!d.s)?`⚠ Sin salida`:tarde?`⚠ +${minTarde}min`:extra?`⚡ ${fh(d.h)}hs`:`${fh(d.h)}hs`; const autoTag=d.autoSal?` auto`:''; return``; }).join(''); const hxMM=toHHMM(e.hx); const pagoExtra=e.eF?0:Math.round(hxMM*e.vh*1.5); const extraCols=e.cE?``:``; // badges: mostrar todos los problemas juntos const inf=e.infractions||[]; const badges=[]; if(inf.some(i=>i.tipo==='ausente')){const n=inf.filter(i=>i.tipo==='ausente').length;badges.push(`${n}d ausente`);} if(inf.some(i=>i.tipo==='tarde')){const n=inf.filter(i=>i.tipo==='tarde').length;badges.push(`${n>1?n+'x ':''}Tarde`);} if(inf.some(i=>i.tipo==='sin_salida')){badges.push(`Sin salida`);} if(inf.some(i=>i.tipo==='hs_minimas')){badges.push(`Hs bajas`);} if(inf.some(i=>i.tipo==='manual')){badges.push(`Manual`);} const infBadge=badges.length?badges.join(' '):`OK`; // total hs: rojo si está bajo el mínimo const thStyle=(!e.eF&&e.th>0&&e.th0&&e.thFijo
$${fm(e.bruto)}` :`
`; return` ${vhCell} ${dc} ${extraCols} `; }).join(''); rTotalFila(); } function sAdel(i,v){const n=parseFloat(v)||0;LQ.rows[i].adel=n;LQ.rows[i].neto=LQ.rows[i].red-n;rL();rR();guardarLiqActual();_syncPausado=true;setTimeout(()=>_syncPausado=false,30000);} function rTotalFila(){ if(!LQ)return; const arr=LQ.rows; const totB=arr.reduce((s,e)=>s+e.red,0); const totA=arr.reduce((s,e)=>s+e.adel,0); const totN=arr.reduce((s,e)=>s+e.neto,0); const cols=11; // Empleado+Sector+$/hs+5días+Total+Xtra⚡+$Xtra const tf=document.getElementById('ltf'); if(!tf)return; tf.innerHTML=``; tf.style.display=totalVis?'':'none'; } function tgTotal(){ totalVis=!totalVis; const tf=document.getElementById('ltf'); if(tf)tf.style.display=totalVis?'':'none'; const btn=document.getElementById('btn-total'); if(btn){btn.textContent=totalVis?'Σ Ocultar total':'Σ Ver total';btn.style.background=totalVis?'#111':'';btn.style.color=totalVis?'#fff':'';} } function abrirCelda(idx,di){ const ce=document.getElementById(`ce_${idx}_${di}`); const vv=document.getElementById(`vv_${idx}_${di}`); if(!ce)return; ce.style.display='flex'; if(vv)vv.style.display='none'; const inp=ce.querySelector('input[type=time]'); if(inp)setTimeout(()=>inp.focus(),50); } function cerrarCelda(idx,di){ const ce=document.getElementById(`ce_${idx}_${di}`); const vv=document.getElementById(`vv_${idx}_${di}`); if(!ce)return; ce.style.display='none'; if(vv)vv.style.display='block'; } function setHoraManual(empIdx,diaIdx,campo,valor){ const e=LQ.rows[empIdx],d=e.dd[diaIdx]; const p=valor.split(':');const hd=parseInt(p[0])+(parseInt(p[1])/60); if(isNaN(hd))return; if(campo==='e')d.e=hd;else d.s=hd; if(diaIdx===4&&d.e!==null&&d.s===null){d.s=e.nombre==='SALDIVAR RODRIGO'?17.0:16.5;d.autoSal=true;} if(d.e!==null&&d.s!==null){ const diff=d.s-d.e; if(diff<=0){d.e=null;d.s=null;d.h=null;toast('⚠ Horas = 0 → marcado como ausente','err');} else d.h=diff; } const th=e.dd.reduce((s,dd)=>s+(dd.h||0),0); const hx=e.cE?Math.max(0,th-HS_MIN):0; const thPay=e.cE?Math.min(th,HS_MIN):th; const thMM=toHHMM(thPay); const hxMM=toHHMM(hx); const _fijoSem=e.eF?(e.fijo_tipo==='mensual'?Math.round(e.fijo/4):e.fijo):0; const bruto=e.eF?_fijoSem:(thMM*e.vh+hxMM*e.vh*1.5); const red=c1k(bruto); e.th=th;e.hx=hx;e.bruto=bruto;e.red=red;e.neto=red-e.adel; calcInfractions(e); toast('✓ '+e.nombre+' — '+d.dia+' actualizado'); rL();rR(); } function confirmar(){ if(!LQ)return; if(H.find(h=>h.sem===LQ.wl)){if(!confirm(`La semana "${LQ.wl}" ya está guardada. ¿Reemplazarla?`))return;H=H.filter(h=>h.sem!==LQ.wl);} H.unshift({sem:LQ.wl,wd:LQ.weekDays,fecha:new Date().toLocaleDateString('es-AR'), rows:LQ.rows.map(e=>({n:e.nombre,s:e.s,vh:e.vh,fijo:e.fijo,fijo_tipo:e.fijo_tipo,cE:e.cE,eF:e.eF,th:e.th,hx:e.hx,red:e.red,adel:e.adel,neto:e.neto,obs:e.obs,infractions:e.infractions||[],wd:e.wd||LQ.weekDays,dd:e.dd||[]})), tn:LQ.rows.reduce((s,e)=>s+e.neto,0),tth:LQ.rows.reduce((s,e)=>s+e.th,0),cnt:LQ.rows.length}); sH(); document.getElementById('cbar').style.display='block'; rH(); guardarLiqActual(); // Quedar en Liquidación con la tabla visible gp('liq'); rL();rR(); toast('✓ Semana '+LQ.wl+' guardada — podés seguir editando'); } function oDet(nombre){ const e=LQ.rows.find(r=>r.nombre===nombre);if(!e)return; document.getElementById('pnm').textContent=e.nombre; const hsNormP=e.cE?Math.min(e.th,HS_MIN):e.th,hsExtraP=e.hx||0; const pagoNormP=e.eF?0:Math.round(hsNormP*e.vh),pagoExtraP=e.eF?0:Math.round(hsExtraP*e.vh*1.5); const LENT=LIMITE_E; const cards=e.dd.map(d=>{ const aus=!d.e,tarde=d.e&&d.e>LENT,sinSal=d.e&&!d.s,extra=d.h&&d.h>JN; const minT=tarde?Math.round((d.e-LENT)*60):0; let bg='background:#f9f9f7',border='border:1px solid #eee'; if(aus){bg='background:#ffebee';border='border:1px solid #ffcdd2';} else if(tarde){bg='background:#fff8e1';border='border:1px solid #ffe082';} else if(sinSal){bg='background:#fff3e0';border='border:1px solid #ffcc02';} return`
${d.dia}${tarde?' ⚠':''}
${aus?'AUSENTE':fh(d.e)||'—'}
${d.s?fh(d.s):sinSal?'sin salida':''}
${d.h?fh(d.h)+'hs':'—'}
${tarde?`
+${minT}min tarde
`:''}
`; }).join(''); const diasExtra=e.dd.filter(d=>d.h&&d.h>JN).map(d=>`
${d.dia}: ${fh(d.h)}hs total+${fh(d.h-JN)} extra
`).join(''); document.getElementById('pbody').innerHTML=`
Sector${e.s}
Valor hora$${fm(e.vh)}/hr
📐 Cálculo semana${getFormulaCal(e)}
Valor hora extra (1.5x)$${fm(Math.round(e.vh*1.5))}/hr
Cobra extras${e.cE?'✓ Sí':'No'}
Horarios de la semana
${cards}
${e.cE&&diasExtra?`
Días con horas extra
${diasExtra}
`:''}
Desglose de liquidación
Total horas${fh(e.th)||'—'}
Horas normales${fh(hsNormP)||'—'}
Pago horas normales$${fm(pagoNormP)}
${hsExtraP>0?`
Hs extra (${fh(hsExtraP)} × $${fm(Math.round(e.vh*1.5))})$${fm(pagoExtraP)}
`:''}
Total bruto$${fm(e.bruto)}
Redondeado al millar$${fm(e.red)}
${e.adel>0?`
Adelanto- $${fm(e.adel)}
`:''}
Neto a cobrar$${fm(e.neto)}
${(e.infractions&&e.infractions.filter(i=>i.tipo!=='manual').length)?`
⚠️ Infracciones:
${e.infractions.filter(i=>i.tipo!=='manual').map(i=>'• '+i.msg).join('
')}
`:''}`; document.getElementById('panel').classList.add('open');document.getElementById('ov').classList.add('open'); } function cPanel(){document.getElementById('panel').classList.remove('open');document.getElementById('ov').classList.remove('open');} // ── RECIBOS ── function rR(){ if(!LQ)return; const q=document.getElementById('rq').value.toLowerCase(),f=document.getElementById('rf2').value; const arr=LQ.rows.filter(e=>(!q||e.nombre.toLowerCase().includes(q))&&(!f||e.s===f)); document.getElementById('rect').textContent='Recibos — '+LQ.wl; document.getElementById('recs').textContent=arr.length+' recibos'; document.getElementById('rtb').innerHTML=arr.map(e=>``).join(''); } function bRec(e){ const wl=e.wd?`${e.wd[0].substring(0,5)} al ${e.wd[4].substring(0,5)}`:''; const nombre=e.nombre||e.n||''; const hsXR=e.hx||0; // Sin extras: todas las horas a tarifa normal const hsNR=e.cE?Math.min(e.th||0,HS_MIN):(e.th||0); const pNR=e.eF?0:Math.round(toHHMM(hsNR)*(e.vh||0)); const pXR=e.eF?0:Math.round(toHHMM(hsXR)*(e.vh||0)*1.5); const adelanto=e.adel||e.adelanto||0; const obsHtml=(e.infractions&&e.infractions.filter(i=>i.tipo!=='manual').length)? `
${e.infractions.filter(i=>i.tipo!=='manual').map(i=>'• '+i.msg).join(' ')}
`:''; return`
RECIBO DE PAGO
Semana ${wl} · ${new Date().toLocaleDateString('es-AR')}
${nombre}
${e.s||''}
Horas
Trabajadas:${fh(e.th)||'—'} hs
Normales:${fh(hsNR)||'—'} hs × $${fm(e.vh||0)}
Extra (1.5x):${hsXR>0?fh(hsXR)+' hs':'— hs'}
Pago normal:$${fm(pNR)}
${hsXR>0?`
Pago extra:$${fm(pXR)}
`:''} ${obsHtml}
Liquidación
Bruto:$${fm(e.red)}
${adelanto>0?`
Adelanto:- $${fm(adelanto)}
`:''}
NETO:$${fm(e.neto)}
Recibí conforme la suma indicada.
FIRMA
ACLARACIÓN
DNI
FECHA
`; } function vRec(nombre){ const e=LQ.rows.find(r=>r.nombre===nombre);if(!e)return; document.getElementById('rc').innerHTML=bRec(e); document.getElementById('rw').classList.add('open'); } function cRec(){document.getElementById('rw').classList.remove('open');document.getElementById('rc').innerHTML='';} function _buildRecibosHTML(arr){ const sheets=[]; for(let i=0;i${g.map(e=>bRec(e)).join('
')}`); } return sheets.join(''); } function verRecibos(){ if(!LQ){toast('Cargá una liquidación primero','err');return;} const sector=document.getElementById('exp-sector')?document.getElementById('exp-sector').value:''; const arr=LQ.rows.filter(e=>!sector||e.s===sector); document.getElementById('rc').innerHTML=_buildRecibosHTML(arr); document.getElementById('rw').classList.add('open'); } function pAll(){ if(!LQ){toast('Cargá una liquidación primero','err');return;} const sector=document.getElementById('exp-sector')?document.getElementById('exp-sector').value:''; const arr=LQ.rows.filter(e=>!sector||e.s===sector); document.getElementById('rc').innerHTML=_buildRecibosHTML(arr); document.getElementById('rw').classList.add('open'); setTimeout(()=>window.print(),400); } // ── HISTORIAL ── function rH(){ document.getElementById('hsub').textContent=H.length+' semanas registradas'; if(H.length>=2){document.getElementById('bcmp').style.cssText='';document.getElementById('bacum').style.cssText='';} else if(H.length===1){document.getElementById('bacum').style.cssText='';} const hl=document.getElementById('hlist'); if(!H.length){hl.innerHTML='
📋 Todavía no hay semanas guardadas.

Procesá una liquidación y hacé clic en "Confirmar semana".
';return;} document.getElementById('ca').innerHTML=H.map((h,i)=>``).join(''); document.getElementById('cb').innerHTML=H.map((h,i)=>``).join(''); if(H.length>1)document.getElementById('cb').selectedIndex=1; hl.innerHTML=H.map((h,i)=>`
📅 Semana ${h.sem}
Confirmada ${h.fecha} · ${h.cnt} empleados
$${fm(h.tn)}
Empleados
${h.cnt}
Total horas
${fh(h.tth)||'—'}
Total neto
$${fm(h.tn)}
Email
Sin resultados
${fi+1} ${idBadge} ${p.n} ${p.s} ${val}
${inpBlock}
SIN DATOS
${vieTag}
${inpBlock}
${fh(d.e)||'--:--'}${fh(d.s)||'--:--'}${autoTag}
${label}
${e.hx>0?fh(e.hx):'—'}${e.hx>0?'$'+fm(pagoExtra):'—'}$${fm(e.vh)}/h
${e.nombre}${e.noCobra?'':e.desestimar?'':''} ${e.s}${fh(e.th)||'—'}
$${fm(e.bruto)}
↑ $${fm(e.red)}
${fm(e.neto)}
${getFormulaCal(e)}
${infBadge} ${e.noCobra ? `⏸ No cobra
` : e.desestimar ? `⊘ Desest.
` : `` }
TOTAL $${fm(totB)} ${totA>0?'-$'+fm(totA):'—'} $${fm(totN)}
${e.nombre}${e.s} ${fh(e.th)||'—'} hs$${fm(e.red)} ${e.adel>0?`$${fm(e.adel)}`:'—'} $${fm(e.neto)}
${h.rows.map(e=>` ${(e.dd&&e.dd.length>=5)?e.dd.map(d=>``).join(''):''} `).join('')}
EmpleadoSectorLunMarMiéJueVieTotal hsBrutoAdelantoNetoEst.
${e.n} ${e.s||'—'}${d.h?fh(d.h):''}${fh(e.th)||'—'} ${fm(e.red)}${e.adel>0?`${fm(e.adel)}`:'—'} ${fm(e.neto)} ${e.obs&&e.obs.length?``:`OK`}