export async function prepareEmbeddedRelations(page) { // Grafo sintético de lectura para L04.3. Solo lo ejecuta el cliente MCP de pruebas. const f = page.adoption; const companyId = f.detail.company.id; const inventory = f.inventory; const resources = f.snapshots[`/resources/${companyId}`].resources; const actorId = inventory.assets[0].history[0].actorId; const asset = inventory.assets.find((a) => a.id === page.embedded.asset); const domain = resources.find((r) => r.directory); const smb = resources.find((r) => r.networkShare); let sequence = 1000; const uuid = () => `019a4300-0000-7000-b000-${String(++sequence).padStart(12, "0")}`; const shares = Array.from({ length: 50 }, (_, i) => { const result = structuredClone(smb); const name = `L043-SHARE-${String(i + 1).padStart(2, "0")} · Administración Peña`; const address = result.networkShare.address .replace(result.networkShare.host, asset.values.name) .replace(result.networkShare.shareName, `L043-area-${i + 1}`); const old = result.networkShare.address; result.id = uuid(); result.values.name = name; result.values.characteristics = Object.fromEntries( Object.entries(result.values.characteristics).map(([key, value]) => [ key, value === old ? address : value === smb.networkShare.host ? asset.values.name : value, ]) ); result.networkShare = { ...result.networkShare, address, normalizedAddress: address.toLowerCase(), host: asset.values.name, shareName: `L043-area-${i + 1}`, }; result.history = [{ ...result.history[0], id: uuid(), values: structuredClone(result.values) }]; return result; }); resources.push(...shares); const reference = (providerId, id) => ({ providerId, entityType: providerId === "assets" ? "assets:asset" : "resources:resource", id, companyId, }); const summary = (ref, name, subtitle) => ({ status: "resolved", summary: { reference: ref, name, subtitle }, }); const publications = shares.map((r) => ({ id: uuid(), companyId, assetId: asset.id, resourceId: r.id, predecessorId: null, startedAt: "2026-03-01T10:00:00.000Z", startedBy: actorId, endedAt: null, endedBy: null, })); const history = shares.slice(0, 25).map((r) => ({ id: uuid(), companyId, assetId: asset.id, resourceId: r.id, predecessorId: null, startedAt: "2026-02-01T10:00:00.000Z", startedBy: actorId, endedAt: "2026-02-10T10:00:00.000Z", endedBy: actorId, })); const computers = inventory.assets .filter((a) => a.values.status === "operational" && a.specification.templateName === "Portátil") .slice(0, 50); const memberships = computers.map((a) => ({ typeId: "assets:directory-membership", origin: { kind: "record", id: uuid() }, source: reference("assets", a.id), target: reference("resources", domain.id), stateCode: "active", roleCode: "member", })); const publicationView = (p) => ({ typeId: "assets:network-share-publication", origin: { kind: "record", id: p.id }, source: reference("assets", p.assetId), target: reference("resources", p.resourceId), stateCode: p.endedAt ? "ended" : "active", }); const summaries = (items, fromAsset) => items.map((item) => { const ref = fromAsset ? item.target : item.source; const entity = (fromAsset ? resources : inventory.assets).find((e) => e.id === ref.id); return summary( ref, entity.values.name, fromAsset ? (entity.networkShare?.address ?? entity.directory?.normalizedDns ?? "") : entity.specification.templateName ); }); const slice = (items, cursor, size = 20) => { const offset = cursor ? items.findIndex((x) => (x.origin?.id ?? x.id) === cursor) + 1 : 0; const values = items.slice(offset, offset + size); return { items: values, nextCursor: offset + size < items.length ? (values.at(-1).origin?.id ?? values.at(-1).id) : null, }; }; page.embeddedData = { shares, domain: domain.id, publications, memberships, requests: [], fault: null, held: null, removed: null, publicationResponse: null, directoryResponse: null, releases: [], }; page.embedded.release = () => page.embeddedData.release?.(); const endpoint = `${page.adoptionOrigin}/api/asset-network-share-publications/${companyId}`; async function publicationResponse(route, _url, params) { const d = page.embeddedData; const fromAsset = params.get("anchorType") === "assets:asset"; const anchor = params.get("anchorId"); const periods = ( params.get("includeEnded") === "true" ? [...history, ...publications] : publications ).filter((p) => (fromAsset ? p.assetId : p.resourceId) === anchor && p.id !== d.removed); const pg = slice(periods, params.get("cursor"), Number(params.get("pageSize"))); const items = pg.items.map(publicationView); const response = { page: { items, nextCursor: pg.nextCursor }, summaries: summaries(items, fromAsset), publications: pg.items, }; d.publicationResponse = response; if (d.fault === "publications") { return route.fulfill({ status: 503, json: { code: "INTERNAL_ERROR" } }); } if (d.fault === "denied") { return route.fulfill({ status: 403, json: { code: "FORBIDDEN" } }); } if (d.fault === "hold-publications") { await new Promise((resolve) => { d.release = resolve; d.releases.push(resolve); }); } return route.fulfill({ json: response }); } async function optionResponse(route, url, params) { const d = page.embeddedData; const kind = url.pathname.endsWith("/asset-options") ? "asset" : "share"; const search = params.get("search") ?? ""; const cursor = params.get("cursor"); const items = ( kind === "asset" ? inventory.assets .filter((a) => a.values.status === "operational") .map((a) => ({ id: a.id, name: a.values.name, templateName: a.specification.templateName, status: a.values.status, })) : shares.map((r) => ({ id: r.id, name: r.values.name, address: r.networkShare.address })) ).filter((x) => `${x.name} ${x.address ?? ""}`.toLowerCase().includes(search.trim().toLowerCase()) ); const response = slice(items, cursor); if (d.fault === "options") { return route.fulfill({ status: 503, json: { code: "INTERNAL_ERROR" } }); } if (search === "ANTIGUA") { d.held = true; await new Promise((resolve) => { d.release = resolve; d.releases.push(resolve); }); // Una respuesta antigua contiene una opción distinguible, aunque su petición haya sido cancelada. response.items = [ { id: shares[0].id, name: "RESPUESTA ANTIGUA", address: shares[0].networkShare.address }, ]; } try { await route.fulfill({ json: response }); } catch (error) { if (!(String(error).includes("intercepted") || String(error).includes("Target page"))) { throw error; } d.requests.push({ aborted: search }); } } await page.route(`${endpoint}/**`, async (route) => { if (route.request().method() !== "GET") { return route.fallback(); } const url = new URL(route.request().url()); page.embeddedData.requests.push({ path: url.pathname, query: Object.fromEntries(url.searchParams), }); return url.pathname.endsWith("/relationships") ? publicationResponse(route, url, url.searchParams) : optionResponse(route, url, url.searchParams); }); await page.route( `${page.adoptionOrigin}/api/asset-directory-memberships/${companyId}/**`, async (route) => { if (route.request().method() !== "GET") { return route.fallback(); } const url = new URL(route.request().url()); const p = url.searchParams; page.embeddedData.requests.push({ path: url.pathname, query: Object.fromEntries(p) }); if (url.pathname.endsWith("/eligibility")) { const id = url.pathname.split("/").at(-2); const candidate = inventory.assets.find((asset) => asset.id === id); const template = candidate?.specification.templateName; return route.fulfill({ json: { operational: candidate?.values.status === "operational", computer: ["Portátil", "PC de sobremesa"].includes(template), server: template === "Servidor", }, }); } if (!url.pathname.endsWith("/relationships")) { return route.fallback(); } const fromAsset = p.get("anchorType") === "assets:asset"; const anchor = p.get("anchorId"); const pg = slice( memberships.filter((m) => (fromAsset ? m.source.id : m.target.id) === anchor), p.get("cursor"), Number(p.get("pageSize")) ); const response = { page: pg, summaries: summaries(pg.items, fromAsset) }; page.embeddedData.directoryResponse = response; return route.fulfill({ json: response }); } ); return { shares: shares.length, publications: publications.length, memberships: memberships.length, }; }