/**
 * ╔══════════════════════════════════════════════════════════════╗
 * ║   SCRIPT DE IMPORTACIÓN: Excel → Proveedores (Acreedores)  ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Crea:                                                       ║
 * ║    • personas (validando si es contribuyente via SIFEN)      ║
 * ║    • proveedores con tipo_entidad = ACREEDOR_VARIO           ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Instrucciones:                                             ║
 * ║  1. Colocar el .xlsx en scripts/data_script/                 ║
 * ║  2. Ajustar CONFIG.EMPRESA_ID (o pasar --empresa=<uuid>)     ║
 * ║  3. Dry-run: npx ts-node scripts/data_script/import-proveedores.ts --dry ║
 * ║  4. Real:    npx ts-node scripts/data_script/import-proveedores.ts       ║
 * ╚══════════════════════════════════════════════════════════════╝
 *
 * Columnas esperadas en el Excel:
 *   NOMBRE ACREEDOR | RUC
 *
 * Basado en import-clientes.ts. Los IDs de catálogo (naturaleza, tipo
 * contribuyente, país, tipo documento) se resuelven DINÁMICAMENTE por su
 * clave natural al inicio, con los UUIDs conocidos como fallback.
 */

import * as path from 'path';
import * as fs from 'fs';
import * as XLSX from 'xlsx';
import * as http from 'http';
import * as https from 'https';
import { PrismaClient } from '@prisma/client';

const httpRequest = (url: URL) => (url.protocol === 'https:' ? https.request.bind(https) : http.request.bind(http));

// ══════════════════════════════════════════════════════════════════
//  CONFIG — ajustar antes de ejecutar
// ══════════════════════════════════════════════════════════════════
const empresaArg = process.argv.find((a) => a.startsWith('--empresa='));

const CONFIG = {
  // ── Empresa destino (o pasar --empresa=<uuid>) ────────────────
  EMPRESA_ID: empresaArg ? empresaArg.split('=')[1] : '3b327470-82de-4c8b-bc6f-070f38a4f238',

  // ── Archivo Excel ─────────────────────────────────────────────
  XLSX_FILE: path.resolve(__dirname, 'LISTADO DE ACREEDORES DOBASA.xlsx'),

  // ── API mdware (consulta SIFEN) ───────────────────────────────
  MDWARE_BASE_URL: 'https://mdware.novasispy.com',
  MDWARE_RUC: '80144315', // RUC de autenticación — ajustar
  MDWARE_PASSWORD: 'ABC#80144315', // Contraseña — ajustar

  // ── Tipo de entidad del proveedor a crear ─────────────────────
  TIPO_ENTIDAD: 'ACREEDOR_VARIO', // PROVEEDOR_LOCAL | PROVEEDOR_EXTERIOR | ACREEDOR_VARIO

  // ── Fallbacks de catálogo (se intenta resolver dinámicamente primero) ──
  FALLBACK: {
    NATURALEZA_CONTRIBUYENTE_ID: '45f32973-7d88-4d47-b2ea-d63b4d448d54',
    NATURALEZA_NO_CONTRIBUYENTE_ID: '50093f82-e37d-4120-ad06-cdfdfffa7ec7',
    TIPO_CONTRIBUYENTE_JURIDICA_ID: 'b0da490e-ff65-4c8f-a5e5-823e17a67e9e',
    TIPO_CONTRIBUYENTE_FISICA_ID: 'da9fdf2b-d72c-4bb7-8689-526a7f846c83',
    PAIS_ID: '253381af-b7e6-4c2f-9e90-33be1aea687b', // PRY
    TIPO_DOCUMENTO_CI_ID: '7ce2cf6d-af16-4be2-a1da-628127a2c3c9', // Cédula paraguaya
  },
};

// Resuelto en runtime (ver resolverCatalogos)
const CAT = {
  NATURALEZA_CONTRIBUYENTE_ID: '',
  NATURALEZA_NO_CONTRIBUYENTE_ID: '',
  TIPO_CONTRIBUYENTE_JURIDICA_ID: '',
  TIPO_CONTRIBUYENTE_FISICA_ID: '',
  PAIS_ID: '',
  TIPO_DOCUMENTO_CI_ID: '',
};

// ══════════════════════════════════════════════════════════════════
//  HELPERS
// ══════════════════════════════════════════════════════════════════

/** Sanitiza celular: solo dígitos, sin prefijo país (595) ni 0 inicial, máx 20 chars */
function sanitizarCelular(v: any): string | null {
  const raw = String(v ?? '').replace(/[\s\-().+]/g, '').trim();
  if (!raw) return null;
  const sinPrefijo = raw.startsWith('595') ? raw.slice(3) : raw;
  const normalizado = sinPrefijo.startsWith('0') ? sinPrefijo.slice(1) : sinPrefijo;
  return normalizado.slice(0, 20) || null;
}

/** Sanitiza nro_documento: elimina puntos, guiones, espacios y el DV (después del '-') */
function sanitizarDoc(v: any): string {
  return String(v ?? '')
    .split('-')[0]
    .replace(/[\s.]/g, '')
    .trim();
}

/** Cálculo de dígito verificador módulo 11 (mismo algoritmo que import-clientes) */
function calcularDVModulo11(p_numero: string, p_basemax = 11): number {
  let v_total = 0;
  let k = 2;
  let v_numero_al = '';
  for (let i = 0; i < p_numero.length; i++) {
    const c = p_numero.charAt(i);
    v_numero_al += /\d/.test(c) ? c : c.charCodeAt(0);
  }
  for (let i = v_numero_al.length - 1; i >= 0; i--) {
    k = k > p_basemax ? 2 : k;
    v_total += parseInt(v_numero_al.charAt(i), 10) * k++;
  }
  const v_resto = v_total % 11;
  return v_resto > 1 ? 11 - v_resto : 0;
}

/** Lee columna case-insensitive con múltiples nombres alternativos */
function colVal(row: any, ...names: string[]): any {
  for (const name of names) {
    if (row[name] !== undefined && row[name] !== null && row[name] !== '') return row[name];
  }
  const norm = (s: string) => s.toLowerCase().replace(/\s+/g, ' ').trim();
  for (const name of names) {
    const t = norm(name);
    const k = Object.keys(row).find((k2) => norm(k2) === t);
    if (k !== undefined) return row[k];
  }
  return undefined;
}

// ══════════════════════════════════════════════════════════════════
//  LOGGER
// ══════════════════════════════════════════════════════════════════
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const LOG_FILE = path.join(__dirname, `import-proveedores-${timestamp}.log`);
const logStream = fs.createWriteStream(LOG_FILE, { flags: 'a' });

type LogLevel = 'INFO' | 'OK' | 'SKIP' | 'WARN' | 'ERROR' | 'FATAL' | 'TITLE' | 'RAW';

function log(level: LogLevel, msg: string) {
  const ts = new Date().toISOString().slice(11, 19);
  const prefix: Record<LogLevel, string> = {
    INFO: `[${ts}] INFO  `,
    OK: `[${ts}] ✓     `,
    SKIP: `[${ts}] ~     `,
    WARN: `[${ts}] WARN  `,
    ERROR: `[${ts}] ✗ ERR `,
    FATAL: `[${ts}] ✗✗FATAL`,
    TITLE: '\n',
    RAW: '',
  };
  const line = prefix[level] + msg;
  console.log(line);
  logStream.write(line + '\n');
}
const info = (m: string) => log('INFO', m);
const ok = (m: string) => log('OK', m);
const skip = (m: string) => log('SKIP', m);
const warn = (m: string) => log('WARN', m);
const err = (m: string) => log('ERROR', m);
const title = (m: string) => log('TITLE', m);
const raw = (m: string) => log('RAW', m);

// ══════════════════════════════════════════════════════════════════
//  AUTENTICACIÓN mdware
// ══════════════════════════════════════════════════════════════════
let tokenAPI: string | null = null;

async function autenticarAPI(): Promise<{ ok: boolean; motivo?: string }> {
  return new Promise((resolve) => {
    const body = JSON.stringify({ ruc: CONFIG.MDWARE_RUC, password: CONFIG.MDWARE_PASSWORD });
    const url = new URL(`${CONFIG.MDWARE_BASE_URL}/api/auth`);
    const options = {
      hostname: url.hostname,
      port: url.port || 443,
      path: url.pathname,
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
      timeout: 10_000,
    };
    const req = httpRequest(url)(options, (res) => {
      let rawB = '';
      res.on('data', (chunk) => (rawB += chunk));
      res.on('end', () => {
        const status = res.statusCode ?? 0;
        if (status === 401 || status === 403) return resolve({ ok: false, motivo: `Credenciales incorrectas (HTTP ${status})` });
        if (status < 200 || status >= 300) return resolve({ ok: false, motivo: `HTTP ${status}: ${rawB.substring(0, 120)}` });
        try {
          const data = JSON.parse(rawB) as { token?: string };
          if (data?.token) {
            tokenAPI = data.token;
            resolve({ ok: true });
          } else resolve({ ok: false, motivo: `Respuesta sin token: ${rawB.substring(0, 120)}` });
        } catch {
          resolve({ ok: false, motivo: `Respuesta no es JSON válido: ${rawB.substring(0, 80)}` });
        }
      });
    });
    req.on('error', (e) => resolve({ ok: false, motivo: `No se puede conectar: ${e.message}` }));
    req.on('timeout', () => {
      req.destroy();
      resolve({ ok: false, motivo: 'Timeout al conectar (>10s)' });
    });
    req.write(body);
    req.end();
  });
}

// ══════════════════════════════════════════════════════════════════
//  CONSULTA SIFEN via mdware
// ══════════════════════════════════════════════════════════════════
interface SifenResult {
  esContribuyente: boolean;
  razonSocialSifen?: string;
  error?: string;
}

async function consultarSifen(nroDoc: string): Promise<SifenResult> {
  return new Promise((resolve) => {
    const body = JSON.stringify({ tipOpe: '7', ruc: nroDoc });
    const url = new URL(`${CONFIG.MDWARE_BASE_URL}/api/maintenance`);
    const options = {
      hostname: url.hostname,
      port: url.port || 443,
      path: url.pathname,
      method: 'POST',
      headers: {
        Authorization: `Bearer ${tokenAPI}`,
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
      },
      timeout: 10_000,
    };
    const req = httpRequest(url)(options, (res) => {
      let rawBody = '';
      res.on('data', (chunk) => (rawBody += chunk));
      res.on('end', () => {
        try {
          const data = JSON.parse(rawBody);
          const status = data?.status ?? '(sin status)';
          const codResp = data?.response?.cod_respuesta ?? '(sin cod_respuesta)';
          const descResp = data?.response?.d_des_resp ?? data?.response?.descripcion ?? '';
          const razonSifen = data?.response?.razon_social ?? '';
          if (data?.status === 'success' && data?.response?.cod_respuesta === '0502') {
            log('INFO', `  SIFEN RUC=${nroDoc} → CONTRIBUYENTE ✓ | razon_social="${razonSifen}"`);
            resolve({ esContribuyente: true, razonSocialSifen: razonSifen });
          } else {
            log('INFO', `  SIFEN RUC=${nroDoc} → NO encontrado | status="${status}" cod="${codResp}" desc="${descResp}"`);
            resolve({ esContribuyente: false });
          }
        } catch {
          log('WARN', `  SIFEN RUC=${nroDoc} → respuesta no es JSON válido: ${rawBody.substring(0, 120)}`);
          resolve({ esContribuyente: false, error: 'Respuesta no es JSON válido' });
        }
      });
    });
    req.on('error', (e: any) => {
      log('WARN', `  SIFEN RUC=${nroDoc} → error de red: ${e.message}`);
      resolve({ esContribuyente: false, error: e.message });
    });
    req.on('timeout', () => {
      req.destroy();
      log('WARN', `  SIFEN RUC=${nroDoc} → timeout (>10s)`);
      resolve({ esContribuyente: false, error: 'Timeout' });
    });
    req.write(body);
    req.end();
  });
}

// ══════════════════════════════════════════════════════════════════
//  RESOLVER CATÁLOGOS (dinámico por clave natural, fallback a UUID)
// ══════════════════════════════════════════════════════════════════
const prisma = new PrismaClient();
const DRY_RUN = process.argv.includes('--dry');

async function resolverCatalogos() {
  const natContrib = await prisma.naturaleza_receptor.findFirst({ where: { descripcion: { equals: 'Contribuyente', mode: 'insensitive' } } });
  const natNoContrib = await prisma.naturaleza_receptor.findFirst({ where: { descripcion: { contains: 'no contribuyente', mode: 'insensitive' } } });
  const tcJuridica = await prisma.tipo_contribuyente.findFirst({ where: { descripcion: { contains: 'Jurídica', mode: 'insensitive' } } });
  const tcFisica = await prisma.tipo_contribuyente.findFirst({ where: { descripcion: { contains: 'Física', mode: 'insensitive' } } });
  const paisPY = await prisma.paises.findFirst({ where: { OR: [{ codigo: 'PRY' }, { descripcion: { equals: 'Paraguay', mode: 'insensitive' } }] } });
  const tdCI = await prisma.tipo_documento_identidad.findFirst({ where: { descripcion: { contains: 'Cédula paraguaya', mode: 'insensitive' } } });

  CAT.NATURALEZA_CONTRIBUYENTE_ID = natContrib?.id ?? CONFIG.FALLBACK.NATURALEZA_CONTRIBUYENTE_ID;
  CAT.NATURALEZA_NO_CONTRIBUYENTE_ID = natNoContrib?.id ?? CONFIG.FALLBACK.NATURALEZA_NO_CONTRIBUYENTE_ID;
  CAT.TIPO_CONTRIBUYENTE_JURIDICA_ID = tcJuridica?.id ?? CONFIG.FALLBACK.TIPO_CONTRIBUYENTE_JURIDICA_ID;
  CAT.TIPO_CONTRIBUYENTE_FISICA_ID = tcFisica?.id ?? CONFIG.FALLBACK.TIPO_CONTRIBUYENTE_FISICA_ID;
  CAT.PAIS_ID = paisPY?.id ?? CONFIG.FALLBACK.PAIS_ID;
  CAT.TIPO_DOCUMENTO_CI_ID = tdCI?.id ?? CONFIG.FALLBACK.TIPO_DOCUMENTO_CI_ID;

  info(`Catálogos → naturaleza(contrib)=${CAT.NATURALEZA_CONTRIBUYENTE_ID.slice(0, 8)} / no=${CAT.NATURALEZA_NO_CONTRIBUYENTE_ID.slice(0, 8)}`);
  info(`Catálogos → tipoContrib(jur)=${CAT.TIPO_CONTRIBUYENTE_JURIDICA_ID.slice(0, 8)} / fis=${CAT.TIPO_CONTRIBUYENTE_FISICA_ID.slice(0, 8)}`);
  info(`Catálogos → pais=${CAT.PAIS_ID.slice(0, 8)} / CI=${CAT.TIPO_DOCUMENTO_CI_ID.slice(0, 8)}`);
}

// ══════════════════════════════════════════════════════════════════
//  MAIN
// ══════════════════════════════════════════════════════════════════
async function run() {
  raw('');
  raw('╔══════════════════════════════════════════════════╗');
  raw(`║  IMPORTACIÓN PROVEEDORES → Novasis  ${DRY_RUN ? '[ DRY RUN ]  ' : '[ REAL — ESCRIBE ]'} ║`);
  raw(`║  tipo_entidad: ${CONFIG.TIPO_ENTIDAD.padEnd(35)}║`);
  raw(`║  Log: ${path.basename(LOG_FILE).padEnd(43)}║`);
  raw('╚══════════════════════════════════════════════════╝');
  raw('');

  if (!CONFIG.EMPRESA_ID) {
    raw('ERROR: Configurá CONFIG.EMPRESA_ID o pasá --empresa=<uuid>.');
    process.exit(1);
  }
  if (!CONFIG.MDWARE_RUC || !CONFIG.MDWARE_PASSWORD) {
    raw('ERROR: Configurá CONFIG.MDWARE_RUC y CONFIG.MDWARE_PASSWORD.');
    process.exit(1);
  }

  // ── Validar empresa destino ──────────────────────────────────
  const empresa = await prisma.empresas.findUnique({ where: { id: CONFIG.EMPRESA_ID } });
  if (!empresa) {
    raw(`ERROR: No existe la empresa ${CONFIG.EMPRESA_ID}.`);
    await prisma.$disconnect();
    process.exit(1);
  }
  info(`Empresa destino: ${empresa.razon_social ?? CONFIG.EMPRESA_ID}`);

  // ── Resolver catálogos ───────────────────────────────────────
  await resolverCatalogos();
  raw('');

  // ── Autenticación mdware ─────────────────────────────────────
  if (!DRY_RUN) {
    info(`Autenticando en API mdware con RUC ${CONFIG.MDWARE_RUC}...`);
    const auth = await autenticarAPI();
    if (!auth.ok) {
      raw('');
      raw('╔══════════════════════════════════════════════════════╗');
      raw('║  ✗ ERROR FATAL — No se puede continuar             ║');
      raw(`║  ${(auth.motivo ?? '').substring(0, 52).padEnd(52)}║`);
      raw('╚══════════════════════════════════════════════════════╝');
      logStream.end();
      await prisma.$disconnect();
      process.exit(1);
    }
    ok('Token obtenido — listo para procesar.');
    raw('');
  }

  // ── Leer Excel ────────────────────────────────────────────────
  if (!fs.existsSync(CONFIG.XLSX_FILE)) {
    raw(`ERROR: No se encontró el archivo: ${CONFIG.XLSX_FILE}`);
    process.exit(1);
  }
  const wb = XLSX.readFile(CONFIG.XLSX_FILE);
  const rows: any[] = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
  info(`Filas en Excel: ${rows.length}`);

  // ── Parsear y deduplicar por nro_documento (RUC) ─────────────
  interface RowParseada {
    razonSocial: string;
    nroDoc: string;
  }
  const filasParseadas: RowParseada[] = [];
  const sinDocumento: number[] = [];

  for (let i = 0; i < rows.length; i++) {
    const row = rows[i];
    const nroDocRaw = colVal(row, 'RUC', 'nro_documento', 'documento', 'cedula', 'ruc');
    const nroDoc = sanitizarDoc(nroDocRaw);
    if (!nroDoc) {
      sinDocumento.push(i + 2);
      warn(`Fila ${i + 2}: sin RUC — omitida`);
      continue;
    }
    filasParseadas.push({
      razonSocial:
        String(colVal(row, 'NOMBRE ACREEDOR', 'nombre acreedor', 'razon_social', 'razon social', 'nombre', 'name') ?? '').trim() ||
        `Acreedor ${nroDoc}`,
      nroDoc,
    });
  }

  const vistosDocs = new Map<string, RowParseada>();
  for (const fp of filasParseadas) {
    if (!vistosDocs.has(fp.nroDoc)) vistosDocs.set(fp.nroDoc, fp);
    else warn(`Documento duplicado en Excel: ${fp.nroDoc} (${fp.razonSocial}) — se usa el primero`);
  }

  info(`Filas con documento   : ${filasParseadas.length}`);
  info(`Documentos únicos     : ${vistosDocs.size}`);
  info(`Sin documento (skip)  : ${sinDocumento.length}`);
  raw('');

  // ══════════════════════════════════════════════════════════════
  //  BUCLE PRINCIPAL
  // ══════════════════════════════════════════════════════════════
  title('━━━ PROCESANDO PROVEEDORES (ACREEDORES) ━━━');

  let creados = 0;
  let existentes = 0;
  let errores = 0;
  let sifenContribuyentes = 0;
  let sifenNoContribuyentes = 0;
  let sifenErrores = 0;
  const errList: string[] = [];
  const entries = [...vistosDocs.entries()];

  for (let i = 0; i < entries.length; i++) {
    const [nroDoc, fp] = entries[i];
    const progreso = `[${String(i + 1).padStart(4)}/${entries.length}]`;

    // ── Consultar SIFEN ────────────────────────────────────────
    let sifen: SifenResult;
    if (DRY_RUN) sifen = { esContribuyente: false };
    else sifen = await consultarSifen(nroDoc);

    if (sifen.error) {
      sifenErrores++;
      warn(`${progreso} SIFEN error para ${nroDoc}: ${sifen.error} → tratado como no contribuyente`);
    } else if (sifen.esContribuyente) sifenContribuyentes++;
    else sifenNoContribuyentes++;

    // ── Construir datos de persona ─────────────────────────────
    let personaData: {
      naturaleza_id: string;
      tipo_contribuyente_id: string | null;
      tipo_documento_id: string | null;
      ruc: string | null;
      dv: number | null;
      nro_documento: string | null;
      pais_id: string;
    };

    if (sifen.esContribuyente) {
      const esJuridica = parseInt(nroDoc, 10) > 80_000_000;
      personaData = {
        naturaleza_id: CAT.NATURALEZA_CONTRIBUYENTE_ID,
        tipo_contribuyente_id: esJuridica ? CAT.TIPO_CONTRIBUYENTE_JURIDICA_ID : CAT.TIPO_CONTRIBUYENTE_FISICA_ID,
        tipo_documento_id: null,
        ruc: nroDoc,
        dv: calcularDVModulo11(nroDoc),
        nro_documento: null,
        pais_id: CAT.PAIS_ID,
      };
    } else {
      personaData = {
        naturaleza_id: CAT.NATURALEZA_NO_CONTRIBUYENTE_ID,
        tipo_contribuyente_id: null,
        tipo_documento_id: CAT.TIPO_DOCUMENTO_CI_ID,
        ruc: null,
        dv: null,
        nro_documento: nroDoc,
        pais_id: CAT.PAIS_ID,
      };
    }

    const tag = sifen.esContribuyente ? 'CONTRIB' : 'CI/DOC';
    const dvStr = personaData.dv !== null ? `-${personaData.dv}` : '';
    info(`${progreso} ${tag} | ${fp.razonSocial.substring(0, 35).padEnd(35)} | doc: ${nroDoc}${dvStr}`);

    if (DRY_RUN) continue;

    try {
      // ── Buscar/crear persona ───────────────────────────────────
      const wherePersona = sifen.esContribuyente
        ? { empresa_id: CONFIG.EMPRESA_ID, ruc: nroDoc, deleted: false }
        : { empresa_id: CONFIG.EMPRESA_ID, nro_documento: nroDoc, deleted: false };

      let persona = await prisma.personas.findFirst({ where: wherePersona });
      if (!persona) {
        persona = await prisma.personas.create({
          data: {
            empresa_id: CONFIG.EMPRESA_ID,
            razon_social: fp.razonSocial,
            naturaleza_id: personaData.naturaleza_id,
            tipo_contribuyente_id: personaData.tipo_contribuyente_id,
            tipo_documento_id: personaData.tipo_documento_id,
            ruc: personaData.ruc,
            dv: personaData.dv !== null ? String(personaData.dv) : null,
            nro_documento: personaData.nro_documento,
            pais_id: personaData.pais_id,
            active: true,
            deleted: false,
          },
        });
      }

      // ── Buscar/crear proveedor ─────────────────────────────────
      let proveedor = await prisma.proveedores.findFirst({
        where: { empresa_id: CONFIG.EMPRESA_ID, persona_id: persona.id },
      });

      if (!proveedor) {
        proveedor = await prisma.proveedores.create({
          data: {
            empresa_id: CONFIG.EMPRESA_ID,
            persona_id: persona.id,
            tipo_entidad: CONFIG.TIPO_ENTIDAD,
            es_extranjero: false,
            activo: true,
          },
        });
        creados++;
        ok(`Creado  | ${fp.razonSocial.substring(0, 35).padEnd(35)} | proveedor_id: ${proveedor.id}`);
      } else {
        existentes++;
        skip(`Existe  | ${fp.razonSocial.substring(0, 35).padEnd(35)} | proveedor_id: ${proveedor.id}`);
      }
    } catch (e: any) {
      errores++;
      const msg = `doc: ${nroDoc} | ${fp.razonSocial} | ERROR: ${e.message}`;
      errList.push(msg);
      err(msg);
    }
  }

  // ══════════════════════════════════════════════════════════════
  //  RESUMEN FINAL
  // ══════════════════════════════════════════════════════════════
  raw('');
  raw('╔══════════════════════════════════════════════════════╗');
  raw('║                   RESUMEN FINAL                      ║');
  raw('╠══════════════════════════════════════════════════════╣');
  raw(`║  Filas en Excel                 : ${String(rows.length).padStart(6)}               ║`);
  raw(`║  Sin documento (omitidas)       : ${String(sinDocumento.length).padStart(6)}               ║`);
  raw(`║  Documentos únicos a procesar   : ${String(vistosDocs.size).padStart(6)}               ║`);
  raw('║──────────────────────────────────────────────────────║');
  raw(`║  SIFEN → Contribuyentes         : ${String(sifenContribuyentes).padStart(6)}               ║`);
  raw(`║  SIFEN → No contribuyentes      : ${String(sifenNoContribuyentes).padStart(6)}               ║`);
  raw(`║  SIFEN → Errores de conexión    : ${String(sifenErrores).padStart(6)}               ║`);
  raw('║──────────────────────────────────────────────────────║');
  raw(`║  Proveedores creados            : ${String(creados).padStart(6)}               ║`);
  raw(`║  Proveedores ya existentes      : ${String(existentes).padStart(6)}               ║`);
  raw(`║  Errores de BD                  : ${String(errores).padStart(6)}               ║`);
  raw('╚══════════════════════════════════════════════════════╝');

  if (errList.length > 0) {
    raw('');
    raw('── ERRORES ───────────────────────────────────────────');
    errList.forEach((e, i) => raw(`  [${String(i + 1).padStart(3)}] ${e}`));
  }

  raw('');
  if (DRY_RUN) {
    raw('⚠  DRY RUN — No se escribió nada en la base de datos.');
    raw('   Ejecutar sin --dry para aplicar la importación real.');
  } else {
    raw(
      errores === 0
        ? '✓  Importación completada sin errores.'
        : `⚠  Importación completada con ${errores} error(es). Revisar sección ERRORES arriba.`,
    );
  }
  raw(`\nLog guardado en: ${LOG_FILE}\n`);

  logStream.end();
  await prisma.$disconnect();
}

run().catch(async (e) => {
  log('FATAL', `Error fatal no capturado: ${e.message}\n${e.stack}`);
  logStream.end();
  await prisma.$disconnect();
  process.exit(1);
});
