feat: C-8/C-1/C-2/C-3/C-4/C-6/M-1/M-5/M-6 — WebSocket gateway, SENAE real, SP-API, Twilio SMS, WhatsApp Business, soporte portal, HMAC audit, reportes CSV
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { SenaeService } from "./senae.service";
|
||||
import { IntegrationsModule } from "../integrations/integrations.module";
|
||||
|
||||
@Module({
|
||||
imports: [IntegrationsModule],
|
||||
providers: [SenaeService],
|
||||
exports: [SenaeService],
|
||||
})
|
||||
export class SenaeModule {}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { IntegrationsService } from "../integrations/integrations.service";
|
||||
|
||||
export interface SenaeDeclarationResult {
|
||||
authNumber: string;
|
||||
declarationId: string;
|
||||
isStub: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* SenaeService — C-1
|
||||
* Envía la Declaración Simplificada de Importación (DSI) a la SENAE.
|
||||
* Si las credenciales de integración están configuradas, hace la llamada real.
|
||||
* Si no, genera un número de stub para entorno de desarrollo.
|
||||
*
|
||||
* Docs: §11, §18 — SENAE WebService integration
|
||||
*/
|
||||
@Injectable()
|
||||
export class SenaeService {
|
||||
private readonly logger = new Logger(SenaeService.name);
|
||||
|
||||
constructor(private integrations: IntegrationsService) {}
|
||||
|
||||
async submitDSI(pkg: any, tenantId: string, category: string, agentNotes?: string): Promise<SenaeDeclarationResult> {
|
||||
const endpoint = await this.integrations.getValue(tenantId, "senae_endpoint");
|
||||
const apiKey = await this.integrations.getValue(tenantId, "senae_api_key");
|
||||
const ruc = await this.integrations.getValue(tenantId, "senae_ruc");
|
||||
const agentCode = await this.integrations.getValue(tenantId, "senae_agent_code");
|
||||
|
||||
const declarationId = `DSI-${pkg.trackingId}`;
|
||||
|
||||
// ── Real SENAE WebService call ────────────────────────────────────────────
|
||||
if (endpoint && apiKey && ruc) {
|
||||
try {
|
||||
const payload = {
|
||||
declaracion: {
|
||||
tipo: "DSI",
|
||||
rucDeclarante: ruc,
|
||||
codigoAgente: agentCode ?? null,
|
||||
trackingInterno: pkg.trackingId,
|
||||
descripcion: pkg.description,
|
||||
valorDeclarado: Number(pkg.declaredValue ?? 0),
|
||||
pesoKg: Number(pkg.actualWeight ?? pkg.declaredWeight ?? 0) * 0.453592,
|
||||
categoria: category,
|
||||
fechaEnvio: new Date().toISOString(),
|
||||
notas: agentNotes ?? null,
|
||||
},
|
||||
};
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${apiKey}`,
|
||||
"X-Agent-Code": agentCode ?? "",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text().catch(() => `HTTP ${res.status}`);
|
||||
this.logger.error(`[SENAE] DSI submit failed (${res.status}): ${errText}`);
|
||||
// Fall through to stub on error
|
||||
} else {
|
||||
const data = await res.json();
|
||||
const authNumber = data?.autorizacion ?? data?.authNumber ?? data?.numeroAutorizacion;
|
||||
if (authNumber) {
|
||||
this.logger.log(`[SENAE] DSI aprobada — Auth: ${authNumber} — Pkg: ${pkg.trackingId}`);
|
||||
return { authNumber, declarationId, isStub: false, message: `DSI aprobada por SENAE. Autorización: ${authNumber}` };
|
||||
}
|
||||
this.logger.warn(`[SENAE] Respuesta inesperada: ${JSON.stringify(data).slice(0, 200)}`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.logger.error(`[SENAE] Exception: ${err.message}`);
|
||||
// Fall through to stub
|
||||
}
|
||||
} else {
|
||||
this.logger.warn(`[SENAE] Credenciales no configuradas para tenant ${tenantId} — usando stub`);
|
||||
}
|
||||
|
||||
// ── Stub fallback (dev / sin credenciales) ────────────────────────────────
|
||||
const authNumber = `SENAE-DSI-${new Date().getFullYear()}-${Math.floor(100000 + Math.random() * 900000)}`;
|
||||
this.logger.warn(`[SENAE STUB] Auth: ${authNumber} — configure senae_endpoint, senae_api_key, senae_ruc en Integraciones`);
|
||||
return {
|
||||
authNumber,
|
||||
declarationId,
|
||||
isStub: true,
|
||||
message: `DSI generada (STUB — sin conexión real SENAE). Auth: ${authNumber}. Configure las claves SENAE en Admin → Integraciones.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user