feat: initial commit - Moraworld Imports project

This commit is contained in:
Lizandro Guarnizo
2026-06-01 08:06:40 -05:00
commit 094ea9cf81
47 changed files with 12844 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
{
"name": "@moraworld/database",
"version": "0.1.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"generate": "prisma generate",
"migrate:dev": "prisma migrate dev",
"push": "prisma db push",
"studio": "prisma studio",
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@prisma/client": "^6.8.2"
},
"devDependencies": {
"@types/node": "^22.15.21",
"prisma": "^6.8.2",
"tsx": "^4.19.4",
"typescript": "^5.8.3"
}
}
@@ -0,0 +1,182 @@
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('SUPER_ADMIN', 'ADMIN_EMPRESA', 'OPERADOR_BODEGA', 'AGENTE_ADUANERO', 'CLIENTE', 'SOPORTE');
-- CreateEnum
CREATE TYPE "PackageStatus" AS ENUM ('REGISTRADO', 'EN_TRANSITO_BODEGA', 'RECIBIDO_BODEGA', 'EN_VERIFICACION', 'VERIFICADO', 'DECLARACION_ADUANERA', 'EN_TRANSITO_ECUADOR', 'EN_ADUANA_ECUADOR', 'LISTO_ENTREGA', 'ENTREGADO', 'INCIDENCIA');
-- CreateEnum
CREATE TYPE "PreAlertStatus" AS ENUM ('PENDIENTE', 'VINCULADA', 'CANCELADA');
-- CreateTable
CREATE TABLE "Tenant" (
"id" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Tenant_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"email" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"phone" TEXT,
"role" "UserRole" NOT NULL DEFAULT 'CLIENTE',
"mfaEnabled" BOOLEAN NOT NULL DEFAULT false,
"mfaSecret" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"lastLoginAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Suite" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"code" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Suite_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Package" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"trackingId" TEXT NOT NULL,
"status" "PackageStatus" NOT NULL DEFAULT 'REGISTRADO',
"description" TEXT NOT NULL,
"store" TEXT,
"declaredValue" DECIMAL(10,2) NOT NULL,
"declaredWeight" DECIMAL(8,2),
"actualWeight" DECIMAL(8,2),
"vendorTracking" TEXT,
"productUrl" TEXT,
"paidAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Package_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PackageStatusHistory" (
"id" TEXT NOT NULL,
"packageId" TEXT NOT NULL,
"status" "PackageStatus" NOT NULL,
"note" TEXT,
"createdBy" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PackageStatusHistory_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PreAlert" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"packageId" TEXT,
"store" TEXT NOT NULL,
"vendorTracking" TEXT,
"description" TEXT NOT NULL,
"declaredValue" DECIMAL(10,2) NOT NULL,
"invoiceKey" TEXT,
"status" "PreAlertStatus" NOT NULL DEFAULT 'PENDIENTE',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "PreAlert_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AuditLog" (
"id" TEXT NOT NULL,
"tenantId" TEXT,
"userId" TEXT,
"action" TEXT NOT NULL,
"resource" TEXT,
"resourceId" TEXT,
"metadata" JSONB,
"ipAddress" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Tenant_slug_key" ON "Tenant"("slug");
-- CreateIndex
CREATE INDEX "User_tenantId_idx" ON "User"("tenantId");
-- CreateIndex
CREATE UNIQUE INDEX "User_tenantId_email_key" ON "User"("tenantId", "email");
-- CreateIndex
CREATE UNIQUE INDEX "Suite_userId_key" ON "Suite"("userId");
-- CreateIndex
CREATE INDEX "Suite_tenantId_idx" ON "Suite"("tenantId");
-- CreateIndex
CREATE UNIQUE INDEX "Suite_tenantId_code_key" ON "Suite"("tenantId", "code");
-- CreateIndex
CREATE UNIQUE INDEX "Package_trackingId_key" ON "Package"("trackingId");
-- CreateIndex
CREATE INDEX "Package_tenantId_status_idx" ON "Package"("tenantId", "status");
-- CreateIndex
CREATE INDEX "Package_userId_idx" ON "Package"("userId");
-- CreateIndex
CREATE INDEX "PackageStatusHistory_packageId_idx" ON "PackageStatusHistory"("packageId");
-- CreateIndex
CREATE UNIQUE INDEX "PreAlert_packageId_key" ON "PreAlert"("packageId");
-- CreateIndex
CREATE INDEX "PreAlert_tenantId_idx" ON "PreAlert"("tenantId");
-- CreateIndex
CREATE INDEX "AuditLog_tenantId_createdAt_idx" ON "AuditLog"("tenantId", "createdAt");
-- CreateIndex
CREATE INDEX "AuditLog_userId_idx" ON "AuditLog"("userId");
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Suite" ADD CONSTRAINT "Suite_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Suite" ADD CONSTRAINT "Suite_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Package" ADD CONSTRAINT "Package_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Package" ADD CONSTRAINT "Package_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PackageStatusHistory" ADD CONSTRAINT "PackageStatusHistory_packageId_fkey" FOREIGN KEY ("packageId") REFERENCES "Package"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreAlert" ADD CONSTRAINT "PreAlert_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PreAlert" ADD CONSTRAINT "PreAlert_packageId_fkey" FOREIGN KEY ("packageId") REFERENCES "Package"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+179
View File
@@ -0,0 +1,179 @@
// Moraworld Imports — Schema v0 (Fase 01)
// Multi-tenant por tenant_id en todas las tablas de negocio
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ─── Enums ───────────────────────────────────────────────────
enum UserRole {
SUPER_ADMIN
ADMIN_EMPRESA
OPERADOR_BODEGA
AGENTE_ADUANERO
CLIENTE
SOPORTE
}
enum PackageStatus {
REGISTRADO
EN_TRANSITO_BODEGA
RECIBIDO_BODEGA
EN_VERIFICACION
VERIFICADO
DECLARACION_ADUANERA
EN_TRANSITO_ECUADOR
EN_ADUANA_ECUADOR
LISTO_ENTREGA
ENTREGADO
INCIDENCIA
}
enum PreAlertStatus {
PENDIENTE
VINCULADA
CANCELADA
}
// ─── Tenant (multi-tenant) ───────────────────────────────────
model Tenant {
id String @id @default(cuid())
slug String @unique
name String
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
suites Suite[]
packages Package[]
preAlerts PreAlert[]
}
// ─── Usuarios ────────────────────────────────────────────────
model User {
id String @id @default(cuid())
tenantId String
email String
passwordHash String
firstName String
lastName String
phone String?
role UserRole @default(CLIENTE)
mfaEnabled Boolean @default(false)
mfaSecret String?
isActive Boolean @default(true)
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
suite Suite?
packages Package[]
@@unique([tenantId, email])
@@index([tenantId])
}
// ─── Casillero / Suite ───────────────────────────────────────
model Suite {
id String @id @default(cuid())
tenantId String
userId String @unique
code String // ej: EC-00345
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([tenantId, code])
@@index([tenantId])
}
// ─── Paquetes ────────────────────────────────────────────────
model Package {
id String @id @default(cuid())
tenantId String
userId String
trackingId String @unique // EC-YYYYMMDD-XXXXXX
status PackageStatus @default(REGISTRADO)
description String
store String?
declaredValue Decimal @db.Decimal(10, 2)
declaredWeight Decimal? @db.Decimal(8, 2)
actualWeight Decimal? @db.Decimal(8, 2)
vendorTracking String?
productUrl String?
paidAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
statusHistory PackageStatusHistory[]
preAlert PreAlert?
@@index([tenantId, status])
@@index([userId])
}
model PackageStatusHistory {
id String @id @default(cuid())
packageId String
status PackageStatus
note String?
createdBy String?
createdAt DateTime @default(now())
package Package @relation(fields: [packageId], references: [id], onDelete: Cascade)
@@index([packageId])
}
// ─── Pre-alertas ─────────────────────────────────────────────
model PreAlert {
id String @id @default(cuid())
tenantId String
packageId String? @unique
store String
vendorTracking String?
description String
declaredValue Decimal @db.Decimal(10, 2)
invoiceKey String?
status PreAlertStatus @default(PENDIENTE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
package Package? @relation(fields: [packageId], references: [id])
@@index([tenantId])
}
// ─── Auditoría (append-only) ───────────────────────────────
model AuditLog {
id String @id @default(cuid())
tenantId String?
userId String?
action String
resource String?
resourceId String?
metadata Json?
ipAddress String?
createdAt DateTime @default(now())
@@index([tenantId, createdAt])
@@index([userId])
}
+36
View File
@@ -0,0 +1,36 @@
import { PrismaClient, UserRole } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
const tenant = await prisma.tenant.upsert({
where: { slug: "moraworld" },
update: {},
create: {
slug: "moraworld",
name: "Moraworld Imports",
},
});
console.log("✓ Tenant:", tenant.slug);
const suiteCode = "EC-00001";
const demoEmail = "demo@moraworld.test";
const existing = await prisma.user.findUnique({
where: { tenantId_email: { tenantId: tenant.id, email: demoEmail } },
});
if (!existing) {
console.log(" Usuario demo se creará en Fase 1 (auth con bcrypt)");
}
console.log("✓ Seed completado");
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());
+16
View File
@@ -0,0 +1,16 @@
export { PrismaClient } from "@prisma/client";
export * from "@prisma/client";
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"],
});
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"lib": ["ES2022"],
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}