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