#!/usr/bin/env python3 """Prepara escenarios de puestos solo en la instalación desechable de visual-audit.py.""" import json import os from pathlib import Path import runpy import tomllib from urllib.parse import urlparse ROOT = Path(__file__).resolve().parent.parent AUDIT = runpy.run_path(str(ROOT / "scripts/visual-audit.py")) def main(): state = (ROOT / os.environ["METIS_PLAYWRIGHT_STORAGE_STATE"]).resolve() if not state.is_relative_to(ROOT / ".artifacts/playwright") or state.stat().st_mode & 0o077: raise RuntimeError("Requiere la sesión privada del preparador sintético.") report = json.loads((state.parent / "report.json").read_text()) if urlparse(report["origin"]).hostname != "localhost" or report.get("ordinaryUser") is not True: raise RuntimeError("Requiere instalación local sintética con usuario ordinario.") config = tomllib.loads((ROOT / ".codex/config.toml").read_text())["mcp_servers"]["playwright"] code = r'''async (page) => { const origin=__ORIGIN__, companyId=__COMPANY__; const session=await page.request.get(origin+'/api/auth/session'); if(session.status()!==200)throw Error('Sesión no disponible'); const {csrfToken}=await session.json(); async function get(path){const r=await page.request.get(origin+'/api'+path);if(r.status()!==200)throw Error('GET '+path+': '+r.status());return r.json()} async function post(path,data){const r=await page.request.post(origin+'/api'+path,{data,headers:{origin,'x-csrf-token':csrfToken}});if(![200,201].includes(r.status())){const j=await r.json();throw Error('POST '+path+': '+r.status()+' '+j.code)}return r.json()} const base='/companies/'+companyId; const details=await get(base);let company=details.company??details; if(company.name!=='Auditoría visual — Datos sintéticos')throw Error('Compañía no sintética'); const catalog=(await get('/asset-catalog/'+companyId)).items; const resourceCatalog=(await get('/resource-catalog/'+companyId)).items; const inventory=await get('/assets/'+companyId); const resources=(await get('/resources/'+companyId)).resources; const profileRecords=(await get('/work-profiles/'+companyId)).profiles; function template(name){const t=catalog.find(x=>x.values.kind==='template'&&x.values.name===name);if(!t)throw Error('Plantilla ausente: '+name);return t.id} async function refreshCompany(){const d=await get(base);company=d.company??d;return company} async function person(name){let p=company.people.find(x=>x.name===name);if(!p){await post(base+'/people',{name,position:null,email:null,phone:null,active:true,isContact:false});await refreshCompany();p=company.people.find(x=>x.name===name)}return p} const angela=await person('Ángela Muñoz de la Peña'), inigo=await person('Íñigo Fernández del Río'); const siteName='Sede de pruebas — área administrativa'; if(!company.sites.some(x=>x.name===siteName)){await post(base+'/sites',{name:siteName,active:true,address:null,phone:null});await refreshCompany()} const site=company.sites.find(x=>x.name===siteName); const deptName='Administración y gestión documental'; if(!company.departments.some(x=>x.name===deptName)){await post(base+'/departments',{name:deptName,active:true});await refreshCompany()} const department=company.departments.find(x=>x.name===deptName); if(!company.assignments.some(x=>x.personId===angela.id&&x.departmentId===department.id)){await post(base+'/people/'+angela.id+'/assignments',{departmentId:department.id,position:'Responsable de documentación',startDate:'2026-01-01',endDate:null,isPrimary:true});await refreshCompany()} async function asset(name,kind='Portátil'){let a=inventory.assets.find(x=>x.values.name===name);if(!a){a=(await post('/assets/'+companyId,{values:{name,templateId:template(kind),siteId:site.id,status:'operational',notes:'Fixture sintética del dashboard.',characteristics:{}}})).asset;inventory.assets.push(a)}return a} const host=await asset('PORT-ADM-01 · Documentación y gestión'); const monitor=await asset('MON-ADM-01 · Monitor panorámico','Monitor'); const printer=await asset('IMP-RED-01 · Impresora de planta compartida','Impresora'); const lab=await asset('PC-LAB-01 · Equipo del laboratorio'); async function resource(key,name,characteristics={}){let r=resources.find(x=>x.values.name===name);if(!r){const t=resourceCatalog.find(x=>x.builtinKey==='template:'+key);if(!t)throw Error('Plantilla recurso ausente: '+key);r=(await post('/resources/'+companyId,{values:{name,templateId:t.id,active:true,notes:'Fixture sintética del dashboard.',characteristics}})).resource;resources.push(r)}return r} function field(key){const f=resourceCatalog.find(x=>x.builtinKey==='field:'+key);if(!f)throw Error('Campo ausente: '+key);return f.id} const app=await resource('application','Gestor documental de ejemplo'); const os=await resource('os','Sistema operativo de ejemplo'); const license=await resource('software_license','Licencia documental de ejemplo'); const certificate=await resource('certificate','Certificado de pruebas de documentación',{[field('expires')]:'2027-12-31'}); const mailbox=await resource('mailbox','Buzón de Ángela',{[field('email')]:'angela@example.invalid',[field('context')]:'Correo de pruebas'}); const unc=String.raw`\\srv-doc.example.invalid\documentacion\administracion`; const share=await resource('shared_folder','Documentación administrativa compartida',{[field('unc')]:unc}); const tool=await resource('support','Herramienta de soporte de ejemplo'); function same(actual,expected){return Object.entries(expected).every(([key,value])=>Array.isArray(value)?Array.isArray(actual[key])&&value.length===actual[key].length&&value.every(x=>actual[key].includes(x)):key==='driveLetter'?(actual[key]??'').replace(/:$/,'')===(value??'').replace(/:$/,''):actual[key]===value)} async function access(values){const before=(await get('/access/'+companyId)).items;const existing=before.find(x=>x.values.kind===values.kind&&(values.name?x.values.name===values.name:values.login?x.values.login===values.login:same(x.values,values)));if(existing)return existing;const saved=await post('/access/'+companyId,{values});return saved.items.find(x=>!before.some(y=>y.id===x.id))} const system=await access({kind:'system',name:'Identidad corporativa de pruebas',active:true}); const account=await access({kind:'account',systemId:system.id,environmentId:null,login:'angela.pruebas',usage:'personal',active:true,expiresOn:null,notes:null}); const period={startDate:'2026-01-01',endDate:null}; const accountPerson=await access({kind:'account_person',accountId:account.id,personId:angela.id,...period,configuration:'configured'}); const type=share.values.integration.resourceTypeId; const permission=await access({kind:'permission',name:'Lectura documental de pruebas',active:true,resourceTypeId:type,description:null}); const need=await access({kind:'need',personId:angela.id,resourceId:share.id,permissionIds:[permission.id],...period,notes:null}); const grant=await access({kind:'grant',resourceId:share.id,permissionId:permission.id,accountId:account.id,groupId:null,...period,configuration:'configured'}); await access({kind:'coverage',needId:need.id,grantId:grant.id,accountPersonId:accountPerson.id,membershipId:null,...period}); async function station(name,computer,personId,ids){let s=inventory.workstations.find(x=>x.values.name===name);if(!s){s=(await post('/assets/'+companyId+'/workstations',{values:{name,active:true,siteId:site.id,personId,computerAssetId:computer?.id??null,assetIds:ids}})).workstation;inventory.workstations.push(s)}return s} let main=inventory.workstations.find(x=>x.values.name==='Administración — documentación y atención interna'); if(!main){const first=await station('Administración — puesto inicial',host,angela.id,[host.id]);main=(await post('/assets/'+companyId+'/workstations/'+first.id+'/update',{values:{...first.values,name:'Administración — documentación y atención interna',assetIds:[host.id,monitor.id]}})).workstation} const withoutResponsible=await station('Laboratorio — equipo compartido sin responsable',lab,null,[lab.id]); const empty=await station('Puesto pendiente de equipamiento',null,null,[]); const retired=await station('Recepción — puesto desmantelado',null,angela.id,[]); if(retired.values.active)await post('/assets/'+companyId+'/workstations/'+retired.id+'/decommission',{}); async function profile(name,personId,assetId,active=true,accountId=null){let p=profileRecords.find(x=>x.values.name===name);if(!p){p=(await post('/work-profiles/'+companyId,{values:{name,personId,assetId,active,accountId,notes:null}})).profile;profileRecords.push(p)}return p} const primary=await profile('Ángela · administración',angela.id,host.id,true,account.id); const secondary=await profile('Íñigo · consultas de documentación',inigo.id,host.id); await profile('Ángela · perfil archivado del mismo ordenador',angela.id,host.id,false); await profile('Ángela · laboratorio',angela.id,lab.id); async function configuration(path,values,collection='configurations'){const items=(await get('/'+path+'/'+companyId))[collection];if(!items.some(x=>same(x.values,values)))await post('/'+path+'/'+companyId,{values})} await configuration('software-installations',{assetId:host.id,resourceId:app.id,licenseResourceId:license.id,version:'4.2',state:'configured',notes:null}); await configuration('software-installations',{assetId:host.id,resourceId:os.id,licenseResourceId:null,version:null,state:'configured',notes:null}); const mailboxUses=(await get('/mailboxes/'+companyId)).uses; if(!mailboxUses.some(x=>x.values.personId===angela.id&&x.values.mailboxId===mailbox.id))await post('/mailboxes/'+companyId+'/uses',{values:{mailboxId:mailbox.id,personId:angela.id,...period}}); await configuration('folder-mappings',{profileId:primary.id,needId:need.id,accessAccountId:account.id,path:unc,driveLetter:'Z',state:'configured',notes:null},'mappings'); await configuration('mail-configurations',{profileId:primary.id,mailboxId:mailbox.id,method:'imap',state:'configured',notes:null}); await configuration('application-configurations',{profileId:primary.id,resourceId:app.id,delivery:'installed',version:'4.2',state:'configured',notes:null}); await configuration('printer-configurations',{profileId:primary.id,printerId:printer.id,connection:'network',isDefault:true,state:'configured',notes:null}); await configuration('certificate-configurations',{profileId:primary.id,resourceId:certificate.id,location:null,state:'pending',notes:'Instalación pendiente de completar.'}); await configuration('telework-authorizations',{personId:angela.id,active:true,startsOn:'2026-01-01',expiresOn:'2027-08-31',notes:null},'authorizations'); await configuration('remote-connections',{personId:angela.id,sourceKind:'company_asset',sourceAssetId:host.id,targetKind:'network',targetId:null,siteId:site.id,method:'VPN corporativa documentada',requiresVpn:true,state:'configured',notes:null},'connections'); const support=await get('/asset-support/'+companyId+'/'+host.id); if(!support.connections.length)await post('/asset-support/'+companyId+'/'+host.id,{values:{resourceId:tool.id,destination:'equipo-adm-01.example.invalid',requiresVpn:false,preferred:true,state:'available',notes:''}}); const denseAssets=[]; for(let i=0;i<80;i++){denseAssets.push(await asset('DASH-DENSE-'+String(i).padStart(2,'0')+(i%10===0?' — Equipo de documentación e ingeniería con nombre extenso':'')))} const dense=await station('Puesto de pruebas — alta densidad',denseAssets[0],null,denseAssets.map(x=>x.id)); let extra=0; while(company.people.filter(x=>x.active).length<52){await person('DASH-PERSON-ACTIVE-'+String(extra++).padStart(2,'0'))} const activePeople=company.people.filter(x=>x.active).slice(0,50); for(let i=0;ix.id!==companyId); return {companyId,origin,main:main.id,withoutResponsible:withoutResponsible.id,empty:empty.id,retired:retired.id,dense:dense.id,primaryProfile:primary.id,secondaryProfile:secondary.id,host:host.id,lab:lab.id,otherCompany:other.id,counts:{companies:(await get('/companies')).companies.length,people:company.people.length,assets:(await get('/assets/'+companyId)).assets.length,resources:(await get('/resources/'+companyId)).resources.length,denseMembers:dense.values.assetIds.length,denseProfiles:60}}; }'''.replace("__ORIGIN__", json.dumps(report["origin"])).replace("__COMPANY__", json.dumps(report["companyId"])) with AUDIT["browser"](config, state) as client: result = AUDIT["result_json"](AUDIT["tool"](client, "browser_run_code_unsafe", {"code": code})) (state.parent / "workstation-fixtures.json").write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n") print(json.dumps(result, ensure_ascii=False)) if __name__ == "__main__": main()