metis-desk/modules/resources/src/api/domain/resource-catalog-seed.ts
David 3ceac47cd6 feat: implement software installation management
- Add SoftwareInstallationValues value object for encapsulating software installation data.
- Create HTTP routes for managing software installations, including GET, POST, and DELETE operations.
- Implement Sequelize repository for software installations, handling database interactions.
- Add migration script for creating necessary database tables for software installations.
- Create persistence mapper for transforming between domain models and database rows.
- Define snapshot schema for software installations to ensure data consistency.
- Introduce common schemas for software installations to validate API requests and responses.
- Develop UI components for displaying and managing software installations, including history dialogs and forms.
- Implement API client for interacting with software installation endpoints.
- Write tests for software installations panel and form components to ensure functionality.
2026-10-12 20:31:21 +02:00

225 lines
6.7 KiB
TypeScript

import { UniqueID } from "@repo/rdx-ddd";
import type { ResourceCatalog } from "./resource-catalog.aggregate";
import type { ResourceDefinitionSpec, ResourceFieldType } from "./resource-definition";
import { ResourceDefinitionValues } from "./value-objects/resource-definition-values.vo";
const FIELDS: readonly [string, string, ResourceFieldType, string, (readonly string[])?][] = [
[
"email",
"Dirección de correo",
"text",
"Indica la dirección completa del buzón. Ejemplo: soporte@empresa.es.",
],
[
"provider",
"Proveedor",
"text",
"Indica el proveedor o fabricante del servicio. Ejemplo: Microsoft 365.",
],
[
"context",
"Contexto",
"text",
"Aclara el uso, área o persona a la que está destinado. Ejemplo: facturación.",
],
["unc", "Ruta UNC", "text", "Usa la ruta de red completa. Ejemplo: \\servidor\\contabilidad."],
[
"server",
"Servidor (referencia documental)",
"text",
"Anota el servidor que publica el recurso, si se conoce. Ejemplo: FS-01.",
],
[
"description",
"Descripción",
"text",
"Añade los detalles necesarios para identificar o utilizar el recurso. Ejemplo: acceso solo para administración.",
],
[
"url",
"URL",
"text",
"Incluye la dirección web de acceso o de documentación. Ejemplo: https://portal.empresa.es.",
],
[
"holder",
"Titular",
"text",
"Indica la persona u organización titular del certificado. Ejemplo: Empresa S. L.",
],
[
"issuer",
"Emisor",
"text",
"Indica la entidad que emitió el certificado. Ejemplo: Let's Encrypt.",
],
[
"expires",
"Caducidad",
"date",
"Indica la fecha en la que deja de ser válido. Ejemplo: 2027-06-30.",
],
[
"fingerprint",
"Huella digital",
"text",
"Copia la huella o identificador único del certificado. Ejemplo: SHA-256 A1:B2:C3:…",
],
[
"purpose",
"Finalidad",
"text",
"Explica qué servicio, proceso o equipo utiliza este recurso. Ejemplo: firma de facturas.",
],
[
"reference",
"Referencia",
"text",
"Registra el identificador, número de contrato o referencia del proveedor. Ejemplo: contrato MS-2026-014.",
],
[
"software_product",
"Producto de software",
"text",
"Indica el nombre comercial del producto licenciado. Ejemplo: Microsoft 365 Business Premium.",
],
[
"software_edition",
"Edición o plan",
"text",
"Indica la edición, nivel o plan contratado. Ejemplo: Enterprise E3.",
],
[
"license_model",
"Modalidad de licencia",
"choice",
"Selecciona cómo se adquiere o mantiene la licencia. Ejemplo: Suscripción.",
["Suscripción", "Perpetua", "OEM", "Otra"],
],
[
"license_metric",
"Criterio de licenciamiento",
"choice",
"Selecciona la unidad con la que se calcula la cobertura contratada. Ejemplo: Por usuario.",
["Por usuario", "Por dispositivo", "Por servidor", "Concurrente", "Corporativa", "Otro"],
],
[
"license_quantity",
"Cantidad contratada",
"number",
"Indica el número de licencias o unidades cubiertas. Ejemplo: 25.",
],
[
"renews",
"Fecha de renovación",
"date",
"Indica la próxima fecha prevista de renovación. Ejemplo: 2027-01-01.",
],
[
"auto_renew",
"Renovación automática",
"boolean",
"Marca si la renovación se realiza automáticamente. Ejemplo: Sí si el proveedor carga el siguiente periodo.",
],
];
const TEMPLATES: readonly [string, string, readonly string[]][] = [
["mailbox", "Buzón de correo", ["email", "provider", "context"]],
["access", "Recurso de acceso", ["reference"]],
["shared_folder", "Carpeta compartida", ["unc", "server", "description"]],
["support", "Herramienta de asistencia remota", ["provider", "url", "description"]],
["os", "Sistema operativo", ["provider", "software_edition", "description"]],
["application", "Aplicación", ["provider", "url", "description"]],
["certificate", "Certificado digital", ["holder", "issuer", "expires", "fingerprint", "purpose"]],
[
"software_license",
"Licencia de software",
[
"software_product",
"software_edition",
"provider",
"license_model",
"license_metric",
"license_quantity",
"reference",
"expires",
"renews",
"auto_renew",
"description",
],
],
];
/** Solo alimenta definiciones; no crea recursos ni migra registros anteriores. */
const SECTION_FIELDS: readonly [string, readonly string[]][] = [
["Identificación", ["holder", "issuer", "fingerprint"]],
["Producto", ["software_product", "software_edition", "provider"]],
["Contratación", ["license_model", "license_metric", "license_quantity", "reference"]],
["Vigencia y renovación", ["expires", "renews", "auto_renew"]],
["Uso y observaciones", ["purpose", "description"]],
];
export function seedResourceCatalog(catalog: ResourceCatalog) {
let current = catalog;
const add = (key: string, input: ResourceDefinitionSpec) => {
const values = ResourceDefinitionValues.create(input);
if (!values.isSuccess) {
throw new Error("Definición inicial de recursos inválida.");
}
const result = current.seed(key, values.data);
if (!result.isSuccess) {
throw new Error("Catálogo inicial de recursos inconsistente.");
}
current = result.data;
};
for (const [key, name, type, description, choices] of FIELDS) {
add(`field:${key}`, {
kind: "field",
name,
description,
type,
unit: "",
choices: [...(choices ?? [])],
});
}
for (const [key, name, fields] of TEMPLATES) {
const sectionFields = fields;
const sections = (fields.length > 3 ? SECTION_FIELDS : [])
.filter(([, keys]) => keys.some((field) => sectionFields.includes(field)))
.map(([name, keys]) => ({
id: UniqueID.generateNewID().toPrimitive(),
name,
description: "",
keys,
}));
const references = fields.map((field) => {
const id = current.definitions
.find((item) => item.stockKey.getOrUndefined() === `field:${field}`)
?.id.toPrimitive();
if (!id) {
throw new Error("Campo inicial de recursos ausente.");
}
const sectionId = sections.find((section) => section.keys.includes(field))?.id;
return { fieldId: id, required: false, ...(sectionId ? { sectionId } : {}) };
});
add(`template:${key}`, {
kind: "template",
...(key === "support" || key === "os" ? { usage: key } : {}),
name,
description: "",
fields: references,
...(sections.length
? { sections: sections.map(({ id, name, description }) => ({ id, name, description })) }
: {}),
});
}
return current;
}