Admin
Conectando...
`; window._init_m4 = async function() { // Cargar empresas const { data: emps } = await db.from('empresas').select('*').eq('activa', true).order('nombre'); window._m4 = { empresas: emps || [], currentEmpresa: null, chart: null }; const sel = document.getElementById('m4-empsel'); if (!emps || emps.length === 0) { sel.innerHTML = ''; document.getElementById('m4-content').innerHTML = '
Añade empresas desde M1 o M3 para ver el portal.
'; return; } sel.innerHTML = emps.map(e => ``).join(''); // Cargar la primera empresa por defecto await m4SelectEmpresa(emps[0].id); }; async function m4SelectEmpresa(empresaId) { if (!empresaId) return; const emp = window._m4.empresas.find(e => e.id === empresaId); if (!emp) return; window._m4.currentEmpresa = emp; // Actualizar header const ini = emp.nombre.split(' ').slice(0,2).map(w=>w[0]).join('').toUpperCase(); document.getElementById('m4-cbadge').textContent = ini; document.getElementById('m4-cname').textContent = emp.nombre; const paqueteLbl = {trimestral:'Trimestral',semestral:'Semestral',anual:'Anual'}[emp.paquete]||'—'; document.getElementById('m4-cplan').textContent = `Paquete ${paqueteLbl} · ${emp.sesiones_semana||0} sesiones/semana`; document.getElementById('m4-content').innerHTML = '
Cargando informe...
'; // Cargar datos en paralelo const hoy = new Date(); const primerDiaMes = new Date(hoy.getFullYear(), hoy.getMonth(), 1).toISOString().split('T')[0]; const ultimoDiaMes = new Date(hoy.getFullYear(), hoy.getMonth()+1, 0).toISOString().split('T')[0]; const [sesRes, trabRes, asistRes, histRes] = await Promise.all([ db.from('sesiones').select('*').eq('empresa_id', empresaId).order('fecha', {ascending:false}), db.from('trabajadores').select('*').eq('empresa_id', empresaId).eq('activo', true).order('nombre'), db.from('asistencia').select('*, sesiones(fecha,tipo,empresa_id), trabajadores(nombre)').eq('asistio', true), db.from('historial_empresa').select('*').eq('empresa_id', empresaId).order('created_at', {ascending:false}).limit(10) ]); const sesiones = sesRes.data || []; const trabajadores = trabRes.data || []; const asistencia = (asistRes.data || []).filter(a => a.sesiones?.empresa_id === empresaId); window._m4._historial = histRes.data || []; // Métricas del mes actual const sesionesMes = sesiones.filter(s => s.fecha >= primerDiaMes && s.fecha <= ultimoDiaMes && s.estado === 'realizada'); const sesionesTotal = sesiones.filter(s => s.estado === 'realizada').length; const sesionesProg = Math.round((emp.sesiones_semana || 2) * 4.33); // Asistencia media por sesión const asistPorSesion = {}; asistencia.forEach(a => { if (!asistPorSesion[a.sesion_id]) asistPorSesion[a.sesion_id] = 0; asistPorSesion[a.sesion_id]++; }); const asistVals = Object.values(asistPorSesion); const asistMedia = asistVals.length > 0 ? Math.round(asistVals.reduce((s,v)=>s+v,0)/asistVals.length) : 0; const asistPct = trabajadores.length > 0 && asistMedia > 0 ? Math.round(asistMedia/trabajadores.length*100) : 0; // Asistencia por trabajador const asistPorTrab = {}; asistencia.forEach(a => { const tid = a.trabajador_id; if (!asistPorTrab[tid]) asistPorTrab[tid] = 0; asistPorTrab[tid]++; }); const trabConAsist = trabajadores.map(t => ({ ...t, sesiones: asistPorTrab[t.id] || 0, pct: sesionesTotal > 0 ? Math.min(100, Math.round((asistPorTrab[t.id]||0)/sesionesTotal*100)) : 0 })).sort((a,b) => b.pct - a.pct); // Sesiones por tipo const porTipo = {}; sesiones.filter(s=>s.estado==='realizada').forEach(s => { porTipo[s.tipo] = (porTipo[s.tipo]||0) + 1; }); // Datos para gráfica — últimos 6 meses const meses = []; const sesionesPorMes = []; const asistPorMes = []; for (let i = 5; i >= 0; i--) { const d = new Date(hoy.getFullYear(), hoy.getMonth()-i, 1); const inicio = d.toISOString().split('T')[0]; const fin = new Date(d.getFullYear(), d.getMonth()+1, 0).toISOString().split('T')[0]; meses.push(d.toLocaleDateString('es-ES',{month:'short'})); const sesMes = sesiones.filter(s => s.fecha >= inicio && s.fecha <= fin && s.estado === 'realizada'); sesionesPorMes.push(sesMes.length); // Asistencia % ese mes const asistMes = asistencia.filter(a => a.sesiones?.fecha >= inicio && a.sesiones?.fecha <= fin); const asistMesPorSes = {}; asistMes.forEach(a => { asistMesPorSes[a.sesion_id] = (asistMesPorSes[a.sesion_id]||0)+1; }); const vals = Object.values(asistMesPorSes); const pctMes = vals.length > 0 && trabajadores.length > 0 ? Math.round((vals.reduce((s,v)=>s+v,0)/vals.length)/trabajadores.length*100) : 0; asistPorMes.push(pctMes); } document.getElementById('m4-content').innerHTML = `
Resumen del mes
Sesiones realizadas
${sesionesMes.length}
de ~${sesionesProg} programadas
Empleados activos
${trabajadores.length}
registrados en plataforma
Asistencia media
${asistPct}%
${asistMedia} personas/sesión
Total sesiones
${sesionesTotal}
desde el inicio
Evolución mensual — últimos 6 meses
Asistencia % Sesiones
${sesiones.length > 0 ? `
Asistencia por tipo de sesión
` : ''}
Evolución mensual — últimos 6 meses
Asistencia % Sesiones
Próximas sesiones
${(() => { const hoyStr = new Date().toISOString().split('T')[0]; const proxSes = sesiones.filter(s=>s.fecha>=hoyStr&&s.empresa_id===empresaId).sort((a,b)=>a.fecha.localeCompare(b.fecha)||a.hora.localeCompare(b.hora)).slice(0,4); if (proxSes.length===0) return `
Sin sesiones programadas próximamente.
`; return proxSes.map(s=>{ const f=new Date(s.fecha+'T12:00:00').toLocaleDateString('es-ES',{weekday:'short',day:'numeric',month:'short'}); const tipoLbl=s.tipo?s.tipo.charAt(0).toUpperCase()+s.tipo.slice(1):'Sesión'; return `
${f} · ${s.hora?.slice(0,5)||''}
${tipoLbl} · ${s.duracion_min||60}min
Programada
`; }).join(''); })()}
Asistencia por empleado
${trabConAsist.length === 0 ? `
Sin trabajadores registrados. Añádelos desde M1.
` : trabConAsist.slice(0,8).map(t => `
${t.nombre}
${t.email||'Sin email'}
${t.pct}%
`).join('') }
Sesiones por tipo
${Object.keys(porTipo).length === 0 ? `
Sin sesiones registradas todavía.
` : Object.entries(porTipo).sort((a,b)=>b[1]-a[1]).map(([tipo, n]) => { const tipoLbl = tipo.charAt(0).toUpperCase()+tipo.slice(1); return `
${tipoLbl}
${n} sesion${n!==1?'es':''}
Realizada${n!==1?'s':''}
`; }).join('') } ${sesionesTotal > 0 ? `
Nota: ${sesionesTotal} sesiones realizadas desde el inicio del contrato. ${asistPct >= 70 ? 'Excelente nivel de participación.' : 'Hay margen de mejora en la asistencia.'}
` : ''}
Mensajes a FitCorp
${(window._m4._historial||[]).length===0 ? `
Sin mensajes todavía.
` : (window._m4._historial||[]).slice(0,5).map(h=>`
${h.tipo==='nota'?'Mensaje':'Comunicación'} ${new Date(h.created_at).toLocaleDateString('es-ES',{day:'numeric',month:'short'})}
${h.texto}
`).join('')}
`; // Gráfica por tipo (si hay datos) if (Object.keys(porTipo).length > 0 && typeof Chart !== 'undefined') { setTimeout(() => { const ctxTipo = document.getElementById('m4-chart-tipo'); if (ctxTipo) { new Chart(ctxTipo.getContext('2d'), { type: 'bar', data: { labels: Object.keys(porTipo).map(t=>t.charAt(0).toUpperCase()+t.slice(1)), datasets: [{ data: Object.values(porTipo), backgroundColor: ['rgba(29,158,117,0.6)','rgba(55,138,221,0.6)','rgba(239,159,39,0.6)','rgba(208,160,255,0.6)','rgba(255,184,90,0.6)','rgba(93,212,192,0.6)'], borderRadius: 4 }] }, options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{display:false}}, scales:{ x:{grid:{display:false},ticks:{color:'#9B9A94',font:{size:11}}}, y:{grid:{color:'rgba(255,255,255,0.05)'},ticks:{color:'#9B9A94',font:{size:11},stepSize:1}} } } }); } }, 100); } }; async function m4EnviarMensaje(empresaId) { const input = document.getElementById('m4-msg-input'); const texto = input?.value?.trim(); if (!texto) return; const { error } = await db.from('historial_empresa').insert({ empresa_id: empresaId, tipo: 'nota', texto }); if (!error) { input.value = ''; // Recargar historial const { data } = await db.from('historial_empresa').select('*').eq('empresa_id', empresaId).order('created_at', {ascending:false}).limit(10); window._m4._historial = data || []; const msgsEl = document.getElementById('m4-msgs'); if (msgsEl) { msgsEl.innerHTML = (data||[]).slice(0,5).map(h=>`
Mensaje ${new Date(h.created_at).toLocaleDateString('es-ES',{day:'numeric',month:'short'})}
${h.texto}
`).join(''); } } } async function m4DescargarPDF() { const emp = window._m4?.currentEmpresa; if (!emp) return; const mes = new Date().toLocaleDateString('es-ES',{month:'long',year:'numeric'}); // Generar informe con IA y abrir ventana de impresión const btn = document.querySelector('[onclick="m4DescargarPDF()"]'); if (btn) { btn.textContent='Generando...'; btn.style.pointerEvents='none'; } try { const res = await fetch('https://api.anthropic.com/v1/messages', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ model:'claude-sonnet-4-20250514', max_tokens:800, messages:[{role:'user', content:`Genera un informe mensual de bienestar corporativo para RRHH de la empresa "${emp.nombre}" correspondiente a ${mes}. Datos: paquete ${emp.paquete}, ${emp.sesiones_semana} sesiones/semana, contacto RRHH: ${emp.rrhh_nombre||'—'}. El informe debe incluir: resumen ejecutivo (2-3 frases), puntos destacados del mes, recomendaciones para el próximo mes. Tono profesional y positivo. Formato limpio sin markdown excesivo, pensado para imprimir.`}] }) }); const d = await res.json(); const contenido = d.content?.[0]?.text || 'Error al generar el informe.'; const ventana = window.open('','_blank'); ventana.document.write(`Informe ${emp.nombre} — ${mes}

Informe de Bienestar Corporativo

${emp.nombre} · ${mes}
Elaborado por FitCorp · fitcorp.es
${contenido.replace(/\n/g,'
')}

`); ventana.document.close(); } catch(e) { alert('Error al generar el informe'); } if (btn) { btn.innerHTML=' Informe PDF'; btn.style.pointerEvents=''; } } const ctx = document.getElementById('m4-chart'); if (ctx && typeof Chart !== 'undefined') { window._m4.chart = new Chart(ctx.getContext('2d'), { data: { labels: meses, datasets: [ { type:'line', label:'Asistencia %', data:asistPorMes, borderColor:'var(--blue)', backgroundColor:'rgba(55,138,221,0.08)', fill:true, tension:0.4, pointBackgroundColor:'var(--blue)', pointRadius:4, yAxisID:'y' }, { type:'bar', label:'Sesiones', data:sesionesPorMes, backgroundColor:'rgba(29,158,117,0.5)', borderRadius:4, yAxisID:'y2' } ] }, options: { responsive:true, maintainAspectRatio:false, plugins:{ legend:{ display:false } }, scales: { x:{ grid:{ display:false }, ticks:{ font:{size:12}, color:'#9B9A94' } }, y:{ position:'left', min:0, max:100, ticks:{ font:{size:11}, color:'#378ADD', callback:v=>v+'%' }, grid:{ color:'rgba(255,255,255,0.05)' } }, y2:{ position:'right', min:0, ticks:{ font:{size:11}, color:'#1D9E75', stepSize:1 }, grid:{ display:false } } } } }); } }; // ── MÓDULO M7: GASTOS E INGRESOS ── MODULES.m7 = () => `
Gastos e ingresos
Finanzas internas de FitCorp
Cargando...
Evolución mensual — últimos 6 meses
Nuevo gasto
Cargando gastos...
Ingresos automáticos — contratos activos
Cargando...
Ingresos manuales (subvenciones, otros)
Cargando...
`; window._init_m7 = async function() { window._m7 = { chart: null }; document.getElementById('m7-g-fecha').value = new Date().toISOString().split('T')[0]; document.getElementById('m7-i-fecha').value = new Date().toISOString().split('T')[0]; await m7Load(); }; async function m7Load() { const [gastosRes, extrasRes, empRes] = await Promise.all([ db.from('gastos').select('*').order('fecha', {ascending:false}), db.from('ingresos_extra').select('*').order('fecha', {ascending:false}), db.from('empresas').select('nombre,importe_total,paquete,estado_pago,fecha_inicio').eq('activa',true) ]); const gastos = gastosRes.data||[]; const extras = extrasRes.data||[]; const empresas = empRes.data||[]; // Ingresos automáticos = suma de contratos const ingresosAuto = empresas.reduce((s,e)=>s+(e.importe_total||0),0); const ingresosExtra = extras.reduce((s,i)=>s+(i.importe||0),0); const totalIngresos = ingresosAuto + ingresosExtra; const totalGastos = gastos.reduce((s,g)=>s+(g.importe||0),0); const margen = totalIngresos - totalGastos; // Métricas const metricsEl = document.getElementById('m7-metrics'); if (metricsEl) metricsEl.innerHTML = `
Ingresos totales
${totalIngresos.toLocaleString('es-ES')}€
Gastos totales
${totalGastos.toLocaleString('es-ES')}€
Margen neto
${margen>=0?'+':''}${margen.toLocaleString('es-ES')}€
Margen %
${totalIngresos>0?Math.round(margen/totalIngresos*100):0}%
`; // Gráfica mensual m7RenderChart(gastos, extras, ingresosAuto); // Lista gastos m7RenderGastos(gastos); // Ingresos automáticos const autoEl = document.getElementById('m7-ingresos-auto'); if (autoEl) { if (empresas.length===0) { autoEl.innerHTML='
Sin empresas activas — los ingresos de contratos aparecen aquí automáticamente.
'; } else { autoEl.innerHTML = `
EmpresaPaqueteImporte
${empresas.map(e=>{ const col={pagado:'var(--green)',pendiente:'var(--amber)',vencido:'var(--red)'}[e.estado_pago]||'var(--text2)'; return `
${e.nombre} ${e.paquete?e.paquete.charAt(0).toUpperCase()+e.paquete.slice(1):'—'} ${(e.importe_total||0).toLocaleString('es-ES')}€
`; }).join('')}
Total contratos: ${ingresosAuto.toLocaleString('es-ES')}€
`; } } // Ingresos extra m7RenderExtras(extras); } function m7RenderGastos(gastos) { const el = document.getElementById('m7-gastos-list'); if (!el) return; if (gastos.length===0) { el.innerHTML='
Sin gastos registrados todavía.
'; return; } const catLabel = {material_deportivo:'Material',desplazamientos:'Desplazamientos',software:'Software',formacion:'Formación',gestoria:'Gestoría',marketing:'Marketing',administrativo:'Administrativo',otro:'Otro'}; // Agrupar por mes const porMes = {}; gastos.forEach(g=>{ const mes=g.fecha.slice(0,7); if(!porMes[mes])porMes[mes]=[]; porMes[mes].push(g); }); el.innerHTML = Object.entries(porMes).map(([mes,items])=>{ const total=items.reduce((s,g)=>s+(g.importe||0),0); const fechaMes=new Date(mes+'-01').toLocaleDateString('es-ES',{month:'long',year:'numeric'}); return `
${fechaMes}${total.toLocaleString('es-ES')}€
${items.map(g=>`
${new Date(g.fecha+'T12:00').toLocaleDateString('es-ES',{day:'numeric',month:'short'})} ${catLabel[g.categoria]||g.categoria} ${g.descripcion} ${(g.importe||0).toLocaleString('es-ES')}€
`).join('')}`; }).join(''); } function m7RenderExtras(extras) { const el = document.getElementById('m7-ingresos-extra-list'); if (!el) return; if (extras.length===0) { el.innerHTML='
Sin ingresos manuales registrados.
'; return; } const catLabel={subvencion:'Subvención',formacion:'Formación',consultoria:'Consultoría',otro:'Otro'}; el.innerHTML = extras.map(i=>`
${new Date(i.fecha+'T12:00').toLocaleDateString('es-ES',{day:'numeric',month:'short'})} ${catLabel[i.categoria]||'Otro'} ${i.concepto} ${(i.importe||0).toLocaleString('es-ES')}€
`).join(''); } function m7RenderChart(gastos, extras, ingresosAuto) { const meses=[]; const gastosM=[]; const ingresosM=[]; const hoy=new Date(); for(let i=5;i>=0;i--){ const d=new Date(hoy.getFullYear(),hoy.getMonth()-i,1); const key=d.toISOString().slice(0,7); meses.push(d.toLocaleDateString('es-ES',{month:'short'})); gastosM.push(gastos.filter(g=>g.fecha.startsWith(key)).reduce((s,g)=>s+(g.importe||0),0)); // Ingresos del mes: proporcional de contratos + extras del mes const extMes=extras.filter(e=>e.fecha.startsWith(key)).reduce((s,e)=>s+(e.importe||0),0); ingresosM.push(Math.round(ingresosAuto/6)+extMes); // distribuir ingresos de contratos equitativamente } if(window._m7.chart){window._m7.chart.destroy();window._m7.chart=null;} const ctx=document.getElementById('m7-chart'); if(!ctx||typeof Chart==='undefined')return; window._m7.chart=new Chart(ctx.getContext('2d'),{ data:{ labels:meses, datasets:[ {type:'bar',label:'Ingresos',data:ingresosM,backgroundColor:'rgba(29,158,117,0.5)',borderRadius:4,yAxisID:'y'}, {type:'bar',label:'Gastos',data:gastosM,backgroundColor:'rgba(220,76,76,0.4)',borderRadius:4,yAxisID:'y'}, {type:'line',label:'Margen',data:ingresosM.map((v,i)=>v-gastosM[i]),borderColor:'var(--accent)',backgroundColor:'transparent',pointBackgroundColor:'var(--accent)',pointRadius:4,tension:0.4,yAxisID:'y'} ] }, options:{ responsive:true,maintainAspectRatio:false, plugins:{legend:{display:true,labels:{color:'#9B9A94',font:{size:11}}}}, scales:{ x:{grid:{display:false},ticks:{color:'#9B9A94',font:{size:11}}}, y:{grid:{color:'rgba(255,255,255,0.05)'},ticks:{color:'#9B9A94',font:{size:11},callback:v=>v+'€'}} } } }); } function m7Tab(tab,btn){ document.querySelectorAll('.m7-tab').forEach(t=>t.classList.remove('active')); document.querySelectorAll('.m7-page').forEach(p=>p.classList.remove('active')); btn.classList.add('active'); document.getElementById('m7-page-'+tab).classList.add('active'); } async function m7AddGasto(){ const fecha=document.getElementById('m7-g-fecha').value; const cat=document.getElementById('m7-g-cat').value; const desc=document.getElementById('m7-g-desc').value.trim(); const importe=parseFloat(document.getElementById('m7-g-importe').value); if(!fecha||!desc||!importe){m7Toast('Completa todos los campos','err');return;} const{error}=await db.from('gastos').insert({fecha,categoria:cat,descripcion:desc,importe,pagado:true}); if(!error){ document.getElementById('m7-g-desc').value=''; document.getElementById('m7-g-importe').value=''; m7Toast('✓ Gasto añadido','ok'); await m7Load(); } else m7Toast('Error: '+error.message,'err'); } async function m7AddIngreso(){ const fecha=document.getElementById('m7-i-fecha').value; const cat=document.getElementById('m7-i-cat').value; const concepto=document.getElementById('m7-i-concepto').value.trim(); const importe=parseFloat(document.getElementById('m7-i-importe').value); if(!fecha||!concepto||!importe){m7Toast('Completa todos los campos','err');return;} const{error}=await db.from('ingresos_extra').insert({fecha,categoria:cat,concepto,importe}); if(!error){ document.getElementById('m7-i-concepto').value=''; document.getElementById('m7-i-importe').value=''; m7Toast('✓ Ingreso añadido','ok'); await m7Load(); } else m7Toast('Error: '+error.message,'err'); } async function m7DelGasto(id){ await db.from('gastos').delete().eq('id',id); await m7Load(); } async function m7DelExtra(id){ await db.from('ingresos_extra').delete().eq('id',id); await m7Load(); } function m7Toast(msg,type){ const t=document.getElementById('m7-toast'); if(!t)return; t.textContent=msg;t.className='m7-toast '+type;t.style.display='block'; setTimeout(()=>{t.style.display='none';},3500); } // ── MÓDULO M8: DOCUMENTACIÓN ── MODULES.m8 = () => `
Documentación
Archivos internos y por empresa
Carpetas
📁General FitCorp
Cargando...
General FitCorp
Selecciona una carpeta
`; window._init_m8 = async function() { window._m8 = { currentEmpresaId: null, currentLabel: 'General FitCorp', pendingFiles: null }; // Cargar carpetas de empresas const {data:emps} = await db.from('empresas').select('id,nombre').eq('activa',true).order('nombre'); const foldersEl = document.getElementById('m8-empresa-folders'); if (foldersEl) { foldersEl.innerHTML = (emps||[]).map(e=>{ const ini=e.nombre.split(' ').slice(0,2).map(w=>w[0]).join('').toUpperCase(); return `
🏢 ${e.nombre}
`; }).join('') || '
Sin empresas
'; } // Cargar docs generales por defecto await m8LoadDocs(null); // Drag & drop const main = document.querySelector('.m8-main'); if (main) { main.addEventListener('dragover', e=>{ e.preventDefault(); main.classList.add('drag'); }); main.addEventListener('dragleave', ()=>main.classList.remove('drag')); main.addEventListener('drop', e=>{ e.preventDefault(); main.classList.remove('drag'); m8HandleFiles(e.dataTransfer.files); }); } }; async function m8SelectFolder(empresaId, label, el) { document.querySelectorAll('.m8-folder').forEach(f=>f.classList.remove('active')); el.classList.add('active'); window._m8.currentEmpresaId = empresaId; window._m8.currentLabel = label; document.getElementById('m8-folder-label').textContent = label; await m8LoadDocs(empresaId); } async function m8LoadDocs(empresaId) { const el = document.getElementById('m8-doclist'); if (!el) return; el.innerHTML = '
Cargando...
'; let query = db.from('documentos').select('*').order('created_at', {ascending:false}); if (empresaId) query = query.eq('empresa_id', empresaId); else query = query.is('empresa_id', null); const {data:docs} = await query; if (!docs||docs.length===0) { el.innerHTML = `
📂
No hay archivos en esta carpeta.
Haz clic en "+ Subir archivo" para añadir.
`; return; } const iconos = {'application/pdf':'📄','image/jpeg':'🖼️','image/png':'🖼️','application/vnd.openxmlformats-officedocument.wordprocessingml.document':'📝','application/msword':'📝','application/vnd.ms-excel':'📊','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':'📊'}; const catLabel={contrato:'Contrato',propuesta:'Propuesta',informe:'Informe',factura:'Factura',general:'General',otro:'Otro'}; el.innerHTML = `
${docs.map(d=>{ const ico = iconos[d.tipo_mime]||'📎'; const tamaño = d.tamaño_bytes ? (d.tamaño_bytes>1024*1024?(d.tamaño_bytes/1024/1024).toFixed(1)+'MB':(d.tamaño_bytes/1024).toFixed(0)+'KB') : '—'; const fecha = new Date(d.created_at).toLocaleDateString('es-ES',{day:'numeric',month:'short',year:'numeric'}); return `
${ico}
${d.nombre}
${catLabel[d.categoria]||'—'} · ${tamaño} · ${fecha} · ${d.subido_por||'FitCorp'}
${d.visible_rrhh?'Visible RRHH':'Privado'}
`; }).join('')}
`; } function m8HandleFiles(files) { if (!files||files.length===0) return; window._m8.pendingFiles = files; const panel = document.getElementById('m8-upload-panel'); const pending = document.getElementById('m8-files-pending'); if (panel) panel.style.display='block'; if (pending) pending.textContent = `${files.length} archivo${files.length!==1?'s':''} listo${files.length!==1?'s':''} para subir: ${Array.from(files).map(f=>f.name).join(', ')}`; } function m8CancelUpload() { window._m8.pendingFiles = null; const panel = document.getElementById('m8-upload-panel'); if (panel) panel.style.display='none'; document.getElementById('m8-fileinput').value=''; } async function m8Upload() { const files = window._m8.pendingFiles; if (!files||files.length===0) return; const categoria = document.getElementById('m8-categoria').value; const visibleRrhh = document.getElementById('m8-visible-rrhh').checked; const email = window.STATE?.currentUser?.email||'jaime@fitcorp.es'; const progress = document.getElementById('m8-progress'); const fill = document.getElementById('m8-progressfill'); if (progress) progress.style.display='block'; let subidos=0; for (const file of Array.from(files)) { const ext = file.name.split('.').pop(); const path = `${window._m8.currentEmpresaId||'general'}/${Date.now()}_${file.name.replace(/[^a-zA-Z0-9._-]/g,'_')}`; // Subir a Supabase Storage const {error:upErr} = await db.storage.from('documentos').upload(path, file, {contentType:file.type}); if (!upErr) { // Guardar metadatos en tabla documentos await db.from('documentos').insert({ nombre: file.name, storage_path: path, tipo_mime: file.type, tamaño_bytes: file.size, empresa_id: window._m8.currentEmpresaId||null, categoria, visible_rrhh: visibleRrhh, subido_por: email }); } subidos++; if (fill) fill.style.width=Math.round(subidos/files.length*100)+'%'; } m8Toast(`✓ ${subidos} archivo${subidos!==1?'s':''} subido${subidos!==1?'s':''}`, 'ok'); m8CancelUpload(); await m8LoadDocs(window._m8.currentEmpresaId); } async function m8Download(path, nombre) { const {data, error} = await db.storage.from('documentos').download(path); if (error||!data) { m8Toast('Error al descargar el archivo','err'); return; } const url = URL.createObjectURL(data); const a = document.createElement('a'); a.href=url; a.download=nombre; a.click(); URL.revokeObjectURL(url); } async function m8Delete(id, path) { await db.storage.from('documentos').remove([path]); await db.from('documentos').delete().eq('id',id); m8Toast('Archivo eliminado','ok'); await m8LoadDocs(window._m8.currentEmpresaId); } function m8Toast(msg,type){ const t=document.getElementById('m8-toast'); if(!t)return; t.textContent=msg;t.className='m8-toast '+type;t.style.display='block'; setTimeout(()=>{t.style.display='none';},3500); } // ── INIT ── document.addEventListener('DOMContentLoaded', async () => { document.getElementById('screen-login').style.display = 'flex'; document.getElementById('module-container').style.display = 'none'; testConnection(); // Comprobar si ya hay sesión activa (token guardado en el navegador) const { data: { session } } = await db.auth.getSession(); if (session?.user) { // Hay sesión activa — buscar rol y entrar directamente const { data: userData } = await db.from('usuarios').select('*').eq('email', session.user.email).single(); if (userData) { const roleLabels = { admin:'Administrador', trainer:'Entrenador', company:'RRHH Empresa', worker:'Empleado' }; const user = { id: userData.id, auth_id: session.user.id, email: userData.email, name: userData.nombre, avatar: userData.nombre.split(' ').slice(0,2).map(w=>w[0]).join('').toUpperCase(), role: userData.rol, roleLabel: roleLabels[userData.rol] || userData.rol, empresa_id: userData.empresa_id || null }; enterApp(user); return; } } // Sin sesión — mostrar login document.getElementById('screen-login').style.display = 'flex'; }); window.fitcorpNavigate = loadModule; window.sendPrompt = (msg) => { if (msg.includes('sesión') || msg.includes('registrar')) loadModule('m5'); else if (msg.includes('empresa')) loadModule('m1'); };