Spaces:
Running
Running
Update core/views.mjs
Browse files- core/views.mjs +15 -1
core/views.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
export const APViews = {
|
| 2 |
-
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"],
|
| 3 |
current:0,
|
| 4 |
safe:v=>AP.esc(v??''),
|
| 5 |
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>';},
|
|
@@ -48,6 +48,19 @@ export const APViews = {
|
|
| 48 |
if(c)c.onclick=()=>{this.activity={q:'',actor:'',limit:200};this.show(20);};
|
| 49 |
},
|
| 50 |
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));},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
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');
|
| 52 |
switch(i){
|
| 53 |
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>');}
|
|
@@ -70,6 +83,7 @@ export const APViews = {
|
|
| 70 |
case 18: return APCompliance.recruitmentView();
|
| 71 |
case 19: return APCompliance.candidateView();
|
| 72 |
case 20: return this.activityView();
|
|
|
|
| 73 |
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>');}
|
| 74 |
default: return this.section(this.names[i],'Sin datos para esta sección.','');
|
| 75 |
}
|
|
|
|
| 1 |
export const APViews = {
|
| 2 |
+
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"],
|
| 3 |
current:0,
|
| 4 |
safe:v=>AP.esc(v??''),
|
| 5 |
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>';},
|
|
|
|
| 48 |
if(c)c.onclick=()=>{this.activity={q:'',actor:'',limit:200};this.show(20);};
|
| 49 |
},
|
| 50 |
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));},
|
| 51 |
+
interventionsView(){
|
| 52 |
+
const counts={};for(const a of ['ALPHA','BETA','GAMMA'])counts[a]=AP.one('SELECT count(*) AS n FROM audit_log WHERE actor=?',[a]).n;
|
| 53 |
+
const pending=AP.one("SELECT count(*) AS n FROM approval_queue WHERE status='pendiente'").n;
|
| 54 |
+
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();
|
| 55 |
+
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>';
|
| 56 |
+
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");
|
| 57 |
+
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");
|
| 58 |
+
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.',
|
| 59 |
+
this.tiles([['Intervenciones ALPHA',counts.ALPHA],['Intervenciones BETA',counts.BETA],['Intervenciones GAMMA',counts.GAMMA],['Decisiones pendientes',pending]])+
|
| 60 |
+
'<h3>Conversación con el modelo (ALPHA)</h3>'+chatHtml+
|
| 61 |
+
'<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>')+
|
| 62 |
+
'<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>'));
|
| 63 |
+
},
|
| 64 |
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');
|
| 65 |
switch(i){
|
| 66 |
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>');}
|
|
|
|
| 83 |
case 18: return APCompliance.recruitmentView();
|
| 84 |
case 19: return APCompliance.candidateView();
|
| 85 |
case 20: return this.activityView();
|
| 86 |
+
case 21: return this.interventionsView();
|
| 87 |
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>');}
|
| 88 |
default: return this.section(this.names[i],'Sin datos para esta sección.','');
|
| 89 |
}
|