Fix: Formato de Archivo de Débito Modificado
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 4m23s
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 4m23s
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user