730 lines
24 KiB
JavaScript
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();
|
|
});
|