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(); });