Files
Kontia/prisma/seed.mjs
Marcelo Dares 65aaf9275e initial push
2026-03-15 15:03:56 +01:00

730 lines
24 KiB
JavaScript

import { PrismaClient, ContentPageType, OverallScoreMethod, PriorityLevel } from "@prisma/client";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const prisma = new PrismaClient();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
function yesNoOptions(questionKey) {
return [
{ key: `${questionKey}-opt-yes`, label: "Si", weight: 5, sortOrder: 1 },
{ key: `${questionKey}-opt-no`, label: "No", weight: 0, sortOrder: 2 },
];
}
const moduleSeeds = [
{
key: "liderazgo-vision-estrategica",
name: "Liderazgo y Vision Estrategica",
description: "Capacidad de la direccion para definir y comunicar una vision clara orientada al valor publico.",
sortOrder: 1,
questions: [
{
key: "liderazgo-vision-estrategica-q1",
prompt: "La direccion tiene una vision clara del proposito de la empresa mas alla de las ganancias?",
helpText: null,
sortOrder: 1,
options: yesNoOptions("liderazgo-vision-estrategica-q1"),
},
{
key: "liderazgo-vision-estrategica-q2",
prompt: "Se comunica regularmente la estrategia empresarial a todo el equipo?",
helpText: null,
sortOrder: 2,
options: yesNoOptions("liderazgo-vision-estrategica-q2"),
},
{
key: "liderazgo-vision-estrategica-q3",
prompt: "Existen objetivos medibles alineados con la generacion de valor publico?",
helpText: null,
sortOrder: 3,
options: yesNoOptions("liderazgo-vision-estrategica-q3"),
},
{
key: "liderazgo-vision-estrategica-q4",
prompt: "La direccion participa activamente en la toma de decisiones estrategicas?",
helpText: null,
sortOrder: 4,
options: yesNoOptions("liderazgo-vision-estrategica-q4"),
},
{
key: "liderazgo-vision-estrategica-q5",
prompt: "Se revisan y ajustan los planes estrategicos al menos anualmente?",
helpText: null,
sortOrder: 5,
options: yesNoOptions("liderazgo-vision-estrategica-q5"),
},
],
},
{
key: "cultura-organizacional",
name: "Cultura Organizacional",
description: "Valores, comportamientos y mentalidad orientados a la excelencia y el impacto social.",
sortOrder: 2,
questions: [
{
key: "cultura-organizacional-q1",
prompt: "La empresa promueve valores de integridad y etica en sus operaciones?",
helpText: null,
sortOrder: 1,
options: yesNoOptions("cultura-organizacional-q1"),
},
{
key: "cultura-organizacional-q2",
prompt: "Existe un ambiente de trabajo colaborativo y respetuoso?",
helpText: null,
sortOrder: 2,
options: yesNoOptions("cultura-organizacional-q2"),
},
{
key: "cultura-organizacional-q3",
prompt: "Se fomenta la mejora continua y el aprendizaje organizacional?",
helpText: null,
sortOrder: 3,
options: yesNoOptions("cultura-organizacional-q3"),
},
{
key: "cultura-organizacional-q4",
prompt: "Los empleados conocen y practican los valores de la empresa?",
helpText: null,
sortOrder: 4,
options: yesNoOptions("cultura-organizacional-q4"),
},
{
key: "cultura-organizacional-q5",
prompt: "Se reconoce y celebra el buen desempeno del equipo?",
helpText: null,
sortOrder: 5,
options: yesNoOptions("cultura-organizacional-q5"),
},
],
},
{
key: "estructura-procesos",
name: "Estructura y Procesos",
description: "Organizacion interna, procedimientos y sistemas de gestion eficientes.",
sortOrder: 3,
questions: [
{
key: "estructura-procesos-q1",
prompt: "Existen procesos documentados para las operaciones principales?",
helpText: null,
sortOrder: 1,
options: yesNoOptions("estructura-procesos-q1"),
},
{
key: "estructura-procesos-q2",
prompt: "La empresa cuenta con un organigrama claro y funcional?",
helpText: null,
sortOrder: 2,
options: yesNoOptions("estructura-procesos-q2"),
},
{
key: "estructura-procesos-q3",
prompt: "Se utilizan herramientas digitales para la gestion del negocio?",
helpText: null,
sortOrder: 3,
options: yesNoOptions("estructura-procesos-q3"),
},
{
key: "estructura-procesos-q4",
prompt: "Existe un sistema de control de calidad en productos o servicios?",
helpText: null,
sortOrder: 4,
options: yesNoOptions("estructura-procesos-q4"),
},
{
key: "estructura-procesos-q5",
prompt: "Se llevan registros financieros y contables actualizados?",
helpText: null,
sortOrder: 5,
options: yesNoOptions("estructura-procesos-q5"),
},
],
},
{
key: "innovacion-sostenibilidad",
name: "Innovacion y Sostenibilidad",
description: "Capacidad de adaptacion, mejora continua y practicas sostenibles.",
sortOrder: 4,
questions: [
{
key: "innovacion-sostenibilidad-q1",
prompt: "La empresa invierte en innovacion de productos, servicios o procesos?",
helpText: null,
sortOrder: 1,
options: yesNoOptions("innovacion-sostenibilidad-q1"),
},
{
key: "innovacion-sostenibilidad-q2",
prompt: "Se implementan practicas de sostenibilidad ambiental?",
helpText: null,
sortOrder: 2,
options: yesNoOptions("innovacion-sostenibilidad-q2"),
},
{
key: "innovacion-sostenibilidad-q3",
prompt: "Existe disposicion para adoptar nuevas tecnologias?",
helpText: null,
sortOrder: 3,
options: yesNoOptions("innovacion-sostenibilidad-q3"),
},
{
key: "innovacion-sostenibilidad-q4",
prompt: "Se monitorean tendencias del mercado y competencia?",
helpText: null,
sortOrder: 4,
options: yesNoOptions("innovacion-sostenibilidad-q4"),
},
{
key: "innovacion-sostenibilidad-q5",
prompt: "Se busca activamente la eficiencia en el uso de recursos?",
helpText: null,
sortOrder: 5,
options: yesNoOptions("innovacion-sostenibilidad-q5"),
},
],
},
{
key: "impacto-social-equidad",
name: "Impacto Social y Equidad",
description: "Contribucion a la comunidad, inclusion y responsabilidad social.",
sortOrder: 5,
questions: [
{
key: "impacto-social-equidad-q1",
prompt: "La empresa contribuye positivamente a la comunidad local?",
helpText: null,
sortOrder: 1,
options: yesNoOptions("impacto-social-equidad-q1"),
},
{
key: "impacto-social-equidad-q2",
prompt: "Se promueve la equidad de genero en la organizacion?",
helpText: null,
sortOrder: 2,
options: yesNoOptions("impacto-social-equidad-q2"),
},
{
key: "impacto-social-equidad-q3",
prompt: "Existen politicas de inclusion para grupos vulnerables?",
helpText: null,
sortOrder: 3,
options: yesNoOptions("impacto-social-equidad-q3"),
},
{
key: "impacto-social-equidad-q4",
prompt: "Se considera el impacto social en las decisiones de negocio?",
helpText: null,
sortOrder: 4,
options: yesNoOptions("impacto-social-equidad-q4"),
},
{
key: "impacto-social-equidad-q5",
prompt: "La empresa participa en iniciativas de responsabilidad social?",
helpText: null,
sortOrder: 5,
options: yesNoOptions("impacto-social-equidad-q5"),
},
],
},
];
const recommendationSeeds = [
{
key: "rec-liderazgo-vision",
moduleKey: "liderazgo-vision-estrategica",
title: "Formalizar vision estrategica anual",
description: "Define metas anuales medibles y comunica el avance con revisiones trimestrales para todo el equipo.",
priority: PriorityLevel.HIGH,
},
{
key: "rec-cultura-integridad",
moduleKey: "cultura-organizacional",
title: "Reforzar cultura de integridad y colaboracion",
description: "Establece rutinas de reconocimiento y acciones concretas para fortalecer valores compartidos.",
priority: PriorityLevel.MEDIUM,
},
{
key: "rec-estructura-procesos",
moduleKey: "estructura-procesos",
title: "Estandarizar procesos y controles de calidad",
description: "Documenta procesos clave y asigna responsables para mantener registros operativos y financieros al dia.",
priority: PriorityLevel.HIGH,
},
{
key: "rec-innovacion-sostenibilidad",
moduleKey: "innovacion-sostenibilidad",
title: "Activar plan de innovacion sostenible",
description: "Prioriza proyectos de innovacion con impacto en eficiencia de recursos y seguimiento de tendencias del mercado.",
priority: PriorityLevel.MEDIUM,
},
{
key: "rec-impacto-social-equidad",
moduleKey: "impacto-social-equidad",
title: "Fortalecer estrategia de impacto social",
description: "Define iniciativas de inclusion y equidad con indicadores concretos y resultados verificables.",
priority: PriorityLevel.MEDIUM,
},
{
key: "rec-gobernanza-global",
moduleKey: null,
title: "Instalar comite mensual de madurez empresarial",
description: "Consolida resultados de los cinco modulos para priorizar inversiones y remover bloqueos.",
priority: PriorityLevel.LOW,
},
];
const workshopSeeds = [
{
key: "taller-liderazgo-hoja-ruta",
moduleKey: "liderazgo-vision-estrategica",
title: "Hoja de Ruta de Liderazgo Estrategico",
summary: "Alinea vision, objetivos y ritmos de seguimiento para mejorar la direccion estrategica del equipo.",
videoUrl: "https://www.youtube.com/embed/dQw4w9WgXcQ",
durationMinutes: 22,
evidenceRequired: "Acta de sesion estrategica con objetivos trimestrales y responsables asignados.",
learningObjectives: [
"Definir prioridades estrategicas medibles",
"Comunicar metas de forma transversal",
"Establecer seguimiento mensual de resultados",
],
sortOrder: 1,
},
{
key: "taller-cultura-colaborativa",
moduleKey: "cultura-organizacional",
title: "Ambiente de Trabajo Colaborativo",
summary: "Fortalece dinamicas de comunicacion y colaboracion para elevar desempeno y compromiso del equipo.",
videoUrl: "https://www.youtube.com/embed/dQw4w9WgXcQ",
durationMinutes: 18,
evidenceRequired: "Fotografia o acta de una dinamica de integracion aplicada con tu equipo.",
learningObjectives: [
"Implementar dinamicas de integracion",
"Crear canales de comunicacion efectivos",
"Manejar conflictos constructivamente",
],
sortOrder: 1,
},
{
key: "taller-procesos-auditables",
moduleKey: "estructura-procesos",
title: "Procesos Auditables y Control Operativo",
summary: "Documenta procesos clave y define controles para mejorar trazabilidad y cumplimiento.",
videoUrl: "https://www.youtube.com/embed/dQw4w9WgXcQ",
durationMinutes: 25,
evidenceRequired: "Procedimiento documentado con roles, pasos, entradas y salidas del proceso.",
learningObjectives: [
"Mapear procesos criticos",
"Definir controles de calidad",
"Estandarizar evidencias operativas",
],
sortOrder: 1,
},
{
key: "taller-innovacion-sostenible",
moduleKey: "innovacion-sostenibilidad",
title: "Innovacion Aplicada con Enfoque Sostenible",
summary: "Disena mejoras de alto impacto para reducir costos y aumentar diferenciacion competitiva.",
videoUrl: "https://www.youtube.com/embed/dQw4w9WgXcQ",
durationMinutes: 20,
evidenceRequired: "Ficha de iniciativa de innovacion con costo estimado, impacto y cronograma.",
learningObjectives: [
"Identificar oportunidades de mejora continua",
"Evaluar impacto de iniciativas sostenibles",
"Priorizar proyectos de innovacion factibles",
],
sortOrder: 1,
},
{
key: "taller-impacto-social-evidencia",
moduleKey: "impacto-social-equidad",
title: "Impacto Social y Equidad con Evidencia",
summary: "Define acciones de impacto social medibles para fortalecer tu posicion en licitaciones.",
videoUrl: "https://www.youtube.com/embed/dQw4w9WgXcQ",
durationMinutes: 24,
evidenceRequired: "Plan de impacto social con objetivos, indicadores y responsables.",
learningObjectives: [
"Disenar iniciativas inclusivas",
"Definir indicadores de impacto verificables",
"Alinear acciones sociales con estrategia comercial",
],
sortOrder: 1,
},
];
const contentPageSeeds = [
{
slug: "faq-calculo-puntaje",
type: ContentPageType.FAQ,
title: "Como se calcula el puntaje global?",
content:
"El puntaje global se obtiene de la normalizacion de respuestas por modulo y un promedio ponderado entre modulos.",
sortOrder: 1,
},
{
slug: "faq-pausa-diagnostico",
type: ContentPageType.FAQ,
title: "Puedo pausar y continuar luego?",
content:
"Si. Las respuestas quedan guardadas por usuario para retomar en el ultimo punto completado del cuestionario.",
sortOrder: 2,
},
{
slug: "manual-ruta-completa",
type: ContentPageType.MANUAL,
title: "Ruta completa de uso de la plataforma",
content:
"Registro, verificacion de correo, onboarding, diagnostico por modulos, resultados y recomendaciones accionables.",
sortOrder: 1,
},
{
slug: "manual-interpretacion-dashboard",
type: ContentPageType.MANUAL,
title: "Interpretacion de dashboard",
content:
"Use barras para avance relativo por modulo y radar para comparacion de madurez entre capacidades clave.",
sortOrder: 2,
},
];
async function upsertDiagnosticStructure() {
const moduleKeys = moduleSeeds.map((moduleSeed) => moduleSeed.key);
await prisma.diagnosticModule.deleteMany({
where: {
key: {
notIn: moduleKeys,
},
},
});
for (const moduleSeed of moduleSeeds) {
const moduleRecord = await prisma.diagnosticModule.upsert({
where: { key: moduleSeed.key },
update: {
name: moduleSeed.name,
description: moduleSeed.description,
sortOrder: moduleSeed.sortOrder,
},
create: {
key: moduleSeed.key,
name: moduleSeed.name,
description: moduleSeed.description,
sortOrder: moduleSeed.sortOrder,
},
});
const questionKeys = moduleSeed.questions.map((questionSeed) => questionSeed.key);
await prisma.question.deleteMany({
where: {
moduleId: moduleRecord.id,
key: {
notIn: questionKeys,
},
},
});
for (const questionSeed of moduleSeed.questions) {
const questionRecord = await prisma.question.upsert({
where: { key: questionSeed.key },
update: {
moduleId: moduleRecord.id,
prompt: questionSeed.prompt,
helpText: questionSeed.helpText,
sortOrder: questionSeed.sortOrder,
},
create: {
key: questionSeed.key,
moduleId: moduleRecord.id,
prompt: questionSeed.prompt,
helpText: questionSeed.helpText,
sortOrder: questionSeed.sortOrder,
},
});
const optionKeys = questionSeed.options.map((optionSeed) => optionSeed.key);
await prisma.answerOption.deleteMany({
where: {
questionId: questionRecord.id,
key: {
notIn: optionKeys,
},
},
});
for (const optionSeed of questionSeed.options) {
await prisma.answerOption.upsert({
where: { key: optionSeed.key },
update: {
questionId: questionRecord.id,
label: optionSeed.label,
weight: optionSeed.weight,
sortOrder: optionSeed.sortOrder,
},
create: {
key: optionSeed.key,
questionId: questionRecord.id,
label: optionSeed.label,
weight: optionSeed.weight,
sortOrder: optionSeed.sortOrder,
},
});
}
}
}
}
async function upsertRecommendations() {
const recommendationKeys = recommendationSeeds.map((recommendationSeed) => recommendationSeed.key);
const moduleLookup = new Map(
(await prisma.diagnosticModule.findMany({ select: { id: true, key: true } })).map((moduleRecord) => [moduleRecord.key, moduleRecord.id]),
);
await prisma.recommendation.deleteMany({
where: {
key: {
notIn: recommendationKeys,
},
},
});
for (const recommendationSeed of recommendationSeeds) {
const moduleId = recommendationSeed.moduleKey ? moduleLookup.get(recommendationSeed.moduleKey) ?? null : null;
await prisma.recommendation.upsert({
where: { key: recommendationSeed.key },
update: {
moduleId,
title: recommendationSeed.title,
description: recommendationSeed.description,
priority: recommendationSeed.priority,
isTemplate: true,
},
create: {
key: recommendationSeed.key,
moduleId,
title: recommendationSeed.title,
description: recommendationSeed.description,
priority: recommendationSeed.priority,
isTemplate: true,
},
});
}
}
async function upsertDevelopmentWorkshops() {
const workshopKeys = workshopSeeds.map((workshopSeed) => workshopSeed.key);
const moduleLookup = new Map(
(await prisma.diagnosticModule.findMany({ select: { id: true, key: true } })).map((moduleRecord) => [moduleRecord.key, moduleRecord.id]),
);
await prisma.developmentWorkshop.deleteMany({
where: {
key: {
notIn: workshopKeys,
},
},
});
for (const workshopSeed of workshopSeeds) {
const moduleId = moduleLookup.get(workshopSeed.moduleKey);
if (!moduleId) {
// Skip orphan workshop seeds when module is unavailable.
continue;
}
await prisma.developmentWorkshop.upsert({
where: { key: workshopSeed.key },
update: {
moduleId,
title: workshopSeed.title,
summary: workshopSeed.summary,
videoUrl: workshopSeed.videoUrl,
durationMinutes: workshopSeed.durationMinutes,
evidenceRequired: workshopSeed.evidenceRequired,
learningObjectives: workshopSeed.learningObjectives,
sortOrder: workshopSeed.sortOrder,
isActive: true,
},
create: {
key: workshopSeed.key,
moduleId,
title: workshopSeed.title,
summary: workshopSeed.summary,
videoUrl: workshopSeed.videoUrl,
durationMinutes: workshopSeed.durationMinutes,
evidenceRequired: workshopSeed.evidenceRequired,
learningObjectives: workshopSeed.learningObjectives,
sortOrder: workshopSeed.sortOrder,
isActive: true,
},
});
}
}
async function upsertContentPages() {
for (const pageSeed of contentPageSeeds) {
await prisma.contentPage.upsert({
where: { slug: pageSeed.slug },
update: {
type: pageSeed.type,
title: pageSeed.title,
content: pageSeed.content,
sortOrder: pageSeed.sortOrder,
isPublished: true,
},
create: {
slug: pageSeed.slug,
type: pageSeed.type,
title: pageSeed.title,
content: pageSeed.content,
sortOrder: pageSeed.sortOrder,
isPublished: true,
},
});
}
}
async function upsertDefaultScoringConfig() {
await prisma.scoringConfig.upsert({
where: { key: "default" },
update: {
lowScoreThreshold: 70,
overallScoreMethod: OverallScoreMethod.EQUAL_ALL_MODULES,
moduleWeights: {},
},
create: {
key: "default",
lowScoreThreshold: 70,
overallScoreMethod: OverallScoreMethod.EQUAL_ALL_MODULES,
moduleWeights: {},
},
});
}
async function loadMunicipalitySeeds() {
const filePath = path.join(__dirname, "data", "municipalities.json");
const content = await readFile(filePath, "utf-8");
const parsed = JSON.parse(content);
if (!Array.isArray(parsed)) {
throw new Error("Invalid municipalities seed file format.");
}
return parsed.filter((item) => {
return (
item &&
typeof item.stateCode === "string" &&
typeof item.stateName === "string" &&
typeof item.municipalityCode === "string" &&
typeof item.municipalityName === "string"
);
});
}
async function upsertMunicipalities() {
const municipalities = await loadMunicipalitySeeds();
const activeKeys = municipalities.map((item) => `${item.stateCode}-${item.municipalityCode}`);
await prisma.municipality.updateMany({
where: {
NOT: municipalities.map((item) => ({
stateCode: item.stateCode,
municipalityCode: item.municipalityCode,
})),
},
data: {
isActive: false,
},
});
for (const municipality of municipalities) {
await prisma.municipality.upsert({
where: {
stateCode_municipalityCode: {
stateCode: municipality.stateCode,
municipalityCode: municipality.municipalityCode,
},
},
update: {
stateName: municipality.stateName,
municipalityName: municipality.municipalityName,
openPortalUrl: municipality.openPortalUrl ?? null,
openPortalType: municipality.openPortalType ?? "GENERIC",
openSyncIntervalDays:
typeof municipality.openSyncIntervalDays === "number" && municipality.openSyncIntervalDays > 0
? municipality.openSyncIntervalDays
: 7,
pntSubjectId: municipality.pntSubjectId ?? null,
pntEntityId: municipality.pntEntityId ?? null,
pntSectorId: municipality.pntSectorId ?? null,
pntEntryUrl: municipality.pntEntryUrl ?? null,
backupUrl: municipality.backupUrl ?? null,
scrapingEnabled: municipality.scrapingEnabled !== false,
isActive: municipality.isActive !== false,
},
create: {
stateCode: municipality.stateCode,
stateName: municipality.stateName,
municipalityCode: municipality.municipalityCode,
municipalityName: municipality.municipalityName,
openPortalUrl: municipality.openPortalUrl ?? null,
openPortalType: municipality.openPortalType ?? "GENERIC",
openSyncIntervalDays:
typeof municipality.openSyncIntervalDays === "number" && municipality.openSyncIntervalDays > 0
? municipality.openSyncIntervalDays
: 7,
pntSubjectId: municipality.pntSubjectId ?? null,
pntEntityId: municipality.pntEntityId ?? null,
pntSectorId: municipality.pntSectorId ?? null,
pntEntryUrl: municipality.pntEntryUrl ?? null,
backupUrl: municipality.backupUrl ?? null,
scrapingEnabled: municipality.scrapingEnabled !== false,
isActive: municipality.isActive !== false,
},
});
}
return activeKeys.length;
}
async function main() {
await upsertDiagnosticStructure();
await upsertDevelopmentWorkshops();
await upsertRecommendations();
await upsertContentPages();
await upsertDefaultScoringConfig();
const municipalitySeedCount = await upsertMunicipalities();
const moduleCount = await prisma.diagnosticModule.count();
const questionCount = await prisma.question.count();
const optionCount = await prisma.answerOption.count();
const workshopCount = await prisma.developmentWorkshop.count();
const recommendationCount = await prisma.recommendation.count();
const contentPageCount = await prisma.contentPage.count();
const municipalityCount = await prisma.municipality.count({ where: { isActive: true } });
console.log("Seed completed", {
modules: moduleCount,
questions: questionCount,
answerOptions: optionCount,
workshops: workshopCount,
recommendations: recommendationCount,
contentPages: contentPageCount,
municipalities: municipalityCount,
municipalitySeedsProcessed: municipalitySeedCount,
});
}
main()
.catch((error) => {
console.error("Seed failed", error);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});