-- ══════════════════════════════════════════════════════════════ -- Moraworld Imports — Migración inicial v0.2 -- Sincronizado con documentacion.html v1.1 -- ══════════════════════════════════════════════════════════════ -- CreateEnum CREATE TYPE "UserRole" AS ENUM ('SUPER_ADMIN', 'ADMIN_EMPRESA', 'OPERADOR_BODEGA', 'AGENTE_ADUANERO', 'CLIENTE', 'SOPORTE'); -- CreateEnum — 11 estados del ciclo de vida de un paquete (doc §08) 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'); -- CreateEnum — Categorías SENAE (doc §15) CREATE TYPE "SenaeCategory" AS ENUM ('REGIMEN_4X4', 'CATEGORIA_B', 'CATEGORIA_C', 'CATEGORIA_D'); -- CreateEnum — Estados de solicitud B2B (doc §13) CREATE TYPE "B2BRequestStatus" AS ENUM ('PENDIENTE', 'EN_COTIZACION', 'COTIZADO', 'ACEPTADO', 'EN_PROCESO', 'COMPLETADO', 'CANCELADO'); -- CreateEnum — Canales de notificación (doc §16) CREATE TYPE "NotificationChannel" AS ENUM ('EMAIL', 'WHATSAPP', 'SMS', 'PUSH'); -- CreateEnum CREATE TYPE "NotificationStatus" AS ENUM ('PENDIENTE', 'ENVIADO', 'FALLIDO'); -- ─── Tenant (multi-tenant) ──────────────────────────────────── 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") ); -- ─── Usuarios ───────────────────────────────────────────────── 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") ); -- ─── Refresh Tokens JWT (doc §05 Auth) ─────────────────────── CREATE TABLE "RefreshToken" ( "id" TEXT NOT NULL, "userId" TEXT NOT NULL, "token" TEXT NOT NULL, "expiresAt" TIMESTAMP(3) NOT NULL, "revokedAt" TIMESTAMP(3), "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "RefreshToken_pkey" PRIMARY KEY ("id") ); -- ─── Suite / Casillero (doc §02 y §09) ─────────────────────── -- Dirección: "150 N Day St, Suite EC-XXXXX, City of Orange, NJ 07050" 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") ); -- ─── Paquetes (doc §07 — Módulo de Tracking) ───────────────── CREATE TABLE "Package" ( "id" TEXT NOT NULL, "tenantId" TEXT NOT NULL, "userId" TEXT NOT NULL, "trackingId" TEXT NOT NULL, -- EC-YYYYMMDD-XXXXXX (doc §08) "status" "PackageStatus" NOT NULL DEFAULT 'REGISTRADO', "description" TEXT NOT NULL, "store" TEXT, "declaredValue" DECIMAL(10,2) NOT NULL, "declaredWeight" DECIMAL(8,2), -- Peso declarado por cliente (lbs) "actualWeight" DECIMAL(8,2), -- Peso real confirmado en bodega NJ "lengthCm" DECIMAL(8,2), -- Para peso volumétrico (doc §15) "widthCm" DECIMAL(8,2), "heightCm" DECIMAL(8,2), "hasDiscrepancy" BOOLEAN NOT NULL DEFAULT false, -- Discrepancia >10% (doc §10) "vendorTracking" TEXT, "productUrl" TEXT, "photos" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[], -- S3 keys de fotos "senaeCategory" "SenaeCategory", -- Categoría SENAE (doc §15) "senaeAuthNumber" TEXT, -- N.º autorización SENAE "senaeDeclarationId" TEXT, -- ID declaración en WebService "paidAt" TIMESTAMP(3), "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, CONSTRAINT "Package_pkey" PRIMARY KEY ("id") ); -- ─── Historial de estados ───────────────────────────────────── 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") ); -- ─── Pre-alertas (doc §07 — Módulo Pre-alerta) ─────────────── CREATE TABLE "PreAlert" ( "id" TEXT NOT NULL, "tenantId" TEXT NOT NULL, "userId" TEXT NOT NULL, -- Cliente que creó la pre-alerta "packageId" TEXT, "store" TEXT NOT NULL, "vendorTracking" TEXT, "description" TEXT NOT NULL, "declaredValue" DECIMAL(10,2) NOT NULL, "invoiceKey" TEXT, -- S3 key de la factura (PDF/imagen) "estimatedArrival" TIMESTAMP(3), -- Fecha estimada llegada bodega NJ "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") ); -- ─── Tarifas configurables (doc §12 + §15) ─────────────────── CREATE TABLE "Tariff" ( "id" TEXT NOT NULL, "tenantId" TEXT NOT NULL, "pricePerLb" DECIMAL(8,4) NOT NULL DEFAULT 3.50, "insurancePct" DECIMAL(5,4) NOT NULL DEFAULT 0.02, "fodinfaPct" DECIMAL(5,4) NOT NULL DEFAULT 0.005, "ivaPct" DECIMAL(5,4) NOT NULL DEFAULT 0.15, "max4x4Value" DECIMAL(10,2) NOT NULL DEFAULT 400, "max4x4WeightKg" DECIMAL(6,2) NOT NULL DEFAULT 4, "max4x4PerYear" INTEGER NOT NULL DEFAULT 4, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, CONSTRAINT "Tariff_pkey" PRIMARY KEY ("id") ); -- ─── Solicitudes B2B — Carga Pesada (doc §13) ──────────────── CREATE TABLE "B2BRequest" ( "id" TEXT NOT NULL, "tenantId" TEXT NOT NULL, "trackingId" TEXT NOT NULL, -- ID B2B propio "contactName" TEXT NOT NULL, "contactEmail" TEXT NOT NULL, "contactPhone" TEXT, "companyName" TEXT, "merchandiseType" TEXT NOT NULL, "description" TEXT NOT NULL, "estimatedWeightKg" DECIMAL(10,2), "pallets" INTEGER, "commercialValue" DECIMAL(12,2), "originCity" TEXT, "requiresInen" BOOLEAN NOT NULL DEFAULT false, -- Requiere certificación INEN "inenCertNumber" TEXT, "status" "B2BRequestStatus" NOT NULL DEFAULT 'PENDIENTE', "quotationAmount" DECIMAL(12,2), "quotationNotes" TEXT, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, CONSTRAINT "B2BRequest_pkey" PRIMARY KEY ("id") ); -- ─── Notificaciones (doc §16) ───────────────────────────────── CREATE TABLE "Notification" ( "id" TEXT NOT NULL, "packageId" TEXT, "userId" TEXT, "channel" "NotificationChannel" NOT NULL, "status" "NotificationStatus" NOT NULL DEFAULT 'PENDIENTE', "subject" TEXT, "body" TEXT NOT NULL, "sentAt" TIMESTAMP(3), "error" TEXT, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "Notification_pkey" PRIMARY KEY ("id") ); -- ─── Auditoría append-only (doc §17 — ISO 27001 A.12) ──────── CREATE TABLE "AuditLog" ( "id" TEXT NOT NULL, "tenantId" TEXT, "userId" TEXT, "action" TEXT NOT NULL, "resource" TEXT, "resourceId" TEXT, "metadata" JSONB, "ipAddress" TEXT, "userAgent" TEXT, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id") ); -- ─── Índices únicos ─────────────────────────────────────────── CREATE UNIQUE INDEX "Tenant_slug_key" ON "Tenant"("slug"); CREATE UNIQUE INDEX "RefreshToken_token_key" ON "RefreshToken"("token"); CREATE UNIQUE INDEX "Suite_userId_key" ON "Suite"("userId"); CREATE UNIQUE INDEX "Suite_tenantId_code_key" ON "Suite"("tenantId", "code"); CREATE UNIQUE INDEX "Package_trackingId_key" ON "Package"("trackingId"); CREATE UNIQUE INDEX "PreAlert_packageId_key" ON "PreAlert"("packageId"); CREATE UNIQUE INDEX "Tariff_tenantId_key" ON "Tariff"("tenantId"); CREATE UNIQUE INDEX "B2BRequest_trackingId_key" ON "B2BRequest"("trackingId"); CREATE UNIQUE INDEX "User_tenantId_email_key" ON "User"("tenantId", "email"); -- ─── Índices de búsqueda ────────────────────────────────────── CREATE INDEX "User_tenantId_idx" ON "User"("tenantId"); CREATE INDEX "RefreshToken_userId_idx" ON "RefreshToken"("userId"); CREATE INDEX "Suite_tenantId_idx" ON "Suite"("tenantId"); CREATE INDEX "Package_tenantId_status_idx" ON "Package"("tenantId", "status"); CREATE INDEX "Package_userId_idx" ON "Package"("userId"); CREATE INDEX "PackageStatusHistory_packageId_idx" ON "PackageStatusHistory"("packageId"); CREATE INDEX "PreAlert_tenantId_idx" ON "PreAlert"("tenantId"); CREATE INDEX "PreAlert_userId_idx" ON "PreAlert"("userId"); CREATE INDEX "B2BRequest_tenantId_status_idx" ON "B2BRequest"("tenantId", "status"); CREATE INDEX "Notification_packageId_idx" ON "Notification"("packageId"); CREATE INDEX "Notification_userId_idx" ON "Notification"("userId"); CREATE INDEX "AuditLog_tenantId_createdAt_idx" ON "AuditLog"("tenantId", "createdAt"); CREATE INDEX "AuditLog_userId_idx" ON "AuditLog"("userId"); -- ─── Claves foráneas ────────────────────────────────────────── ALTER TABLE "User" ADD CONSTRAINT "User_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "Suite" ADD CONSTRAINT "Suite_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "Suite" ADD CONSTRAINT "Suite_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "Package" ADD CONSTRAINT "Package_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "Package" ADD CONSTRAINT "Package_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "PackageStatusHistory" ADD CONSTRAINT "PackageStatusHistory_packageId_fkey" FOREIGN KEY ("packageId") REFERENCES "Package"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "PreAlert" ADD CONSTRAINT "PreAlert_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "PreAlert" ADD CONSTRAINT "PreAlert_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "PreAlert" ADD CONSTRAINT "PreAlert_packageId_fkey" FOREIGN KEY ("packageId") REFERENCES "Package"("id") ON DELETE SET NULL ON UPDATE CASCADE; ALTER TABLE "Tariff" ADD CONSTRAINT "Tariff_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "B2BRequest" ADD CONSTRAINT "B2BRequest_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE "Notification" ADD CONSTRAINT "Notification_packageId_fkey" FOREIGN KEY ("packageId") REFERENCES "Package"("id") ON DELETE SET NULL ON UPDATE CASCADE;