initial push

This commit is contained in:
Marcelo Dares
2026-03-15 15:03:56 +01:00
parent d48b9d5352
commit 65aaf9275e
146 changed files with 70245 additions and 100 deletions

477
prisma/schema.prisma Normal file
View 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)
}