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 '
'+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='
'+
''+
''+
''+
''+
'
';
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+'
'+this.activityResults()+'
');
},
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)
])):'
No hay eventos que cumplan estos filtros.
';
return '
Mostrando '+rows.length+' de '+shown+' eventos que cumplen el filtro, de '+AP.one('SELECT count(*) AS n FROM audit_log').n+' registrados en total.
';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='Salario ordinario prorrateado (simulación): '+ordinary.toLocaleString('es-CO')+' COP Auxilio, extras/recargos, prestaciones y seguridad social: pendientes de reglas validadas. 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 '
'+this.safe(who)+' · '+this.safe(new Date(x.occurred_at).toLocaleString('es-ES',{dateStyle:'short',timeStyle:'short'}))+(x.action==='chat_sin_respuesta'?' · sin respuesta del modelo':'')+' '+this.safe(txt)+'
';}).join(''):'
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.
';
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]])+
'
'));
},
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]])+ '
'+ask('BETA · Analizar cartera','Analiza la cartera de pedidos y el riesgo de inventario. No infieras cobros ni pagos.')+'
');}
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]])+'
'+ask('ALPHA · Revisar suministro','Revisa las alertas de reposición y, si corresponde, crea propuestas locales de compra pendientes de aprobación.')+'
');
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]])+'
Opciones de abastecimiento
'+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]))+'
'+ask('GAMMA · Evaluar plazos','Compara proveedores activos y sus plazos para productos con alerta; no afirmes envíos ni entregas que no consten.')+'
');
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)]])+'
'+ask('ALPHA · Analizar stock','Revisa existencias y pedidos. Si procede, crea una propuesta local de reposición, sin comprar ni registrar recepción.')+'
');
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]])+'
'+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.')+'
');
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))]])+'
'+ask('BETA · Revisar clientes','Analiza clientes y pedidos de la base local. No inventes historial, satisfacción ni predicciones cuantificadas.')+'
');}
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)+' %':'-']])+'
'+ask('BETA · Evaluar precios','Revisa precios de venta y costes de proveedores. Sugiere oportunidades sin afirmar un cambio ni asignar precios nuevos.')+'
');}
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)]])+'
Equipo y carga
'+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]])+'
Disponibilidad
'+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]])+'
'+ask('ALPHA · Revisar prioridades','Revisa stock, pedidos y propuestas para señalar prioridades. No cambies fechas ni prometas entregas.')+'
');}
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)]])+'
'+ask('ALPHA · Evaluar materiales','Revisa las órdenes de producción y el stock disponible; señala riesgos materiales sin afirmar fabricación real.')+'
');}
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]])+'
Registro de operaciones
'+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]])+'