feat: Fase 2 — SENAE DSI, verificación bodega, fotos, notificaciones, auditoría, tarifas CRUD, páginas faltantes

API:
- PackagesService: verifyPackage (discrepancia >10%), addPhotos (multer disk), generateSenaeDeclaration (DSI stub)
- PATCH /packages/:id/verify — peso real + dimensiones + detección automática de discrepancia
- POST  /packages/:id/photos — upload multipart fotos (hasta 10, 10 MB c/u)
- POST  /packages/:id/senae/declare — genera DSI, N° autorización SENAE, cambia estado DECLARACION_ADUANERA
- GET   /packages/pending-declaration — cola de paquetes VERIFICADO para agente aduanero
- AuditLogModule: GET /audit-logs con filtros (acción, recurso, fechas, paginación)
- NotificationsModule: notifyStatusChange → crea registros EMAIL/WHATSAPP/PUSH en DB (stub dispatch)
- TariffsModule: GET/PUT /tariffs — configuración de tarifas por tenant (pricePerLb, IVA, FODINFA…)
- ProductsModule: POST /products/scan — stub extracción de producto desde URL Amazon/eBay/Walmart
- StorageModule: saveFile/deleteFile con disco local (S3-ready)
- main.ts: NestExpressApplication + useStaticAssets('/uploads')
- Dirección bodega: de hardcoded a env vars (WAREHOUSE_ADDRESS_*)

Web:
- /bodega/verificacion — formulario real: peso real, dims, fotos, detección discrepancia, notificación
- /bodega/despacho — lista paquetes DECLARACION_ADUANERA → despacha a EN_TRANSITO_ECUADOR
- /bodega/declaraciones — cola SENAE: auto-detecta categoría 4×4, genera DSI por paquete
- /bodega/layout — agrega enlace Declaraciones SENAE, corrige ALLOWED roles
- /admin/tarifas — CRUD real: edita pricePerLb, IVA, FODINFA, límites 4×4; preview fórmulas
- /admin/auditoria — tabla paginada con filtros desde GET /audit-logs
- /portal/pre-alerta — tabs URL-scan / Manual, upload de factura, campos correctos
- /casillero/como-usar — timeline 9 pasos con dirección NJ y CTA
- /carga-pesada/como-funciona — flujo B2B 5 pasos, servicios incluidos
- /carga-pesada/inen — 6 categorías reguladas, callout de advertencia, proceso de asistencia
- api.ts: verify, uploadPhotos, senaeDeclare, pendingDeclaration, tariffs, auditLogs, products.scan
This commit is contained in:
Lizandro Guarnizo
2026-06-01 10:27:52 -05:00
parent 4872053fd1
commit 6e90f06f6d
33 changed files with 1712 additions and 257 deletions
@@ -0,0 +1,22 @@
import { Controller, Get, Put, Body, UseGuards } from "@nestjs/common";
import { TariffsService } from "./tariffs.service";
import { JwtAuthGuard, RolesGuard, Roles } from "../auth/guards/auth.guard";
import { CurrentUser } from "../auth/decorators/current-user.decorator";
@Controller("tariffs")
@UseGuards(JwtAuthGuard, RolesGuard)
export class TariffsController {
constructor(private svc: TariffsService) {}
@Get()
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN", "OPERADOR_BODEGA", "AGENTE_ADUANERO")
get(@CurrentUser() user: any) {
return this.svc.findByTenant(user.tenantId);
}
@Put()
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
update(@CurrentUser() user: any, @Body() dto: any) {
return this.svc.update(user.tenantId, dto);
}
}
+12
View File
@@ -0,0 +1,12 @@
import { Module } from "@nestjs/common";
import { TariffsService } from "./tariffs.service";
import { TariffsController } from "./tariffs.controller";
import { PrismaModule } from "../prisma/prisma.module";
@Module({
imports: [PrismaModule],
providers: [TariffsService],
controllers: [TariffsController],
exports: [TariffsService],
})
export class TariffsModule {}
+34
View File
@@ -0,0 +1,34 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
@Injectable()
export class TariffsService {
constructor(private prisma: PrismaService) {}
async findByTenant(tenantId: string): Promise<any> {
let tariff = await this.prisma.client.tariff.findUnique({ where: { tenantId } });
if (!tariff) {
// Auto-create default tariff if not exists
tariff = await this.prisma.client.tariff.create({
data: { tenantId },
});
}
return tariff;
}
async update(tenantId: string, dto: Partial<{
pricePerLb: number;
insurancePct: number;
fodinfaPct: number;
ivaPct: number;
max4x4Value: number;
max4x4WeightKg: number;
max4x4PerYear: number;
}>): Promise<any> {
const existing = await this.prisma.client.tariff.findUnique({ where: { tenantId } });
if (!existing) {
return this.prisma.client.tariff.create({ data: { tenantId, ...dto } });
}
return this.prisma.client.tariff.update({ where: { tenantId }, data: dto });
}
}