feat: add Warehouses + Integrations modules, /admin/configuracion page

- Schema v0.3: Warehouse + Integration models (db push applied)
- WarehousesModule: CRUD, set-default, multi-warehouse support
- IntegrationsModule: 26 keys across 7 groups (payment, notifications,
  customs, courier, marketplace, compliance, warehouse)
- AuthService: suite address pulled from default Warehouse in DB (env fallback)
- AuthModule: imports WarehousesModule
- Seed: creates default warehouse from WAREHOUSE_ADDRESS_* env vars
- Web: /admin/configuracion page (3 tabs: Bodegas, Integraciones, Estado APIs)
- Web: admin layout adds Configuracion nav link
- api.ts: warehouses + integrations client methods
This commit is contained in:
Lizandro Guarnizo
2026-06-01 16:49:19 -05:00
parent 03b18e7a84
commit b2b292c50a
15 changed files with 822 additions and 18 deletions
+57 -1
View File
@@ -1,4 +1,4 @@
// Moraworld Imports — Schema v0.2 (Fase 01)
// Moraworld Imports — Schema v0.3 (Fase 2 — Bodegas + Integraciones)
// Multi-tenant por tenant_id en todas las tablas de negocio
// Sincronizado con documentacion.html v1.1
@@ -90,6 +90,8 @@ model Tenant {
preAlerts PreAlert[]
tariffs Tariff[]
b2bRequests B2BRequest[]
warehouses Warehouse[]
integrations Integration[]
}
// ─── Usuarios ────────────────────────────────────────────────
@@ -344,3 +346,57 @@ model AuditLog {
@@index([tenantId, createdAt])
@@index([userId])
}
// ─── Bodegas (multi-bodega, doc §07 + §12) ───────────────────
/// Bodega física. Cada tenant puede tener varias; una es la default.
model Warehouse {
id String @id @default(cuid())
tenantId String
name String // ej: "Bodega NJ — City of Orange"
street String // ej: "150 N Day St"
city String // ej: "City of Orange"
state String // ej: "NJ"
zip String // ej: "07050"
country String @default("US")
phone String?
email String?
contactName String?
/// Horario de operación (texto libre)
schedule String?
/// Capacidad aproximada (m²)
capacityM2 Decimal? @db.Decimal(10, 2)
isDefault Boolean @default(false)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@index([tenantId])
}
// ─── Integraciones (API keys por tenant) ─────────────────────
/// Configuración de integraciones externas (API keys, endpoints, webhooks).
/// El valor se almacena en texto plano en dev; en prod debe cifrarse (AES-256).
model Integration {
id String @id @default(cuid())
tenantId String
/// Clave identificadora: ej. "whatsapp_token", "senae_endpoint", "stripe_secret"
key String
/// Valor (API key, URL, token — cifrar en prod)
value String?
/// Descripción legible del campo
label String?
/// Grupo de integración: "payment", "notifications", "customs", "courier", "warehouse"
group String?
isActive Boolean @default(false)
updatedAt DateTime @updatedAt
updatedBy String?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@unique([tenantId, key])
@@index([tenantId, group])
}
+24
View File
@@ -50,6 +50,30 @@ async function main() {
});
console.log("✓ Tarifa base configurada ($3.50/lb, 2% seguro, 0.5% FODINFA, 15% IVA)");
// ─── Bodega predeterminada (doc §07 + §12) ────────────────
const warehouseCount = await prisma.warehouse.count({ where: { tenantId: tenant.id } });
if (warehouseCount === 0) {
await prisma.warehouse.create({
data: {
tenantId: tenant.id,
name: "Bodega NJ — City of Orange",
street: process.env.WAREHOUSE_ADDRESS_STREET ?? "150 N Day St",
city: process.env.WAREHOUSE_ADDRESS_CITY ?? "City of Orange",
state: process.env.WAREHOUSE_ADDRESS_STATE ?? "NJ",
zip: process.env.WAREHOUSE_ADDRESS_ZIP ?? "07050",
country: process.env.WAREHOUSE_ADDRESS_COUNTRY ?? "US",
phone: "+1-555-0100",
contactName: "Operaciones Moraworld NJ",
schedule: "LunVie 8:0017:00 ET",
isDefault: true,
isActive: true,
},
});
console.log("✓ Bodega predeterminada creada: 150 N Day St, City of Orange, NJ 07050");
} else {
console.log("→ Bodega predeterminada ya existe");
}
// ─── Usuarios de prueba (uno por cada rol del §06) ────────
const usersToSeed: Array<{