File size: 24,995 Bytes
789633a
ae1eada
789633a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae1eada
 
 
 
 
 
 
 
 
 
 
 
 
789633a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae1eada
789633a
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
export const APViews = {
  names:["Dashboard","Finanzas","Suministro","Logística IA","Gestión de Stock","Ventas","CRM Predictivo","Precios Dinámicos","RRHH","Talento IA","Planificación","Producción","Reportes Avanzados","Agentes IA","Cumplimiento","Nómina Colombia","Subagentes jurídicos","Costes de mano de obra","Reclutamiento y cambio","Currícula · equipo","Registro de actividad","Intervenciones de agentes"],
  current:0,
  safe:v=>AP.esc(v??''),
  table(headers,rows){return '<div class="ap-table-wrap"><table class="ap-table"><thead><tr>'+headers.map(x=>'<th>'+this.safe(x)+'</th>').join('')+'</tr></thead><tbody>'+rows.map(row=>'<tr>'+row.map(x=>'<td>'+this.safe(x)+'</td>').join('')+'</tr>').join('')+'</tbody></table></div>';},
  tiles(items){return '<div class="ap-grid">'+items.map(([label,value])=>'<div class="ap-tile"><small>'+this.safe(label)+'</small><b>'+this.safe(value)+'</b></div>').join('')+'</div>';},
  ask(label,text){const a=String(label).split(' · ')[0];const agent=['ALPHA','BETA','GAMMA'].includes(a)?a:'ALPHA';return '<button class="ap-agent-btn" data-prompt="'+this.safe(text)+'" data-agent="'+agent+'">'+this.safe(label)+'</button>';},
  section(title,lead,body){return '<h2>'+this.safe(title)+'</h2><p class="ap-view-lead">'+this.safe(lead)+'</p>'+body;},
  activity:{q:'',actor:'',limit:200},
  activityView(){
    const actors=AP.query('SELECT DISTINCT actor FROM audit_log ORDER BY actor'),st=this.activity;
    const total=AP.one('SELECT count(*) AS n FROM audit_log').n;
    const last=AP.one('SELECT occurred_at FROM audit_log ORDER BY occurred_at DESC LIMIT 1');
    const filters='<div class="ap-activity-form">'+
      '<label class="ap-activity-search">Buscar<input type="search" id="ap-activity-q" maxlength="80" placeholder="Acción, entidad o detalle" value="'+this.safe(st.q)+'"></label>'+
      '<label>Agente<select id="ap-activity-actor"><option value="">Todos</option>'+actors.map(a=>'<option value="'+this.safe(a.actor)+'"'+(st.actor===a.actor?' selected':'')+'>'+this.safe(a.actor)+'</option>').join('')+'</select></label>'+
      '<label>Límite<select id="ap-activity-limit">'+[50,200,500].map(n=>'<option value="'+n+'"'+(st.limit===n?' selected':'')+'>'+n+' eventos</option>').join('')+'</select></label>'+
      '<button type="button" id="ap-activity-clear" class="ap-agent-btn">Limpiar filtros</button>'+
      '</div>';
    return this.section('Registro de actividad','Historial de acciones que esta demo guardó en tu navegador. Solo lectura: no se edita y no se envía a ningún servidor. Estos mismos eventos aparecen resumidos en Reportes Avanzados y Agentes IA.',
      this.tiles([['Eventos registrados',total],['Agentes distintos',actors.length],['Último evento',last?new Date(last.occurred_at).toLocaleString('es-ES',{dateStyle:'short',timeStyle:'short'}):'Sin eventos']])+
      filters+'<div id="ap-activity-results">'+this.activityResults()+'</div>');
  },
  activityResults(){
    const st=this.activity,q=st.q.trim().replace(/[%_\\]/g,''),args=[],where=[];
    if(q){const like='%'+q.toLowerCase()+'%';where.push('(lower(actor) LIKE ? OR lower(action) LIKE ? OR lower(entity_type) LIKE ? OR lower(entity_id) LIKE ? OR lower(details_json) LIKE ?)');args.push(like,like,like,like,like);}
    if(st.actor){where.push('actor=?');args.push(st.actor);}
    const clause=where.length?' WHERE '+where.join(' AND '):'';
    const shown=AP.one('SELECT count(*) AS n FROM audit_log'+clause,args).n;
    const rows=AP.query('SELECT occurred_at,actor,action,entity_type,entity_id,details_json FROM audit_log'+clause+' ORDER BY occurred_at DESC,rowid DESC LIMIT ?',args.concat([st.limit]));
    const table=rows.length?this.table(['Fecha','Agente','Acción','Entidad','Detalle'],rows.map(r=>[
      new Date(r.occurred_at).toLocaleString('es-ES',{dateStyle:'short',timeStyle:'short'}),
      r.actor,r.action,r.entity_type+' · '+r.entity_id,String(r.details_json).slice(0,180)
    ])):'<p class="ap-compliance-note">No hay eventos que cumplan estos filtros.</p>';
    return '<p class="ap-activity-meta">Mostrando '+rows.length+' de '+shown+' eventos que cumplen el filtro, de '+AP.one('SELECT count(*) AS n FROM audit_log').n+' registrados en total.</p>'+table;
  },
  wireActivity(box){
    const st=this.activity,results=box.querySelector('#ap-activity-results');
    if(!results)return;
    const redraw=()=>{results.innerHTML=this.activityResults();};
    const q=box.querySelector('#ap-activity-q');
    if(q)q.oninput=()=>{clearTimeout(this.activityTimer);this.activityTimer=setTimeout(()=>{st.q=q.value;redraw();},220);};
    const a=box.querySelector('#ap-activity-actor');
    if(a)a.onchange=()=>{st.actor=a.value;redraw();};
    const l=box.querySelector('#ap-activity-limit');
    if(l)l.onchange=()=>{st.limit=Number(l.value);redraw();};
    const c=box.querySelector('#ap-activity-clear');
    if(c)c.onclick=()=>{this.activity={q:'',actor:'',limit:200};this.show(20);};
  },
  show(id){id=Number(id);if(!Number.isInteger(id)||id<0||id>=this.names.length)id=0;this.current=id;if(innerWidth<=760)APMenu.close();if(id===0&&typeof APAgentCenter!=='undefined'&&APAgentCenter.selected)APAgentCenter.back();document.querySelectorAll('[data-view]').forEach(b=>{b.classList.toggle('active',Number(b.dataset.view)===id);b.setAttribute('aria-current',Number(b.dataset.view)===id?'page':'false')});document.getElementById('ap-dashboard').hidden=id!==0;const box=document.getElementById('ap-view');box.hidden=id===0;if(id&&!AP.db){box.innerHTML='<p>Cargando datos locales...</p>';return;}if(id)box.innerHTML=this.render(id);const country=box.querySelector('#ap-country');if(country)country.onchange=()=>{const from=localStorage.getItem('ap-country')||'CO';if(country.value!==from){AP.audit('SISTEMA','pais_cambiado','settings','country',{from,to:country.value});}localStorage.setItem('ap-country',country.value);this.show(id)};if(id===20)this.wireActivity(box);if(id===19){const form=box.querySelector('#ap-candidate-form');form.onsubmit=async e=>{e.preventDefault();const f=form.elements,alias=f.alias.value.trim(),role=f.role.value,evidence=f.evidence.value.trim();if(!alias||alias.length>60||!['Operaciones','Relaciones','Exploración'].includes(role)||!evidence||evidence.length>700)return;const check=APCompliance.assessEvidence(evidence);try{const sid=crypto.randomUUID();AP.exec({sql:'INSERT INTO candidate_screenings VALUES(?,?,?,?,?,?,?,?,?)',args:[sid,alias,role,evidence,check.score,check.explanation,check.status,0,new Date().toISOString()],actor:'SISTEMA',action:'perfil_preevaluado',entity:'candidate_screening',id:sid,detail:{alias,role,status:check.status,score:check.score}});await AP.persist();this.show(19)}catch(err){box.querySelector('#ap-candidate-feedback').textContent='No se pudo guardar el perfil local.'}};box.querySelectorAll('[data-approve-candidate]').forEach(b=>b.onclick=async()=>{const id=b.dataset.approveCandidate,row=AP.one('SELECT * FROM candidate_screenings WHERE id=?',[id]);if(!row||row.status!=='review'||row.score<60)return;const ok=await AP.confirmAction('Avanzar perfil al reclutamiento','Revisa la evidencia y decide tú: ¿mover '+row.alias+' al tablero de colaboradores internos? No supone contratación.');if(!ok)return;try{AP.exec({sql:'UPDATE candidate_screenings SET status=? WHERE id=?',args:['approved',id],actor:'SISTEMA',action:'perfil_aprobado',entity:'candidate_screening',id,detail:{alias:row.alias}});AP.exec({sql:'INSERT INTO recruitment_pipeline VALUES(?,?,?,?,?,?,?,?)',args:[crypto.randomUUID(),'Colaboradores internos',row.alias,'Detectado','Perfil preevaluado; pendiente de entrevista y validación humana.','Aprobación humana del avance',row.demo,new Date().toISOString()],audit:false});await AP.persist();this.show(19)}catch(err){box.querySelector('#ap-candidate-feedback').textContent='No se pudo avanzar el perfil.'}})}if(id===18){const form=box.querySelector('#ap-recruit-form');form.onsubmit=async e=>{e.preventDefault();const f=form.elements,kind=f.kind.value,title=f.title.value.trim(),signal=f.signal.value.trim(),note=f.note.value.trim();if(!['Colaboradores internos','Aliados','Productos','Clientes','Mercados'].includes(kind)||!title||title.length>100||note.length>180||signal.length>160)return;try{const rid=crypto.randomUUID();AP.exec({sql:'INSERT INTO recruitment_pipeline VALUES(?,?,?,?,?,?,?,?)',args:[rid,kind,title,'Detectado',note,signal,0,new Date().toISOString()],actor:'SISTEMA',action:'contacto_registrado',entity:'recruitment_pipeline',id:rid,detail:{kind,title}});await AP.persist();this.show(18)}catch(err){box.querySelector('#ap-recruit-feedback').textContent='No se pudo guardar; revisa la información.'}};box.querySelectorAll('[data-recruit-id]').forEach(b=>b.onclick=async()=>{const id=b.dataset.recruitId,to=b.dataset.nextStage;try{const row=AP.one('SELECT stage FROM recruitment_pipeline WHERE id=?',[id]),stages=['Detectado','Exploración','Evaluación','Incorporación'];if(!row||stages.indexOf(to)!==stages.indexOf(row.stage)+1)return;AP.exec({sql:'UPDATE recruitment_pipeline SET stage=? WHERE id=?',args:[to,id],actor:'SISTEMA',action:'etapa_avanzada',entity:'recruitment_pipeline',id,detail:{stage:to,previous:row.stage}});await AP.persist();this.show(18)}catch(err){box.querySelector('#ap-recruit-feedback').textContent='No se pudo cambiar la etapa.'}})}if(id===17){const form=box.querySelector('#ap-cost-form');form.onsubmit=async e=>{e.preventDefault();const f=form.elements,period=f.period.value,structure=f.structure.value,area=f.area.value,amount=Number(f.amount.value),country=APCompliance.country();if(!/^\d{4}-(0[1-9]|1[0-2])$/.test(period)||!['Fabricación','Comercialización','Servicios'].includes(structure)||!['Operativa','Soporte'].includes(area)||!Number.isFinite(amount)||amount<0||amount>1e12||Math.round(amount*100)/100!==amount)return;const cents=Math.round(amount*100),currency=country==='VE'?'VES':'COP';try{const lid=crypto.randomUUID();AP.exec({sql:'INSERT INTO labor_cost_entries VALUES(?,?,?,?,?,?,?,?)',args:[lid,country,period,structure,area,cents,currency,new Date().toISOString()],actor:'SISTEMA',action:'costo_registrado',entity:'labor_cost_entry',id:lid,detail:{country,period,structure,area,cents,currency}});await AP.persist();this.show(17)}catch(err){box.querySelector('#ap-cost-feedback').textContent='No se pudo guardar la entrada; comprueba los datos y vuelve a intentarlo.'}}}if(id===15){const f=box.querySelector('#ap-payroll-form');if(f)f.onsubmit=e=>{e.preventDefault();const salary=Number(f.elements.salary.value),days=Number(f.elements.days.value);if(!Number.isFinite(salary)||salary<0||!Number.isInteger(days)||days<0||days>30)return;const ordinary=Math.round(salary*days/30);box.querySelector('#ap-payroll-output').innerHTML='<b>Salario ordinario prorrateado (simulación): '+ordinary.toLocaleString('es-CO')+' COP</b><br>Auxilio, extras/recargos, prestaciones y seguridad social: <b>pendientes de reglas validadas</b>.<br>Norma de referencia: CO-CST; revisión jurídica obligatoria. No es neto a pagar.'}}box.querySelectorAll('[data-prompt]').forEach(b=>b.onclick=()=>AP.ask(b.dataset.prompt,false,b.dataset.agent));},
  interventionsView(){
    const counts={};for(const a of ['ALPHA','BETA','GAMMA'])counts[a]=AP.one('SELECT count(*) AS n FROM audit_log WHERE actor=?',[a]).n;
    const pending=AP.one("SELECT count(*) AS n FROM approval_queue WHERE status='pendiente'").n;
    const chatRows=AP.query("SELECT occurred_at,actor,action,details_json FROM audit_log WHERE action IN ('pregunta_chat','respuesta_modelo','chat_sin_respuesta') ORDER BY occurred_at DESC,rowid DESC LIMIT 30").reverse();
    const chatHtml=chatRows.length?chatRows.map(x=>{let txt='';try{const d=JSON.parse(x.details_json);txt=d.text||d.error||''}catch(e){}const who=x.action==='pregunta_chat'?'Tú':x.actor;return '<div class="ap-line"><b>'+this.safe(who)+'</b> · '+this.safe(new Date(x.occurred_at).toLocaleString('es-ES',{dateStyle:'short',timeStyle:'short'}))+(x.action==='chat_sin_respuesta'?' · <b>sin respuesta del modelo</b>':'')+'<br>'+this.safe(txt)+'</div>';}).join(''):'<p class="ap-compliance-note">Todavía no hay conversación registrada en esta sesión. Abre el chat (👤 Supervisión humana → pestaña Chat) y habla con ALPHA: cada turno queda registrado aquí. La conversación necesita la conexión con el modelo.</p>';
    const decisions=AP.query("SELECT p.id,p.sku,p.proposed_qty,a.status,a.decided_by,a.decided_at FROM purchase_proposals p JOIN approval_queue a ON a.proposal_id=p.id ORDER BY p.rowid DESC LIMIT 20");
    const events=AP.query("SELECT occurred_at,actor,action,entity_id FROM audit_log WHERE actor IN ('ALPHA','BETA','GAMMA','USUARIO') AND action NOT IN ('pregunta_chat','respuesta_modelo','chat_sin_respuesta') ORDER BY occurred_at DESC,rowid DESC LIMIT 50");
    return this.section('Intervenciones de agentes','Todo lo que cada agente ha decidido, propuesto o dicho, leído de la auditoría local de este navegador. BETA y GAMMA actúan con reglas fijas: no conversan; sus intervenciones son los eventos y decisiones de abajo. Solo ALPHA conversa con el modelo, y solo cuando la conexión está disponible. Nada se inventa: cada línea es un registro real de esta sesión.',
      this.tiles([['Intervenciones ALPHA',counts.ALPHA],['Intervenciones BETA',counts.BETA],['Intervenciones GAMMA',counts.GAMMA],['Decisiones pendientes',pending]])+
      '<h3>Conversación con el modelo (ALPHA)</h3>'+chatHtml+
      '<h3>Decisiones y su estado</h3>'+(decisions.length?this.table(['Propuesta','SKU','Unidades','Estado','Decidida por','Fecha'],decisions.map(x=>[x.id,x.sku,x.proposed_qty,x.status,x.decided_by||'—',x.decided_at?x.decided_at.slice(0,19).replace('T',' '):'—'])):'<p class="ap-compliance-note">Aún no hay propuestas que decidir.</p>')+
      '<h3>Intervenciones registradas · acciones y reglas</h3>'+(events.length?this.table(['Fecha','Agente','Acción','Entidad'],events.map(x=>[x.occurred_at.slice(0,19).replace('T',' '),x.actor,x.action.replaceAll('_',' '),x.entity_id])):'<p class="ap-compliance-note">Aún no hay intervenciones registradas.</p>'));
  },
  render(i){const q=(sql,args=[])=>AP.query(sql,args),one=(sql)=>AP.one(sql),money=n=>AP.money(n||0),t=(h,r)=>this.table(h,r),tiles=x=>this.tiles(x),ask=(l,p)=>this.ask(l,p),p=q('SELECT * FROM stock_status ORDER BY sku'),orders=q('SELECT o.id,o.customer_name,o.region,o.status,o.placed_at,coalesce(sum(l.quantity*l.unit_price_cents),0) AS amount FROM sales_orders o LEFT JOIN sales_order_lines l ON l.order_id=o.id GROUP BY o.id ORDER BY o.rowid DESC LIMIT 40'),props=q('SELECT p.id,p.sku,s.name,p.proposed_qty,p.unit_cost_cents,s.lead_days,a.status FROM purchase_proposals p JOIN suppliers s ON s.id=p.supplier_id JOIN approval_queue a ON a.proposal_id=p.id ORDER BY p.rowid DESC LIMIT 30');
    switch(i){
      case 1: {const total=one('SELECT coalesce(sum(quantity*unit_price_cents),0) AS n FROM sales_order_lines').n, committed=one("SELECT coalesce(sum(p.proposed_qty*p.unit_cost_cents),0) AS n FROM purchase_proposals p JOIN approval_queue a ON a.proposal_id=p.id WHERE a.status='aprobada'").n;return this.section('Finanzas','Cartera de pedidos y compromisos locales. Una propuesta aprobada no equivale a un pago.',tiles([['Valor de pedidos',money(total)],['Propuestas aprobadas',money(committed)],['Pedidos registrados',orders.length]])+ '<h3>Valor por pedido</h3>'+t(['Pedido','Cliente','Estado','Importe'],orders.map(x=>[x.id,x.customer_name,x.status,money(x.amount)]))+ '<div class="ap-tools">'+ask('BETA · Analizar cartera','Analiza la cartera de pedidos y el riesgo de inventario. No infieras cobros ni pagos.')+'</div>');}
      case 2: return this.section('Suministro','Propuestas locales, proveedores activos y plazos. Aprobar no emite una orden de compra.',tiles([['Propuestas',props.length],['Pendientes',props.filter(x=>x.status==='pendiente').length],['Proveedores activos',one('SELECT count(*) AS n FROM suppliers WHERE active=1').n]])+'<h3>Propuestas</h3>'+t(['ID','SKU','Proveedor','Unidades','Plazo','Estado'],props.map(x=>[x.id,x.sku,x.name,x.proposed_qty,x.lead_days+' días',x.status]))+'<div class="ap-tools">'+ask('ALPHA · Revisar suministro','Revisa las alertas de reposición y, si corresponde, crea propuestas locales de compra pendientes de aprobación.')+'</div>');
      case 3: return this.section('Logística IA','GAMMA compara proveedores y plazos para las propuestas creadas en este navegador.',tiles([['Proveedores activos',one('SELECT count(*) AS n FROM suppliers WHERE active=1').n],['Propuestas con proveedor',props.length]])+'<h3>Opciones de abastecimiento</h3>'+t(['Proveedor','SKU','Plazo','Coste unitario','Mínimo'],q('SELECT s.name,sp.sku,s.lead_days,sp.unit_cost_cents,sp.min_order_qty FROM supplier_products sp JOIN suppliers s ON s.id=sp.supplier_id WHERE s.active=1 ORDER BY sp.sku,s.lead_days,sp.unit_cost_cents').map(x=>[x.name,x.sku,x.lead_days+' días',money(x.unit_cost_cents),x.min_order_qty]))+'<div class="ap-tools">'+ask('GAMMA · Evaluar plazos','Compara proveedores activos y sus plazos para productos con alerta; no afirmes envíos ni entregas que no consten.')+'</div>');
      case 4: return this.section('Gestión de Stock','Existencias físicas, reservas y umbrales calculados desde SQLite.',tiles([['Referencias',p.length],['Con alerta',p.filter(x=>x.needs_restock).length],['Unidades disponibles',p.reduce((n,x)=>n+x.available,0)]])+'<h3>Inventario</h3>'+t(['SKU','Producto','Físico','Reservado','Disponible','Umbral','Objetivo','Estado'],p.map(x=>[x.sku,x.name,x.on_hand,x.reserved,x.available,x.reorder_point,x.target_stock,x.needs_restock?'Reponer':'Correcto']))+'<div class="ap-tools">'+ask('ALPHA · Analizar stock','Revisa existencias y pedidos. Si procede, crea una propuesta local de reposición, sin comprar ni registrar recepción.')+'</div>');
      case 5: return this.section('Ventas','Pedidos registrados en este navegador. BETA añade entradas durante los ciclos visibles.',tiles([['Pedidos',one('SELECT count(*) AS n FROM sales_orders').n],['Valor registrado',money(one('SELECT coalesce(sum(quantity*unit_price_cents),0) AS n FROM sales_order_lines').n)],['Pendientes',one("SELECT count(*) AS n FROM sales_orders WHERE status='pendiente'").n]])+'<h3>Pedidos recientes</h3>'+t(['Pedido','Cliente','Región','Estado','Valor'],orders.map(x=>[x.id,x.customer_name,x.region,x.status,money(x.amount)]))+'<div class="ap-tools">'+ask('BETA · Analizar pedidos','Revisa los pedidos existentes y señala si alguna línea presenta riesgo por stock bajo, sin inventar ventas ni cobros.')+'</div>');

      case 6: {const customers=q('SELECT customer_name,region,count(*) AS orders,sum(l.quantity*l.unit_price_cents) AS value FROM sales_orders o JOIN sales_order_lines l ON l.order_id=o.id GROUP BY customer_name,region ORDER BY value DESC');return this.section('CRM Predictivo','BETA observa el valor y frecuencia por cliente. Las señales son reglas sobre datos locales, no una probabilidad externa.',tiles([['Clientes',customers.length],['Cartera',money(customers.reduce((n,x)=>n+x.value,0))]])+'<h3>Clientes y señales</h3>'+t(['Cliente','Región','Pedidos','Valor','Señal'],customers.map(x=>[x.customer_name,x.region,x.orders,money(x.value),x.orders>2?'Seguimiento prioritario':'Relación activa']))+'<div class="ap-tools">'+ask('BETA · Revisar clientes','Analiza clientes y pedidos de la base local. No inventes historial, satisfacción ni predicciones cuantificadas.')+'</div>');}
      case 7: {const prices=q('SELECT p.sku,p.name,p.sale_price_cents,min(sp.unit_cost_cents) AS cost,count(l.order_id) AS lines FROM products p LEFT JOIN supplier_products sp ON sp.sku=p.sku LEFT JOIN sales_order_lines l ON l.sku=p.sku GROUP BY p.sku ORDER BY p.sku');return this.section('Precios Dinámicos','BETA compara precios de venta con costes registrados. Sin cambiar precios sin aprobación humana.',tiles([['Referencias',prices.length],['Margen bruto orientativo',prices.length?Math.round(prices.reduce((n,x)=>n+(x.sale_price_cents-x.cost)/x.sale_price_cents,0)/prices.length*100)+' %':'-']])+'<h3>Lectura de márgenes</h3>'+t(['SKU','Producto','Precio','Coste mínimo','Margen bruto','Líneas'],prices.map(x=>[x.sku,x.name,money(x.sale_price_cents),money(x.cost),x.cost?Math.round((x.sale_price_cents-x.cost)/x.sale_price_cents*100)+' %':'-',x.lines]))+'<div class="ap-tools">'+ask('BETA · Evaluar precios','Revisa precios de venta y costes de proveedores. Sugiere oportunidades sin afirmar un cambio ni asignar precios nuevos.')+'</div>');}
      case 8: {const people=q('SELECT * FROM workforce ORDER BY team,name');return this.section('RRHH','Capacidad de equipo registrada en este entorno. No se toman decisiones laborales de forma automática.',tiles([['Personas',people.length],['Horas asignadas',people.reduce((n,x)=>n+x.assigned_hours,0)],['Capacidad semanal',people.reduce((n,x)=>n+x.capacity_hours,0)]])+'<h3>Equipo y carga</h3>'+t(['Nombre','Rol','Equipo','Asignadas','Capacidad','Estado'],people.map(x=>[x.name,x.role,x.team,x.assigned_hours+' h',x.capacity_hours+' h',x.status])));}
      case 9: {const people=q('SELECT name,role,team,capacity_hours-assigned_hours AS available,status FROM workforce ORDER BY available DESC');return this.section('Talento IA','Vista de capacidad disponible por equipo. No evalúa personas ni infiere aptitudes o rendimiento.',tiles([['Horas libres',people.reduce((n,x)=>n+x.available,0)],['Equipos',new Set(people.map(x=>x.team)).size]])+'<h3>Disponibilidad</h3>'+t(['Persona','Especialidad registrada','Equipo','Horas libres','Estado'],people.map(x=>[x.name,x.role,x.team,x.available+' h',x.status])));}
      case 10: {const tasks=q('SELECT * FROM planning_tasks ORDER BY due_at');return this.section('Planificación','Tareas operativas vinculadas a pedidos. Fechas del conjunto local, sin agenda externa.',tiles([['Tareas',tasks.length],['Pendientes',tasks.filter(x=>x.status==='pendiente').length]])+'<h3>Próximos hitos</h3>'+t(['ID','Tarea','Fecha objetivo','Equipo','Pedido','Estado'],tasks.map(x=>[x.id,x.title,x.due_at.slice(0,10),x.team,x.linked_order_id||'-',x.status]))+'<div class="ap-tools">'+ask('ALPHA · Revisar prioridades','Revisa stock, pedidos y propuestas para señalar prioridades. No cambies fechas ni prometas entregas.')+'</div>');}
      case 11: {const jobs=q('SELECT j.*,p.name FROM production_jobs j JOIN products p ON p.sku=j.sku ORDER BY j.due_at');return this.section('Producción','Órdenes de trabajo locales conectadas a productos y pedidos. Sin registrar producción física automáticamente.',tiles([['Órdenes de trabajo',jobs.length],['Unidades planificadas',jobs.reduce((n,x)=>n+x.quantity,0)]])+'<h3>Estado de fabricación</h3>'+t(['Orden','Producto','Unidades','Fase','Objetivo','Responsable','Pedido'],jobs.map(x=>[x.id,x.name,x.quantity,x.stage,x.due_at.slice(0,10),x.owner,x.order_id||'-']))+'<div class="ap-tools">'+ask('ALPHA · Evaluar materiales','Revisa las órdenes de producción y el stock disponible; señala riesgos materiales sin afirmar fabricación real.')+'</div>');}
      case 12: {const events=q('SELECT occurred_at,actor,action,entity_id FROM audit_log ORDER BY rowid DESC LIMIT 50');return this.section('Reportes Avanzados','Informe en vivo de la base de este navegador. Cada evento tiene registro de auditoría.',tiles([['Pedidos',one('SELECT count(*) AS n FROM sales_orders').n],['Eventos',one('SELECT count(*) AS n FROM audit_log').n],['Propuestas',props.length]])+'<h3>Registro de operaciones</h3>'+t(['Fecha','Agente','Acción','Entidad'],events.map(x=>[x.occurred_at.slice(0,19).replace('T',' '),x.actor,x.action.replaceAll('_',' '),x.entity_id])));}
      case 14: return APCompliance.view();
      case 15: return APCompliance.payrollView();
      case 16: return APCompliance.agentsView();
      case 17: return APCompliance.costView();
      case 18: return APCompliance.recruitmentView();
      case 19: return APCompliance.candidateView();
      case 20: return this.activityView();
      case 21: return this.interventionsView();
      case 13: {const events=q('SELECT actor,action,entity_id,occurred_at FROM audit_log ORDER BY rowid DESC LIMIT 30');return this.section('Agentes IA','ALPHA analiza inventario, BETA registra pedidos y GAMMA compara proveedores. Los estados se basan en operaciones de SQLite.',tiles([['ALPHA',document.getElementById('ap-agent-alpha').textContent],['BETA',document.getElementById('ap-agent-beta').textContent],['GAMMA',document.getElementById('ap-agent-gamma').textContent]])+'<h3>Actividad registrada</h3>'+t(['Agente','Operación','Entidad','Hora'],events.map(x=>[x.actor,x.action.replaceAll('_',' '),x.entity_id,x.occurred_at.slice(11,19)]))+'<div class="ap-tools">'+ask('ALPHA · Ejecutar análisis','Revisa inventario y pedidos actuales, y crea propuesta de reposición si es necesaria.')+'</div>');}
      default: return this.section(this.names[i],'Sin datos para esta sección.','');
    }
  }
};

globalThis.APViews = APViews;