Fix: Formato de Archivo de Débito Modificado
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 4m23s

This commit is contained in:
2025-08-12 12:49:56 -03:00
parent da50c052f1
commit 038faefd35

View File

@@ -17,8 +17,8 @@ namespace GestionIntegral.Api.Services.Suscripciones
private readonly DbConnectionFactory _connectionFactory; private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<DebitoAutomaticoService> _logger; private readonly ILogger<DebitoAutomaticoService> _logger;
private const string NRO_PRESTACION = "123456"; private const string NRO_PRESTACION = "123456"; // Reemplazar por el número real
private const string ORIGEN_EMPRESA = "ELDIA"; private const string ORIGEN_EMPRESA = "EMPRESA";
public DebitoAutomaticoService( public DebitoAutomaticoService(
IFacturaRepository facturaRepository, IFacturaRepository facturaRepository,
@@ -40,9 +40,7 @@ namespace GestionIntegral.Api.Services.Suscripciones
public async Task<(string? ContenidoArchivo, string? NombreArchivo, string? Error)> GenerarArchivoPagoDirecto(int anio, int mes, int idUsuario) public async Task<(string? ContenidoArchivo, string? NombreArchivo, string? Error)> GenerarArchivoPagoDirecto(int anio, int mes, int idUsuario)
{ {
// Se define la identificación del archivo. // Este número debe ser gestionado para no repetirse. Por ahora, lo mantenemos como 1.
// Este número debe ser gestionado para no repetirse en archivos generados
// para la misma prestación y fecha.
const int identificacionArchivo = 1; const int identificacionArchivo = 1;
var periodo = $"{anio}-{mes:D2}"; var periodo = $"{anio}-{mes:D2}";
@@ -62,8 +60,6 @@ namespace GestionIntegral.Api.Services.Suscripciones
var importeTotal = facturasParaDebito.Sum(f => f.Factura.ImporteFinal); var importeTotal = facturasParaDebito.Sum(f => f.Factura.ImporteFinal);
var cantidadRegistros = facturasParaDebito.Count(); var cantidadRegistros = facturasParaDebito.Count();
// Se utiliza la variable 'identificacionArchivo' para nombrar el archivo.
var nombreArchivo = $"{NRO_PRESTACION}{fechaGeneracion:yyyyMMdd}{identificacionArchivo}.txt"; var nombreArchivo = $"{NRO_PRESTACION}{fechaGeneracion:yyyyMMdd}{identificacionArchivo}.txt";
var nuevoLote = new LoteDebito var nuevoLote = new LoteDebito
@@ -78,13 +74,11 @@ namespace GestionIntegral.Api.Services.Suscripciones
if (loteCreado == null) throw new DataException("No se pudo crear el registro del lote de débito."); if (loteCreado == null) throw new DataException("No se pudo crear el registro del lote de débito.");
var sb = new StringBuilder(); var sb = new StringBuilder();
// Se pasa la 'identificacionArchivo' al método que crea el Header.
sb.Append(CrearRegistroHeader(fechaGeneracion, importeTotal, cantidadRegistros, identificacionArchivo)); sb.Append(CrearRegistroHeader(fechaGeneracion, importeTotal, cantidadRegistros, identificacionArchivo));
foreach (var item in facturasParaDebito) foreach (var item in facturasParaDebito)
{ {
sb.Append(CrearRegistroDetalle(item.Factura, item.Suscriptor)); sb.Append(CrearRegistroDetalle(item.Factura, item.Suscriptor));
} }
// Se pasa la 'identificacionArchivo' al método que crea el Trailer.
sb.Append(CrearRegistroTrailer(fechaGeneracion, importeTotal, cantidadRegistros, identificacionArchivo)); sb.Append(CrearRegistroTrailer(fechaGeneracion, importeTotal, cantidadRegistros, identificacionArchivo));
var idsFacturas = facturasParaDebito.Select(f => f.Factura.IdFactura); var idsFacturas = facturasParaDebito.Select(f => f.Factura.IdFactura);
@@ -108,17 +102,14 @@ namespace GestionIntegral.Api.Services.Suscripciones
var facturas = await _facturaRepository.GetByPeriodoAsync(periodo); var facturas = await _facturaRepository.GetByPeriodoAsync(periodo);
var resultado = new List<(Factura, Suscriptor)>(); var resultado = new List<(Factura, Suscriptor)>();
foreach (var f in facturas.Where(fa => fa.EstadoPago == "Pendiente")) foreach (var f in facturas.Where(fa => fa.EstadoPago == "Pendiente" || fa.EstadoPago == "Pagada Parcialmente" || fa.EstadoPago == "Rechazada"))
{ {
var suscriptor = await _suscriptorRepository.GetByIdAsync(f.IdSuscriptor); var suscriptor = await _suscriptorRepository.GetByIdAsync(f.IdSuscriptor);
// Se valida que el CBU de Banelco (22 caracteres) exista antes de intentar la conversión.
if (suscriptor == null || string.IsNullOrWhiteSpace(suscriptor.CBU) || suscriptor.CBU.Length != 22) if (suscriptor == null || string.IsNullOrWhiteSpace(suscriptor.CBU) || suscriptor.CBU.Length != 22)
{ {
_logger.LogWarning("Suscriptor ID {IdSuscriptor} omitido del lote de débito por CBU inválido o ausente (se esperan 22 dígitos).", suscriptor?.IdSuscriptor); _logger.LogWarning("Suscriptor ID {IdSuscriptor} omitido del lote de débito por CBU inválido o ausente (se esperan 22 dígitos).", f.IdSuscriptor);
continue; continue;
} }
var formaPago = await _formaPagoRepository.GetByIdAsync(suscriptor.IdFormaPagoPreferida); var formaPago = await _formaPagoRepository.GetByIdAsync(suscriptor.IdFormaPagoPreferida);
if (formaPago != null && formaPago.RequiereCBU) if (formaPago != null && formaPago.RequiereCBU)
{ {
@@ -128,26 +119,13 @@ namespace GestionIntegral.Api.Services.Suscripciones
return resultado; return resultado;
} }
// Lógica de conversión de CBU.
private string ConvertirCbuBanelcoASnp(string cbu22) private string ConvertirCbuBanelcoASnp(string cbu22)
{ {
if (string.IsNullOrEmpty(cbu22) || cbu22.Length != 22) if (string.IsNullOrEmpty(cbu22) || cbu22.Length != 22) return "".PadRight(26);
{
_logger.LogError("Se intentó convertir un CBU inválido de {Length} caracteres. Se devolverá un campo vacío.", cbu22?.Length ?? 0);
// Devolver un string de 26 espacios/ceros según la preferencia del banco para campos erróneos.
return "".PadRight(26);
}
// El formato SNP de 26 se obtiene insertando un "0" al inicio y "000" después del 8vo caracter del CBU de 22.
// Formato Banelco (22): [BBBSSSSX] [T....Y]
// Posiciones: (0-7) (8-21)
// Formato SNP (26): 0[BBBSSSSX]000[T....Y]
try try
{ {
string bloque1 = cbu22.Substring(0, 8); // Contiene código de banco, sucursal y DV del bloque 1. string bloque1 = cbu22.Substring(0, 8);
string bloque2 = cbu22.Substring(8); // Contiene el resto de la cadena. string bloque2 = cbu22.Substring(8);
// Reconstruir en formato SNP de 26 dígitos según el instructivo.
return $"0{bloque1}000{bloque2}"; return $"0{bloque1}000{bloque2}";
} }
catch (Exception ex) catch (Exception ex)
@@ -157,27 +135,23 @@ namespace GestionIntegral.Api.Services.Suscripciones
} }
} }
// --- Métodos de Formateo y Mapeo --- // --- Helpers de Formateo ---
private string FormatString(string? value, int length) => (value ?? "").PadRight(length); private string FormatString(string? value, int length) => (value ?? "").PadRight(length);
private string FormatNumeric(long value, int length) => value.ToString().PadLeft(length, '0'); private string FormatNumeric(long value, int length) => value.ToString().PadLeft(length, '0');
private string FormatNumericString(string? value, int length) => (value ?? "").PadLeft(length, '0');
private string MapTipoDocumento(string tipo) => tipo.ToUpper() switch private string MapTipoDocumento(string tipo) => tipo.ToUpper() switch
{ {
"DNI" => "0096", "DNI" => "0096", "CUIT" => "0080", "CUIL" => "0086", "LE" => "0089", "LC" => "0090", _ => "0000"
"CUIT" => "0080",
"CUIL" => "0086",
"LE" => "0089",
"LC" => "0090",
_ => "0000" // Tipo no especificado o C.I. Policía Federal según anexo.
}; };
private string CrearRegistroHeader(DateTime fechaGeneracion, decimal importeTotal, int cantidadRegistros, int identificacionArchivo) private string CrearRegistroHeader(DateTime fechaGeneracion, decimal importeTotal, int cantidadRegistros, int identificacionArchivo)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("00"); // Tipo de Registro Header sb.Append("00");
sb.Append(FormatString(NRO_PRESTACION, 6)); sb.Append(FormatNumericString(NRO_PRESTACION, 6));
sb.Append("C"); // Servicio: Sistema Nacional de Pagos sb.Append("C");
sb.Append(fechaGeneracion.ToString("yyyyMMdd")); sb.Append(fechaGeneracion.ToString("yyyyMMdd"));
sb.Append(FormatString(identificacionArchivo.ToString(), 1)); // Identificación de Archivo sb.Append(FormatString(identificacionArchivo.ToString(), 1));
sb.Append(FormatString(ORIGEN_EMPRESA, 7)); sb.Append(FormatString(ORIGEN_EMPRESA, 7));
sb.Append(FormatNumeric((long)(importeTotal * 100), 14)); sb.Append(FormatNumeric((long)(importeTotal * 100), 14));
sb.Append(FormatNumeric(cantidadRegistros, 7)); sb.Append(FormatNumeric(cantidadRegistros, 7));
@@ -188,35 +162,33 @@ namespace GestionIntegral.Api.Services.Suscripciones
private string CrearRegistroDetalle(Factura factura, Suscriptor suscriptor) private string CrearRegistroDetalle(Factura factura, Suscriptor suscriptor)
{ {
// Convertimos el CBU de 22 (Banelco) a 26 (SNP) antes de usarlo.
string cbu26 = ConvertirCbuBanelcoASnp(suscriptor.CBU!); string cbu26 = ConvertirCbuBanelcoASnp(suscriptor.CBU!);
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("0370"); // Tipo de Registro Detalle (Orden de Débito) sb.Append("0370");
sb.Append(FormatString(suscriptor.IdSuscriptor.ToString(), 22)); // Identificación de Cliente sb.Append(FormatString(suscriptor.IdSuscriptor.ToString(), 22));
sb.Append(FormatString(cbu26, 26)); // CBU en formato SNP de 26 caracteres. sb.Append(cbu26);
sb.Append(FormatString($"SUSC-{factura.IdFactura}", 15)); // Referencia Unívoca de la factura. sb.Append(FormatString($"SUSC-{factura.IdFactura}", 15));
sb.Append(factura.FechaVencimiento.ToString("yyyyMMdd")); sb.Append(factura.FechaVencimiento.ToString("yyyyMMdd"));
sb.Append(FormatNumeric((long)(factura.ImporteFinal * 100), 14)); sb.Append(FormatNumeric((long)(factura.ImporteFinal * 100), 14));
sb.Append(FormatNumeric(0, 8)); // Fecha 2do Vencimiento sb.Append(FormatNumeric(0, 8)); // Fecha 2do Vencimiento
sb.Append(FormatNumeric(0, 14)); // Importe 2do Vencimiento sb.Append(FormatNumeric(0, 14)); // Importe 2do Vencimiento
sb.Append(FormatNumeric(0, 8)); // Fecha 3er Vencimiento sb.Append(FormatNumeric(0, 8)); // Fecha 3er Vencimiento
sb.Append(FormatNumeric(0, 14)); // Importe 3er Vencimiento sb.Append(FormatNumeric(0, 14)); // Importe 3er Vencimiento
sb.Append("0"); // Moneda (0 = Pesos) sb.Append("0");
sb.Append(FormatString("", 3)); // Motivo Rechazo (vacío en el envío) sb.Append(FormatString("", 3));
sb.Append(FormatString(MapTipoDocumento(suscriptor.TipoDocumento), 4)); sb.Append(FormatString(MapTipoDocumento(suscriptor.TipoDocumento), 4));
sb.Append(FormatString(suscriptor.NroDocumento, 11)); sb.Append(FormatNumericString(suscriptor.NroDocumento, 11));
sb.Append(FormatString("", 22)); // Nueva ID Cliente sb.Append(FormatString("", 22));
sb.Append(FormatString("", 26)); // Nueva CBU sb.Append(FormatString("", 26));
sb.Append(FormatNumeric(0, 14)); // Importe Mínimo sb.Append(FormatNumeric(0, 14));
sb.Append(FormatNumeric(0, 8)); // Fecha Próximo Vencimiento sb.Append(FormatNumeric(0, 8));
sb.Append(FormatString("", 22)); // Identificación Cuenta Anterior sb.Append(FormatString("", 22));
sb.Append(FormatString("", 40)); // Mensaje ATM sb.Append(FormatString("", 40));
sb.Append(FormatString($"Susc.{factura.Periodo}", 10)); // Concepto Factura sb.Append(FormatString($"Susc.{factura.Periodo}", 10));
sb.Append(FormatNumeric(0, 8)); // Fecha de Cobro sb.Append(FormatNumeric(0, 8));
sb.Append(FormatNumeric(0, 14)); // Importe Cobrado sb.Append(FormatNumeric(0, 14));
sb.Append(FormatNumeric(0, 8)); // Fecha de Acreditamiento sb.Append(FormatNumeric(0, 8));
sb.Append(FormatString("", 26)); // Libre sb.Append(FormatString("", 26));
sb.Append("\r\n"); sb.Append("\r\n");
return sb.ToString(); return sb.ToString();
} }
@@ -224,16 +196,16 @@ namespace GestionIntegral.Api.Services.Suscripciones
private string CrearRegistroTrailer(DateTime fechaGeneracion, decimal importeTotal, int cantidadRegistros, int identificacionArchivo) private string CrearRegistroTrailer(DateTime fechaGeneracion, decimal importeTotal, int cantidadRegistros, int identificacionArchivo)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("99"); // Tipo de Registro Trailer sb.Append("99");
sb.Append(FormatString(NRO_PRESTACION, 6)); sb.Append(FormatNumericString(NRO_PRESTACION, 6));
sb.Append("C"); // Servicio: Sistema Nacional de Pagos sb.Append("C");
sb.Append(fechaGeneracion.ToString("yyyyMMdd")); sb.Append(fechaGeneracion.ToString("yyyyMMdd"));
sb.Append(FormatString(identificacionArchivo.ToString(), 1)); // Identificación de Archivo sb.Append(FormatString(identificacionArchivo.ToString(), 1));
sb.Append(FormatString(ORIGEN_EMPRESA, 7)); sb.Append(FormatString(ORIGEN_EMPRESA, 7));
sb.Append(FormatNumeric((long)(importeTotal * 100), 14)); sb.Append(FormatNumeric((long)(importeTotal * 100), 14));
sb.Append(FormatNumeric(cantidadRegistros, 7)); sb.Append(FormatNumeric(cantidadRegistros, 7));
sb.Append(FormatString("", 304)); sb.Append(FormatString("", 304));
// La última línea del archivo no lleva salto de línea (\r\n). sb.Append("\r\n");
return sb.ToString(); return sb.ToString();
} }