72 lines
5.2 KiB
Python
72 lines
5.2 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Amplía únicamente la instalación desechable de visual-audit.py mediante HTTP real.
|
||
|
|
|
||
|
|
Requiere METIS_PLAYWRIGHT_STORAGE_STATE. Repetir reutiliza registros por nombre.
|
||
|
|
No lee .env ni almacena credenciales; la sesión privada sigue perteneciendo al preparador.
|
||
|
|
"""
|
||
|
|
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():
|
||
|
|
value = os.environ.get("METIS_PLAYWRIGHT_STORAGE_STATE")
|
||
|
|
if not value:
|
||
|
|
raise RuntimeError("Requiere sesión del preparador desechable.")
|
||
|
|
state = (ROOT / value).resolve()
|
||
|
|
if not state.is_relative_to(ROOT / ".artifacts/playwright") or state.stat().st_mode & 0o077:
|
||
|
|
raise RuntimeError("La sesión debe ser privada y estar en el directorio de auditoría.")
|
||
|
|
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 sintética local con usuario ordinario.")
|
||
|
|
config = tomllib.loads((ROOT / ".codex/config.toml").read_text())["mcp_servers"]["playwright"]
|
||
|
|
with AUDIT["browser"](config, state) as client:
|
||
|
|
code = """async (page) => {
|
||
|
|
const origin=__AUDIT_ORIGIN__, company=__AUDIT_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(r.status()!==201)throw Error('POST '+path+': '+r.status());return r.json()}
|
||
|
|
const companies=(await get('/companies')).companies;
|
||
|
|
if(!companies.some(c=>c.id===company&&c.name==='Auditoría visual — Datos sintéticos'))throw Error('La compañía no es la fixture del preparador');
|
||
|
|
for(let i=1;i<=14;i++){const name='AUDIT-COMPANY-'+String(i).padStart(2,'0');if(!companies.some(c=>c.name===name))await post('/companies',{name,active:i%5!==0})}
|
||
|
|
const details=await get('/companies/'+company);
|
||
|
|
const people=details.people??details.company?.people;
|
||
|
|
if(!Array.isArray(people))throw Error('No se reconoce la colección de personas');
|
||
|
|
const catalog=(await get('/asset-catalog/'+company)).items;
|
||
|
|
const laptop=catalog.find(c=>c.values.kind==='template'&&c.values.name==='Portátil');
|
||
|
|
const printer=catalog.find(c=>c.values.kind==='template'&&c.values.name==='Impresora');
|
||
|
|
if(!laptop||!printer)throw Error('Faltan plantillas de activos');
|
||
|
|
const assets=(await get('/assets/'+company)).assets;
|
||
|
|
const resourceCatalog=(await get('/resource-catalog/'+company)).items;
|
||
|
|
const application=resourceCatalog.find(c=>c.builtinKey==='template:application');
|
||
|
|
if(!application)throw Error('Falta plantilla de aplicación');
|
||
|
|
const resources=(await get('/resources/'+company)).resources;
|
||
|
|
for(let i=1;i<=50;i++){
|
||
|
|
const suffix=String(i).padStart(2,'0');
|
||
|
|
const person='AUDIT-PERSON-'+suffix+(i%10===0?' — Álvaro Muñoz de la Peña':'');
|
||
|
|
if(!people.some(p=>p.name===person))await post('/companies/'+company+'/people',{name:person,position:i%3?'Soporte':null,email:'person'+suffix+'@audit.example.test',phone:null,active:i%7!==0,isContact:i%4===0});
|
||
|
|
const name='AUDIT-ASSET-'+suffix+(i%10===0?' — Equipo de documentación e ingeniería':'');
|
||
|
|
if(!assets.some(a=>a.values.name===name))await post('/assets/'+company,{values:{name,templateId:i%5===0?printer.id:laptop.id,siteId:null,registeredOn:i%3?'2026-01-15':null,status:i%7===0?'retired':'operational',notes:i%10===0?'Notas sintéticas de tamaño representativo. '.repeat(15):'',characteristics:{}}});
|
||
|
|
const resource='AUDIT-VOLUME-APP-'+suffix;
|
||
|
|
if(!resources.some(r=>r.values.name===resource))await post('/resources/'+company,{values:{name:resource,templateId:application.id,active:i%7!==0,notes:i%10===0?'Aplicación sintética para pruebas con grandes volúmenes. '.repeat(15):'',characteristics:{}}});
|
||
|
|
}
|
||
|
|
const inventory=await get('/assets/'+company);
|
||
|
|
const finalDetails=await get('/companies/'+company);
|
||
|
|
return {companies:(await get('/companies')).companies.length,people:(finalDetails.people??finalDetails.company.people).length,assets:inventory.assets.length,resources:(await get('/resources/'+company)).resources.length,companyId:company};
|
||
|
|
}""".replace("__AUDIT_ORIGIN__", json.dumps(report["origin"])).replace("__AUDIT_COMPANY__", json.dumps(report["companyId"]))
|
||
|
|
result = AUDIT["result_json"](AUDIT["tool"](client, "browser_run_code_unsafe", {"code": code}))
|
||
|
|
(state.parent / "volume.json").write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
|
||
|
|
print(json.dumps(result, ensure_ascii=False))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|