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 { 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.`, }; } }