94 lines
3.9 KiB
TypeScript
94 lines
3.9 KiB
TypeScript
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.`,
|
|
};
|
|
}
|
|
}
|