initial push
This commit is contained in:
42094
prisma/data/municipalities.json
Normal file
42094
prisma/data/municipalities.json
Normal file
File diff suppressed because it is too large
Load Diff
250
prisma/migrations/20260213022602_init/migration.sql
Normal file
250
prisma/migrations/20260213022602_init/migration.sql
Normal file
@@ -0,0 +1,250 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PriorityLevel" AS ENUM ('LOW', 'MEDIUM', 'HIGH');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "UserRole" AS ENUM ('USER', 'ADMIN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "ContentPageType" AS ENUM ('FAQ', 'MANUAL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "OverallScoreMethod" AS ENUM ('EQUAL_ALL_MODULES', 'EQUAL_ANSWERED_MODULES', 'WEIGHTED_ANSWERED_MODULES');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"passwordHash" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"role" "UserRole" NOT NULL DEFAULT 'USER',
|
||||
"emailVerifiedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Organization" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"industry" TEXT,
|
||||
"companySize" TEXT,
|
||||
"country" TEXT,
|
||||
"primaryObjective" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DiagnosticModule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DiagnosticModule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Question" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"moduleId" TEXT NOT NULL,
|
||||
"prompt" TEXT NOT NULL,
|
||||
"helpText" TEXT,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Question_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AnswerOption" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"questionId" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"weight" INTEGER NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AnswerOption_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Response" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"questionId" TEXT NOT NULL,
|
||||
"answerOptionId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Response_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AssessmentResult" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"moduleId" TEXT,
|
||||
"overallScore" DOUBLE PRECISION,
|
||||
"moduleScore" DOUBLE PRECISION,
|
||||
"metadata" JSONB,
|
||||
"calculatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AssessmentResult_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Recommendation" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"moduleId" TEXT,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"priority" "PriorityLevel" NOT NULL DEFAULT 'MEDIUM',
|
||||
"isTemplate" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Recommendation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ContentPage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" "ContentPageType" NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isPublished" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ContentPage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ScoringConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"lowScoreThreshold" INTEGER NOT NULL DEFAULT 70,
|
||||
"overallScoreMethod" "OverallScoreMethod" NOT NULL DEFAULT 'EQUAL_ALL_MODULES',
|
||||
"moduleWeights" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ScoringConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "EmailVerificationToken" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"consumedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "EmailVerificationToken_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Organization_userId_key" ON "Organization"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DiagnosticModule_key_key" ON "DiagnosticModule"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Question_key_key" ON "Question"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Question_moduleId_sortOrder_idx" ON "Question"("moduleId", "sortOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AnswerOption_key_key" ON "AnswerOption"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AnswerOption_questionId_sortOrder_idx" ON "AnswerOption"("questionId", "sortOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Response_userId_idx" ON "Response"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Response_userId_questionId_key" ON "Response"("userId", "questionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AssessmentResult_userId_calculatedAt_idx" ON "AssessmentResult"("userId", "calculatedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AssessmentResult_moduleId_idx" ON "AssessmentResult"("moduleId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Recommendation_key_key" ON "Recommendation"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Recommendation_moduleId_idx" ON "Recommendation"("moduleId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ContentPage_slug_key" ON "ContentPage"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ContentPage_type_sortOrder_idx" ON "ContentPage"("type", "sortOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ScoringConfig_key_key" ON "ScoringConfig"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "EmailVerificationToken_token_key" ON "EmailVerificationToken"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmailVerificationToken_userId_idx" ON "EmailVerificationToken"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "EmailVerificationToken_expiresAt_idx" ON "EmailVerificationToken"("expiresAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Organization" ADD CONSTRAINT "Organization_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Question" ADD CONSTRAINT "Question_moduleId_fkey" FOREIGN KEY ("moduleId") REFERENCES "DiagnosticModule"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AnswerOption" ADD CONSTRAINT "AnswerOption_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "Question"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Response" ADD CONSTRAINT "Response_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Response" ADD CONSTRAINT "Response_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "Question"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Response" ADD CONSTRAINT "Response_answerOptionId_fkey" FOREIGN KEY ("answerOptionId") REFERENCES "AnswerOption"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AssessmentResult" ADD CONSTRAINT "AssessmentResult_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AssessmentResult" ADD CONSTRAINT "AssessmentResult_moduleId_fkey" FOREIGN KEY ("moduleId") REFERENCES "DiagnosticModule"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Recommendation" ADD CONSTRAINT "Recommendation_moduleId_fkey" FOREIGN KEY ("moduleId") REFERENCES "DiagnosticModule"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "EmailVerificationToken" ADD CONSTRAINT "EmailVerificationToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,50 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "OrganizationDocumentType" AS ENUM ('ACTA_CONSTITUTIVA');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Organization"
|
||||
ADD COLUMN "rfc" TEXT,
|
||||
ADD COLUMN "legalRepresentative" TEXT,
|
||||
ADD COLUMN "incorporationDate" TEXT,
|
||||
ADD COLUMN "deedNumber" TEXT,
|
||||
ADD COLUMN "notaryName" TEXT,
|
||||
ADD COLUMN "fiscalAddress" TEXT,
|
||||
ADD COLUMN "businessPurpose" TEXT,
|
||||
ADD COLUMN "actaExtractedData" JSONB,
|
||||
ADD COLUMN "actaUploadedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "onboardingCompletedAt" TIMESTAMP(3);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "OrganizationDocument" (
|
||||
"id" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"type" "OrganizationDocumentType" NOT NULL,
|
||||
"fileName" TEXT NOT NULL,
|
||||
"storedFileName" TEXT NOT NULL,
|
||||
"filePath" TEXT NOT NULL,
|
||||
"mimeType" TEXT NOT NULL,
|
||||
"sizeBytes" INTEGER NOT NULL,
|
||||
"checksumSha256" TEXT,
|
||||
"extractedData" JSONB,
|
||||
"extractedTextSnippet" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "OrganizationDocument_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OrganizationDocument_organizationId_type_key" ON "OrganizationDocument"("organizationId", "type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OrganizationDocument_userId_type_key" ON "OrganizationDocument"("userId", "type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "OrganizationDocument_type_idx" ON "OrganizationDocument"("type");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OrganizationDocument" ADD CONSTRAINT "OrganizationDocument_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OrganizationDocument" ADD CONSTRAINT "OrganizationDocument_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Organization"
|
||||
ADD COLUMN "stateOfIncorporation" TEXT,
|
||||
ADD COLUMN "companyType" TEXT,
|
||||
ADD COLUMN "actaLookupDictionary" JSONB;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Organization"
|
||||
ADD COLUMN "tradeName" TEXT,
|
||||
ADD COLUMN "operatingState" TEXT,
|
||||
ADD COLUMN "municipality" TEXT,
|
||||
ADD COLUMN "yearsOfOperation" TEXT,
|
||||
ADD COLUMN "annualRevenueRange" TEXT,
|
||||
ADD COLUMN "hasGovernmentContracts" BOOLEAN;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Response"
|
||||
ADD COLUMN "evidence" JSONB;
|
||||
@@ -0,0 +1,42 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "StrategicDiagnosticEvidenceSection" AS ENUM ('TECHNICAL', 'EXPERIENCE', 'ORGANIZATION', 'PUBLIC_PROCUREMENT');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Organization"
|
||||
ADD COLUMN "strategicDiagnosticData" JSONB,
|
||||
ADD COLUMN "strategicDiagnosticScores" JSONB,
|
||||
ADD COLUMN "strategicDiagnosticCompletedAt" TIMESTAMP(3);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "StrategicDiagnosticEvidenceDocument" (
|
||||
"id" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"section" "StrategicDiagnosticEvidenceSection" NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"fileName" TEXT NOT NULL,
|
||||
"storedFileName" TEXT NOT NULL,
|
||||
"filePath" TEXT NOT NULL,
|
||||
"mimeType" TEXT NOT NULL,
|
||||
"sizeBytes" INTEGER NOT NULL,
|
||||
"checksumSha256" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "StrategicDiagnosticEvidenceDocument_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StrategicDiagnosticEvidenceDocument_organizationId_section_idx" ON "StrategicDiagnosticEvidenceDocument"("organizationId", "section");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StrategicDiagnosticEvidenceDocument_userId_section_idx" ON "StrategicDiagnosticEvidenceDocument"("userId", "section");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "StrategicDiagnosticEvidenceDocument_createdAt_idx" ON "StrategicDiagnosticEvidenceDocument"("createdAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StrategicDiagnosticEvidenceDocument" ADD CONSTRAINT "StrategicDiagnosticEvidenceDocument_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "StrategicDiagnosticEvidenceDocument" ADD CONSTRAINT "StrategicDiagnosticEvidenceDocument_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,134 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LicitationSource" AS ENUM ('PNT', 'MUNICIPAL_BACKUP');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LicitationProcedureType" AS ENUM ('LICITACION_PUBLICA', 'INVITACION_RESTRINGIDA', 'ADJUDICACION_DIRECTA', 'UNKNOWN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LicitationCategory" AS ENUM ('GOODS', 'SERVICES', 'WORKS', 'MIXED', 'UNKNOWN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SyncRunStatus" AS ENUM ('SUCCESS', 'PARTIAL', 'FAILED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Municipality" (
|
||||
"id" TEXT NOT NULL,
|
||||
"stateCode" TEXT NOT NULL,
|
||||
"stateName" TEXT NOT NULL,
|
||||
"municipalityCode" TEXT NOT NULL,
|
||||
"municipalityName" TEXT NOT NULL,
|
||||
"pntSubjectId" TEXT,
|
||||
"pntEntityId" TEXT,
|
||||
"pntSectorId" TEXT,
|
||||
"pntEntryUrl" TEXT,
|
||||
"backupUrl" TEXT,
|
||||
"scrapingEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Municipality_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Licitation" (
|
||||
"id" TEXT NOT NULL,
|
||||
"municipalityId" TEXT NOT NULL,
|
||||
"source" "LicitationSource" NOT NULL,
|
||||
"sourceRecordId" TEXT NOT NULL,
|
||||
"procedureType" "LicitationProcedureType" NOT NULL DEFAULT 'UNKNOWN',
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"category" "LicitationCategory" DEFAULT 'UNKNOWN',
|
||||
"publishDate" TIMESTAMP(3),
|
||||
"eventDates" JSONB,
|
||||
"amount" DECIMAL(14,2),
|
||||
"currency" TEXT,
|
||||
"status" TEXT,
|
||||
"supplierAwarded" TEXT,
|
||||
"documents" JSONB,
|
||||
"rawSourceUrl" TEXT,
|
||||
"rawPayload" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Licitation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SyncRun" (
|
||||
"id" TEXT NOT NULL,
|
||||
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"finishedAt" TIMESTAMP(3),
|
||||
"municipalityId" TEXT,
|
||||
"source" "LicitationSource" NOT NULL,
|
||||
"status" "SyncRunStatus" NOT NULL DEFAULT 'SUCCESS',
|
||||
"stats" JSONB,
|
||||
"error" TEXT,
|
||||
|
||||
CONSTRAINT "SyncRun_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CompanyProfile" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"organizationId" TEXT,
|
||||
"locations" JSONB,
|
||||
"categoriesSupported" JSONB,
|
||||
"keywords" JSONB,
|
||||
"minAmount" DECIMAL(14,2),
|
||||
"maxAmount" DECIMAL(14,2),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "CompanyProfile_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Municipality_stateCode_municipalityCode_key" ON "Municipality"("stateCode", "municipalityCode");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Municipality_stateCode_municipalityName_idx" ON "Municipality"("stateCode", "municipalityName");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Municipality_isActive_scrapingEnabled_idx" ON "Municipality"("isActive", "scrapingEnabled");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Licitation_municipalityId_source_sourceRecordId_key" ON "Licitation"("municipalityId", "source", "sourceRecordId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Licitation_municipalityId_publishDate_idx" ON "Licitation"("municipalityId", "publishDate");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Licitation_procedureType_category_idx" ON "Licitation"("procedureType", "category");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Licitation_amount_idx" ON "Licitation"("amount");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Licitation_createdAt_idx" ON "Licitation"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SyncRun_municipalityId_startedAt_idx" ON "SyncRun"("municipalityId", "startedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SyncRun_source_status_startedAt_idx" ON "SyncRun"("source", "status", "startedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CompanyProfile_userId_key" ON "CompanyProfile"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CompanyProfile_organizationId_key" ON "CompanyProfile"("organizationId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Licitation" ADD CONSTRAINT "Licitation_municipalityId_fkey" FOREIGN KEY ("municipalityId") REFERENCES "Municipality"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SyncRun" ADD CONSTRAINT "SyncRun_municipalityId_fkey" FOREIGN KEY ("municipalityId") REFERENCES "Municipality"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CompanyProfile" ADD CONSTRAINT "CompanyProfile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CompanyProfile" ADD CONSTRAINT "CompanyProfile_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "LicitationSource" ADD VALUE IF NOT EXISTS 'MUNICIPAL_OPEN_PORTAL';
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "MunicipalOpenPortalType" AS ENUM ('GENERIC', 'SAN_PEDRO_ASPX');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Municipality"
|
||||
ADD COLUMN "openPortalUrl" TEXT,
|
||||
ADD COLUMN "openPortalType" "MunicipalOpenPortalType" NOT NULL DEFAULT 'GENERIC',
|
||||
ADD COLUMN "openSyncIntervalDays" INTEGER NOT NULL DEFAULT 7,
|
||||
ADD COLUMN "lastOpenSyncAt" TIMESTAMP(3);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Licitation"
|
||||
ADD COLUMN "tenderCode" TEXT,
|
||||
ADD COLUMN "isOpen" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "openingDate" TIMESTAMP(3),
|
||||
ADD COLUMN "closingDate" TIMESTAMP(3),
|
||||
ADD COLUMN "lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Licitation_municipalityId_isOpen_closingDate_idx" ON "Licitation"("municipalityId", "isOpen", "closingDate");
|
||||
@@ -0,0 +1,99 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WorkshopProgressStatus" AS ENUM ('NOT_STARTED', 'WATCHED', 'EVIDENCE_SUBMITTED', 'APPROVED', 'REJECTED', 'SKIPPED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WorkshopEvidenceValidationStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED', 'ERROR');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DevelopmentWorkshop" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"moduleId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"summary" TEXT NOT NULL,
|
||||
"videoUrl" TEXT NOT NULL,
|
||||
"durationMinutes" INTEGER NOT NULL DEFAULT 0,
|
||||
"evidenceRequired" TEXT NOT NULL,
|
||||
"learningObjectives" JSONB,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DevelopmentWorkshop_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DevelopmentWorkshopProgress" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workshopId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"status" "WorkshopProgressStatus" NOT NULL DEFAULT 'NOT_STARTED',
|
||||
"watchedAt" TIMESTAMP(3),
|
||||
"skippedAt" TIMESTAMP(3),
|
||||
"completedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DevelopmentWorkshopProgress_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DevelopmentWorkshopEvidence" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workshopId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"validationStatus" "WorkshopEvidenceValidationStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"validationReason" TEXT,
|
||||
"validationConfidence" DOUBLE PRECISION,
|
||||
"validatedAt" TIMESTAMP(3),
|
||||
"fileName" TEXT NOT NULL,
|
||||
"storedFileName" TEXT NOT NULL,
|
||||
"filePath" TEXT NOT NULL,
|
||||
"mimeType" TEXT NOT NULL,
|
||||
"sizeBytes" INTEGER NOT NULL,
|
||||
"checksumSha256" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DevelopmentWorkshopEvidence_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DevelopmentWorkshop_key_key" ON "DevelopmentWorkshop"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DevelopmentWorkshop_moduleId_sortOrder_idx" ON "DevelopmentWorkshop"("moduleId", "sortOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DevelopmentWorkshop_isActive_sortOrder_idx" ON "DevelopmentWorkshop"("isActive", "sortOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DevelopmentWorkshopProgress_workshopId_userId_key" ON "DevelopmentWorkshopProgress"("workshopId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DevelopmentWorkshopProgress_userId_status_idx" ON "DevelopmentWorkshopProgress"("userId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DevelopmentWorkshopProgress_workshopId_status_idx" ON "DevelopmentWorkshopProgress"("workshopId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DevelopmentWorkshopEvidence_userId_workshopId_createdAt_idx" ON "DevelopmentWorkshopEvidence"("userId", "workshopId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DevelopmentWorkshopEvidence_workshopId_validationStatus_idx" ON "DevelopmentWorkshopEvidence"("workshopId", "validationStatus");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DevelopmentWorkshop" ADD CONSTRAINT "DevelopmentWorkshop_moduleId_fkey" FOREIGN KEY ("moduleId") REFERENCES "DiagnosticModule"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DevelopmentWorkshopProgress" ADD CONSTRAINT "DevelopmentWorkshopProgress_workshopId_fkey" FOREIGN KEY ("workshopId") REFERENCES "DevelopmentWorkshop"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DevelopmentWorkshopProgress" ADD CONSTRAINT "DevelopmentWorkshopProgress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DevelopmentWorkshopEvidence" ADD CONSTRAINT "DevelopmentWorkshopEvidence_workshopId_fkey" FOREIGN KEY ("workshopId") REFERENCES "DevelopmentWorkshop"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DevelopmentWorkshopEvidence" ADD CONSTRAINT "DevelopmentWorkshopEvidence_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
477
prisma/schema.prisma
Normal file
477
prisma/schema.prisma
Normal file
@@ -0,0 +1,477 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
enum PriorityLevel {
|
||||
LOW
|
||||
MEDIUM
|
||||
HIGH
|
||||
}
|
||||
|
||||
enum UserRole {
|
||||
USER
|
||||
ADMIN
|
||||
}
|
||||
|
||||
enum ContentPageType {
|
||||
FAQ
|
||||
MANUAL
|
||||
}
|
||||
|
||||
enum OverallScoreMethod {
|
||||
EQUAL_ALL_MODULES
|
||||
EQUAL_ANSWERED_MODULES
|
||||
WEIGHTED_ANSWERED_MODULES
|
||||
}
|
||||
|
||||
enum OrganizationDocumentType {
|
||||
ACTA_CONSTITUTIVA
|
||||
}
|
||||
|
||||
enum StrategicDiagnosticEvidenceSection {
|
||||
TECHNICAL
|
||||
EXPERIENCE
|
||||
ORGANIZATION
|
||||
PUBLIC_PROCUREMENT
|
||||
}
|
||||
|
||||
enum WorkshopProgressStatus {
|
||||
NOT_STARTED
|
||||
WATCHED
|
||||
EVIDENCE_SUBMITTED
|
||||
APPROVED
|
||||
REJECTED
|
||||
SKIPPED
|
||||
}
|
||||
|
||||
enum WorkshopEvidenceValidationStatus {
|
||||
PENDING
|
||||
APPROVED
|
||||
REJECTED
|
||||
ERROR
|
||||
}
|
||||
|
||||
enum LicitationSource {
|
||||
MUNICIPAL_OPEN_PORTAL
|
||||
PNT
|
||||
MUNICIPAL_BACKUP
|
||||
}
|
||||
|
||||
enum LicitationProcedureType {
|
||||
LICITACION_PUBLICA
|
||||
INVITACION_RESTRINGIDA
|
||||
ADJUDICACION_DIRECTA
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
enum LicitationCategory {
|
||||
GOODS
|
||||
SERVICES
|
||||
WORKS
|
||||
MIXED
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
enum SyncRunStatus {
|
||||
SUCCESS
|
||||
PARTIAL
|
||||
FAILED
|
||||
}
|
||||
|
||||
enum MunicipalOpenPortalType {
|
||||
GENERIC
|
||||
SAN_PEDRO_ASPX
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
passwordHash String
|
||||
name String?
|
||||
role UserRole @default(USER)
|
||||
emailVerifiedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization?
|
||||
companyProfile CompanyProfile?
|
||||
organizationDocs OrganizationDocument[]
|
||||
strategicDiagnosticEvidenceDocs StrategicDiagnosticEvidenceDocument[]
|
||||
workshopProgresses DevelopmentWorkshopProgress[]
|
||||
workshopEvidenceDocs DevelopmentWorkshopEvidence[]
|
||||
verificationTokens EmailVerificationToken[]
|
||||
responses Response[]
|
||||
results AssessmentResult[]
|
||||
}
|
||||
|
||||
model Organization {
|
||||
id String @id @default(cuid())
|
||||
userId String @unique
|
||||
name String
|
||||
tradeName String?
|
||||
rfc String?
|
||||
legalRepresentative String?
|
||||
incorporationDate String?
|
||||
deedNumber String?
|
||||
notaryName String?
|
||||
stateOfIncorporation String?
|
||||
companyType String?
|
||||
fiscalAddress String?
|
||||
businessPurpose String?
|
||||
industry String?
|
||||
operatingState String?
|
||||
municipality String?
|
||||
companySize String?
|
||||
yearsOfOperation String?
|
||||
annualRevenueRange String?
|
||||
hasGovernmentContracts Boolean?
|
||||
country String?
|
||||
primaryObjective String?
|
||||
actaExtractedData Json?
|
||||
actaLookupDictionary Json?
|
||||
actaUploadedAt DateTime?
|
||||
onboardingCompletedAt DateTime?
|
||||
strategicDiagnosticData Json?
|
||||
strategicDiagnosticScores Json?
|
||||
strategicDiagnosticCompletedAt DateTime?
|
||||
companyProfile CompanyProfile?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
documents OrganizationDocument[]
|
||||
strategicDiagnosticEvidenceDocs StrategicDiagnosticEvidenceDocument[]
|
||||
}
|
||||
|
||||
model OrganizationDocument {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
userId String
|
||||
type OrganizationDocumentType
|
||||
fileName String
|
||||
storedFileName String
|
||||
filePath String
|
||||
mimeType String
|
||||
sizeBytes Int
|
||||
checksumSha256 String?
|
||||
extractedData Json?
|
||||
extractedTextSnippet String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([organizationId, type])
|
||||
@@unique([userId, type])
|
||||
@@index([type])
|
||||
}
|
||||
|
||||
model StrategicDiagnosticEvidenceDocument {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
userId String
|
||||
section StrategicDiagnosticEvidenceSection
|
||||
category String
|
||||
fileName String
|
||||
storedFileName String
|
||||
filePath String
|
||||
mimeType String
|
||||
sizeBytes Int
|
||||
checksumSha256 String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([organizationId, section])
|
||||
@@index([userId, section])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model DiagnosticModule {
|
||||
id String @id @default(cuid())
|
||||
key String @unique
|
||||
name String
|
||||
description String?
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
questions Question[]
|
||||
results AssessmentResult[]
|
||||
recommendations Recommendation[]
|
||||
workshops DevelopmentWorkshop[]
|
||||
}
|
||||
|
||||
model DevelopmentWorkshop {
|
||||
id String @id @default(cuid())
|
||||
key String @unique
|
||||
moduleId String
|
||||
title String
|
||||
summary String
|
||||
videoUrl String
|
||||
durationMinutes Int @default(0)
|
||||
evidenceRequired String
|
||||
learningObjectives Json?
|
||||
sortOrder Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
module DiagnosticModule @relation(fields: [moduleId], references: [id], onDelete: Cascade)
|
||||
progresses DevelopmentWorkshopProgress[]
|
||||
evidences DevelopmentWorkshopEvidence[]
|
||||
|
||||
@@index([moduleId, sortOrder])
|
||||
@@index([isActive, sortOrder])
|
||||
}
|
||||
|
||||
model DevelopmentWorkshopProgress {
|
||||
id String @id @default(cuid())
|
||||
workshopId String
|
||||
userId String
|
||||
status WorkshopProgressStatus @default(NOT_STARTED)
|
||||
watchedAt DateTime?
|
||||
skippedAt DateTime?
|
||||
completedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
workshop DevelopmentWorkshop @relation(fields: [workshopId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([workshopId, userId])
|
||||
@@index([userId, status])
|
||||
@@index([workshopId, status])
|
||||
}
|
||||
|
||||
model DevelopmentWorkshopEvidence {
|
||||
id String @id @default(cuid())
|
||||
workshopId String
|
||||
userId String
|
||||
validationStatus WorkshopEvidenceValidationStatus @default(PENDING)
|
||||
validationReason String?
|
||||
validationConfidence Float?
|
||||
validatedAt DateTime?
|
||||
fileName String
|
||||
storedFileName String
|
||||
filePath String
|
||||
mimeType String
|
||||
sizeBytes Int
|
||||
checksumSha256 String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
workshop DevelopmentWorkshop @relation(fields: [workshopId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId, workshopId, createdAt])
|
||||
@@index([workshopId, validationStatus])
|
||||
}
|
||||
|
||||
model Question {
|
||||
id String @id @default(cuid())
|
||||
key String @unique
|
||||
moduleId String
|
||||
prompt String
|
||||
helpText String?
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
module DiagnosticModule @relation(fields: [moduleId], references: [id], onDelete: Cascade)
|
||||
answerOptions AnswerOption[]
|
||||
responses Response[]
|
||||
|
||||
@@index([moduleId, sortOrder])
|
||||
}
|
||||
|
||||
model AnswerOption {
|
||||
id String @id @default(cuid())
|
||||
key String @unique
|
||||
questionId String
|
||||
label String
|
||||
weight Int
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)
|
||||
responses Response[]
|
||||
|
||||
@@index([questionId, sortOrder])
|
||||
}
|
||||
|
||||
model Response {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
questionId String
|
||||
answerOptionId String
|
||||
evidence Json?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)
|
||||
answerOption AnswerOption @relation(fields: [answerOptionId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, questionId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model AssessmentResult {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
moduleId String?
|
||||
overallScore Float?
|
||||
moduleScore Float?
|
||||
metadata Json?
|
||||
calculatedAt DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
module DiagnosticModule? @relation(fields: [moduleId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([userId, calculatedAt])
|
||||
@@index([moduleId])
|
||||
}
|
||||
|
||||
model Recommendation {
|
||||
id String @id @default(cuid())
|
||||
key String @unique
|
||||
moduleId String?
|
||||
title String
|
||||
description String
|
||||
priority PriorityLevel @default(MEDIUM)
|
||||
isTemplate Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
module DiagnosticModule? @relation(fields: [moduleId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([moduleId])
|
||||
}
|
||||
|
||||
model ContentPage {
|
||||
id String @id @default(cuid())
|
||||
type ContentPageType
|
||||
slug String @unique
|
||||
title String
|
||||
content String
|
||||
sortOrder Int @default(0)
|
||||
isPublished Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([type, sortOrder])
|
||||
}
|
||||
|
||||
model ScoringConfig {
|
||||
id String @id @default(cuid())
|
||||
key String @unique
|
||||
lowScoreThreshold Int @default(70)
|
||||
overallScoreMethod OverallScoreMethod @default(EQUAL_ALL_MODULES)
|
||||
moduleWeights Json?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model EmailVerificationToken {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
token String @unique
|
||||
expiresAt DateTime
|
||||
consumedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
model Municipality {
|
||||
id String @id @default(cuid())
|
||||
stateCode String
|
||||
stateName String
|
||||
municipalityCode String
|
||||
municipalityName String
|
||||
openPortalUrl String?
|
||||
openPortalType MunicipalOpenPortalType @default(GENERIC)
|
||||
openSyncIntervalDays Int @default(7)
|
||||
lastOpenSyncAt DateTime?
|
||||
pntSubjectId String?
|
||||
pntEntityId String?
|
||||
pntSectorId String?
|
||||
pntEntryUrl String?
|
||||
backupUrl String?
|
||||
scrapingEnabled Boolean @default(true)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
licitations Licitation[]
|
||||
syncRuns SyncRun[]
|
||||
|
||||
@@unique([stateCode, municipalityCode])
|
||||
@@index([stateCode, municipalityName])
|
||||
@@index([isActive, scrapingEnabled])
|
||||
}
|
||||
|
||||
model Licitation {
|
||||
id String @id @default(cuid())
|
||||
municipalityId String
|
||||
source LicitationSource
|
||||
sourceRecordId String
|
||||
tenderCode String?
|
||||
procedureType LicitationProcedureType @default(UNKNOWN)
|
||||
title String
|
||||
description String?
|
||||
category LicitationCategory? @default(UNKNOWN)
|
||||
isOpen Boolean @default(true)
|
||||
openingDate DateTime?
|
||||
closingDate DateTime?
|
||||
publishDate DateTime?
|
||||
eventDates Json?
|
||||
amount Decimal? @db.Decimal(14, 2)
|
||||
currency String?
|
||||
status String?
|
||||
supplierAwarded String?
|
||||
documents Json?
|
||||
rawSourceUrl String?
|
||||
rawPayload Json
|
||||
lastSeenAt DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
municipality Municipality @relation(fields: [municipalityId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([municipalityId, source, sourceRecordId])
|
||||
@@index([municipalityId, isOpen, closingDate])
|
||||
@@index([municipalityId, publishDate])
|
||||
@@index([procedureType, category])
|
||||
@@index([amount])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model SyncRun {
|
||||
id String @id @default(cuid())
|
||||
startedAt DateTime @default(now())
|
||||
finishedAt DateTime?
|
||||
municipalityId String?
|
||||
source LicitationSource
|
||||
status SyncRunStatus @default(SUCCESS)
|
||||
stats Json?
|
||||
error String?
|
||||
municipality Municipality? @relation(fields: [municipalityId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([municipalityId, startedAt])
|
||||
@@index([source, status, startedAt])
|
||||
}
|
||||
|
||||
model CompanyProfile {
|
||||
id String @id @default(cuid())
|
||||
userId String @unique
|
||||
organizationId String? @unique
|
||||
locations Json?
|
||||
categoriesSupported Json?
|
||||
keywords Json?
|
||||
minAmount Decimal? @db.Decimal(14, 2)
|
||||
maxAmount Decimal? @db.Decimal(14, 2)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: SetNull)
|
||||
}
|
||||
729
prisma/seed.mjs
Normal file
729
prisma/seed.mjs
Normal file
@@ -0,0 +1,729 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user