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 ILogger<DebitoAutomaticoService> _logger;
|
||||
|
||||
private const string NRO_PRESTACION = "123456";
|
||||
private const string ORIGEN_EMPRESA = "ELDIA";
|
||||
private const string NRO_PRESTACION = "123456"; // Reemplazar por el número real
|
||||
private const string ORIGEN_EMPRESA = "EMPRESA";
|
||||
|
||||
public DebitoAutomaticoService(
|
||||
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)
|
||||
{
|
||||
// Se define la identificación del archivo.
|
||||
// Este número debe ser gestionado para no repetirse en archivos generados
|
||||
// para la misma prestación y fecha.
|
||||
// Este número debe ser gestionado para no repetirse. Por ahora, lo mantenemos como 1.
|
||||
const int identificacionArchivo = 1;
|
||||
|
||||
var periodo = $"{anio}-{mes:D2}";
|
||||
@@ -62,8 +60,6 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
|
||||
var importeTotal = facturasParaDebito.Sum(f => f.Factura.ImporteFinal);
|
||||
var cantidadRegistros = facturasParaDebito.Count();
|
||||
|
||||
// Se utiliza la variable 'identificacionArchivo' para nombrar el archivo.
|
||||
var nombreArchivo = $"{NRO_PRESTACION}{fechaGeneracion:yyyyMMdd}{identificacionArchivo}.txt";
|
||||
|
||||
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.");
|
||||
|
||||
var sb = new StringBuilder();
|
||||
// Se pasa la 'identificacionArchivo' al método que crea el Header.
|
||||
sb.Append(CrearRegistroHeader(fechaGeneracion, importeTotal, cantidadRegistros, identificacionArchivo));
|
||||
foreach (var item in facturasParaDebito)
|
||||
{
|
||||
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));
|
||||
|
||||
var idsFacturas = facturasParaDebito.Select(f => f.Factura.IdFactura);
|
||||
@@ -108,17 +102,14 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
var facturas = await _facturaRepository.GetByPeriodoAsync(periodo);
|
||||
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);
|
||||
|
||||
// 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)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
|
||||
var formaPago = await _formaPagoRepository.GetByIdAsync(suscriptor.IdFormaPagoPreferida);
|
||||
if (formaPago != null && formaPago.RequiereCBU)
|
||||
{
|
||||
@@ -128,26 +119,13 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
return resultado;
|
||||
}
|
||||
|
||||
// Lógica de conversión de CBU.
|
||||
private string ConvertirCbuBanelcoASnp(string cbu22)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cbu22) || cbu22.Length != 22)
|
||||
{
|
||||
_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]
|
||||
if (string.IsNullOrEmpty(cbu22) || cbu22.Length != 22) return "".PadRight(26);
|
||||
try
|
||||
{
|
||||
string bloque1 = cbu22.Substring(0, 8); // Contiene código de banco, sucursal y DV del bloque 1.
|
||||
string bloque2 = cbu22.Substring(8); // Contiene el resto de la cadena.
|
||||
|
||||
// Reconstruir en formato SNP de 26 dígitos según el instructivo.
|
||||
string bloque1 = cbu22.Substring(0, 8);
|
||||
string bloque2 = cbu22.Substring(8);
|
||||
return $"0{bloque1}000{bloque2}";
|
||||
}
|
||||
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 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
|
||||
{
|
||||
"DNI" => "0096",
|
||||
"CUIT" => "0080",
|
||||
"CUIL" => "0086",
|
||||
"LE" => "0089",
|
||||
"LC" => "0090",
|
||||
_ => "0000" // Tipo no especificado o C.I. Policía Federal según anexo.
|
||||
"DNI" => "0096", "CUIT" => "0080", "CUIL" => "0086", "LE" => "0089", "LC" => "0090", _ => "0000"
|
||||
};
|
||||
|
||||
private string CrearRegistroHeader(DateTime fechaGeneracion, decimal importeTotal, int cantidadRegistros, int identificacionArchivo)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("00"); // Tipo de Registro Header
|
||||
sb.Append(FormatString(NRO_PRESTACION, 6));
|
||||
sb.Append("C"); // Servicio: Sistema Nacional de Pagos
|
||||
sb.Append("00");
|
||||
sb.Append(FormatNumericString(NRO_PRESTACION, 6));
|
||||
sb.Append("C");
|
||||
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(FormatNumeric((long)(importeTotal * 100), 14));
|
||||
sb.Append(FormatNumeric(cantidadRegistros, 7));
|
||||
@@ -188,35 +162,33 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
|
||||
private string CrearRegistroDetalle(Factura factura, Suscriptor suscriptor)
|
||||
{
|
||||
// Convertimos el CBU de 22 (Banelco) a 26 (SNP) antes de usarlo.
|
||||
string cbu26 = ConvertirCbuBanelcoASnp(suscriptor.CBU!);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("0370"); // Tipo de Registro Detalle (Orden de Débito)
|
||||
sb.Append(FormatString(suscriptor.IdSuscriptor.ToString(), 22)); // Identificación de Cliente
|
||||
sb.Append(FormatString(cbu26, 26)); // CBU en formato SNP de 26 caracteres.
|
||||
sb.Append(FormatString($"SUSC-{factura.IdFactura}", 15)); // Referencia Unívoca de la factura.
|
||||
sb.Append("0370");
|
||||
sb.Append(FormatString(suscriptor.IdSuscriptor.ToString(), 22));
|
||||
sb.Append(cbu26);
|
||||
sb.Append(FormatString($"SUSC-{factura.IdFactura}", 15));
|
||||
sb.Append(factura.FechaVencimiento.ToString("yyyyMMdd"));
|
||||
sb.Append(FormatNumeric((long)(factura.ImporteFinal * 100), 14));
|
||||
sb.Append(FormatNumeric(0, 8)); // Fecha 2do Vencimiento
|
||||
sb.Append(FormatNumeric(0, 14)); // Importe 2do Vencimiento
|
||||
sb.Append(FormatNumeric(0, 8)); // Fecha 3er Vencimiento
|
||||
sb.Append(FormatNumeric(0, 14)); // Importe 3er Vencimiento
|
||||
sb.Append("0"); // Moneda (0 = Pesos)
|
||||
sb.Append(FormatString("", 3)); // Motivo Rechazo (vacío en el envío)
|
||||
sb.Append("0");
|
||||
sb.Append(FormatString("", 3));
|
||||
sb.Append(FormatString(MapTipoDocumento(suscriptor.TipoDocumento), 4));
|
||||
sb.Append(FormatString(suscriptor.NroDocumento, 11));
|
||||
sb.Append(FormatString("", 22)); // Nueva ID Cliente
|
||||
sb.Append(FormatString("", 26)); // Nueva CBU
|
||||
sb.Append(FormatNumeric(0, 14)); // Importe Mínimo
|
||||
sb.Append(FormatNumeric(0, 8)); // Fecha Próximo Vencimiento
|
||||
sb.Append(FormatString("", 22)); // Identificación Cuenta Anterior
|
||||
sb.Append(FormatString("", 40)); // Mensaje ATM
|
||||
sb.Append(FormatString($"Susc.{factura.Periodo}", 10)); // Concepto Factura
|
||||
sb.Append(FormatNumeric(0, 8)); // Fecha de Cobro
|
||||
sb.Append(FormatNumeric(0, 14)); // Importe Cobrado
|
||||
sb.Append(FormatNumeric(0, 8)); // Fecha de Acreditamiento
|
||||
sb.Append(FormatString("", 26)); // Libre
|
||||
sb.Append(FormatNumericString(suscriptor.NroDocumento, 11));
|
||||
sb.Append(FormatString("", 22));
|
||||
sb.Append(FormatString("", 26));
|
||||
sb.Append(FormatNumeric(0, 14));
|
||||
sb.Append(FormatNumeric(0, 8));
|
||||
sb.Append(FormatString("", 22));
|
||||
sb.Append(FormatString("", 40));
|
||||
sb.Append(FormatString($"Susc.{factura.Periodo}", 10));
|
||||
sb.Append(FormatNumeric(0, 8));
|
||||
sb.Append(FormatNumeric(0, 14));
|
||||
sb.Append(FormatNumeric(0, 8));
|
||||
sb.Append(FormatString("", 26));
|
||||
sb.Append("\r\n");
|
||||
return sb.ToString();
|
||||
}
|
||||
@@ -224,16 +196,16 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
private string CrearRegistroTrailer(DateTime fechaGeneracion, decimal importeTotal, int cantidadRegistros, int identificacionArchivo)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("99"); // Tipo de Registro Trailer
|
||||
sb.Append(FormatString(NRO_PRESTACION, 6));
|
||||
sb.Append("C"); // Servicio: Sistema Nacional de Pagos
|
||||
sb.Append("99");
|
||||
sb.Append(FormatNumericString(NRO_PRESTACION, 6));
|
||||
sb.Append("C");
|
||||
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(FormatNumeric((long)(importeTotal * 100), 14));
|
||||
sb.Append(FormatNumeric(cantidadRegistros, 7));
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user