diff --git a/ROADMAP.md b/ROADMAP.md index 85205cc..d67bb6c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -119,7 +119,8 @@ | 2026-07-08 | F1.3-1.5 | Fix arquitectura: repos faltantes + services refactored to use repos via DI | ServicesExternos (Fase 2) | | 2026-07-08 | F1 | 13 controllers, Ejecutivo entity, ArqueoService, JwtMiddleware, SpComplexQueries | Fase 2 | | 2026-07-08 | F1.7 | Reportes restantes: Anexo, ContratoBlack, Presupuesto, CAEMP/CAEMPSNC, CC_EMPCSD, PropuestaComercial | Fase 2 | -| 2026-07-08 | F2 | ServicesExternos: DTE (LibreDTE), Transbank Webpay, Email (MailKit) + API + Dockerfile | Fase 3 (Frontend) | +| 2026-07-08 | F2 | ServicesExternos: DTE, Transbank, Email + API | Fase 3 | +| 2026-07-08 | AUDIT | 27 bugs corregidos: JWT secret, QuestPDF license, schema SPs, Transbank MySQL, JwtMiddleware pipeline, Email disconnect, model validation, casts | Fase 3 | | | | | | | | | | | diff --git a/backend/src/Ventas.API/Controllers/AuthController.cs b/backend/src/Ventas.API/Controllers/AuthController.cs index b0c8e7e..d77f509 100644 --- a/backend/src/Ventas.API/Controllers/AuthController.cs +++ b/backend/src/Ventas.API/Controllers/AuthController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ventas.Core.DTOs; using Ventas.Services; @@ -42,6 +43,7 @@ public class AuthController : ControllerBase }); } + [Authorize] [HttpGet("perfil")] public async Task Perfil([FromQuery] string usuarioId) { diff --git a/backend/src/Ventas.API/Program.cs b/backend/src/Ventas.API/Program.cs index caa0973..567f27f 100644 --- a/backend/src/Ventas.API/Program.cs +++ b/backend/src/Ventas.API/Program.cs @@ -2,6 +2,7 @@ using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; +using QuestPDF.Infrastructure; using Ventas.Infrastructure.Data; using Ventas.Infrastructure.Repositories; using Ventas.Core.Interfaces; @@ -9,6 +10,8 @@ using Ventas.Services; var builder = WebApplication.CreateBuilder(args); +QuestPDF.Settings.License = LicenseType.Community; + var connectionString = builder.Configuration.GetConnectionString("Default")!; builder.Services.AddControllers(); @@ -50,7 +53,8 @@ builder.Services.AddScoped(sp => builder.Services.AddScoped(sp => new EmpresaReportService(sp.GetRequiredService(), connectionString)); -var jwtSecret = builder.Configuration["Jwt:Secret"] ?? "default-dev-secret-change-in-production"; +var jwtSecret = builder.Configuration["Jwt:Secret"]; +if (string.IsNullOrEmpty(jwtSecret)) jwtSecret = "default-dev-secret-change-in-production"; var jwtExpiration = int.Parse(builder.Configuration["Jwt:ExpirationMinutes"] ?? "30"); builder.Services.AddScoped(sp => new JwtService(jwtSecret, jwtExpiration)); @@ -88,6 +92,7 @@ if (app.Environment.IsDevelopment()) app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); +app.UseMiddleware(); app.MapControllers(); app.Run(); diff --git a/backend/src/Ventas.API/appsettings.json b/backend/src/Ventas.API/appsettings.json index bad5a74..fb4f3cf 100644 --- a/backend/src/Ventas.API/appsettings.json +++ b/backend/src/Ventas.API/appsettings.json @@ -10,7 +10,6 @@ "Default": "Host=192.168.0.254;Port=5432;Database=ichn;Username=postgres;Password=apoca11;Pooling=true;Maximum Pool Size=100;" }, "Jwt": { - "Secret": "", "ExpirationMinutes": 30 } } diff --git a/backend/src/Ventas.Infrastructure/Repositories/InformeRepository.cs b/backend/src/Ventas.Infrastructure/Repositories/InformeRepository.cs index 36fb6ac..76a48f7 100644 --- a/backend/src/Ventas.Infrastructure/Repositories/InformeRepository.cs +++ b/backend/src/Ventas.Infrastructure/Repositories/InformeRepository.cs @@ -37,7 +37,7 @@ public class InformeRepository : IInformeRepository { using var connection = new NpgsqlConnection(_connectionString); var rows = await connection.QueryAsync( - "Buscar_LeadDiarios", + "sige_sam_v3.Buscar_LeadDiarios", new { inicio, termino, vendedor = vendedorId }, commandType: System.Data.CommandType.StoredProcedure); return rows.Select(r => (Dictionary)(IDictionary)r!); diff --git a/backend/src/Ventas.Infrastructure/Repositories/LeadQueryRepository.cs b/backend/src/Ventas.Infrastructure/Repositories/LeadQueryRepository.cs index bf26be3..0bc21f6 100644 --- a/backend/src/Ventas.Infrastructure/Repositories/LeadQueryRepository.cs +++ b/backend/src/Ventas.Infrastructure/Repositories/LeadQueryRepository.cs @@ -31,7 +31,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.BuscarLeadID", new { id = idLead }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> BuscarNuevosAsync(int ejecutivo) @@ -41,7 +41,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.BuscarLeadNuevos", new { ejecutivo = ejecutivo }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> BuscarGestionAsync(int ejecutivo, DateTime fecha) @@ -51,7 +51,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.BuscarLeadGestion", new { ejecutivoid = ejecutivo, fecha = fecha }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> BuscarMailAsync(string mail) @@ -61,7 +61,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.BuscarLeadMail", new { mailbuscar = mail }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> BuscarTituloAsync(string nombre) @@ -71,7 +71,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.BuscarLeadTitulo", new { nombrebuscar = nombre }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> BuscarXestadoAsync(int ejecutivo, int estado) @@ -81,7 +81,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.Lead_buscarXestado", new { userid = ejecutivo, estadoid = estado }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> BuscarXfiltroAsync(string tipo, string busqueda) @@ -91,7 +91,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.Lead_buscarXfiltro", new { tipofiltro = tipo, valorbuscar = busqueda }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> BuscarXinformeAsync(string tipo) @@ -101,7 +101,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.Lead_InformeXhoy", new { tipoinforme = tipo }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> MontosAsync(int ejecutivo) @@ -111,7 +111,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.BuscarLeadMontos", new { ejecutivo = ejecutivo }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> ActividadesAsync(int leadId, string tipo) @@ -121,7 +121,7 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.BuscarActividadesLead", new { id = leadId, tipoactividad = tipo }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task>> MotivosPerdidoAsync() @@ -130,7 +130,7 @@ public class LeadQueryRepository : ILeadQueryRepository var rows = await connection.QueryAsync( "sige_sam_v3.BuscarMotivoLeadPerdido", commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } public async Task BuscarContactoAsync(int leadId) @@ -149,6 +149,6 @@ public class LeadQueryRepository : ILeadQueryRepository "sige_sam_v3.LeadCantidadEjecutivoNuevo", new { userid = ejecutivoId }, commandType: System.Data.CommandType.StoredProcedure); - return rows.Select(r => (Dictionary)r); + return rows.Select(r => (Dictionary)(IDictionary)r!); } } diff --git a/backend/src/Ventas.Infrastructure/Repositories/LeadRepository.cs b/backend/src/Ventas.Infrastructure/Repositories/LeadRepository.cs index f18490e..5fe17da 100644 --- a/backend/src/Ventas.Infrastructure/Repositories/LeadRepository.cs +++ b/backend/src/Ventas.Infrastructure/Repositories/LeadRepository.cs @@ -17,7 +17,7 @@ public class LeadRepository : ILeadRepository public async Task IngresarAsync(LeadCreateDto dto) { using var connection = new NpgsqlConnection(_connectionString); - using var reader = await connection.ExecuteReaderAsync( + await connection.ExecuteAsync( "sige_sam_v3.GrabaLead", new { diff --git a/services-externos/src/ServicesExternos.API/Controllers/DteController.cs b/services-externos/src/ServicesExternos.API/Controllers/DteController.cs index a2f8951..62b93ae 100644 --- a/services-externos/src/ServicesExternos.API/Controllers/DteController.cs +++ b/services-externos/src/ServicesExternos.API/Controllers/DteController.cs @@ -25,8 +25,8 @@ public class DteController : ControllerBase return BadRequest(new { error = result.Estado }); } - [HttpGet("estado/{codigo}")] - public async Task ConsultarEstado(string codigo, [FromQuery] int dte, [FromQuery] long emisor) + [HttpGet("estado/{dte}/{emisor}")] + public async Task ConsultarEstado(int dte, long emisor) { var result = await _dteService.ConsultarSiguienteFolioAsync(dte, emisor); return Ok(result); diff --git a/services-externos/src/ServicesExternos.API/Controllers/TransbankController.cs b/services-externos/src/ServicesExternos.API/Controllers/TransbankController.cs index 0dca79e..aae2f1b 100644 --- a/services-externos/src/ServicesExternos.API/Controllers/TransbankController.cs +++ b/services-externos/src/ServicesExternos.API/Controllers/TransbankController.cs @@ -30,12 +30,14 @@ public class TransbankController : ControllerBase } [HttpPost("voucher")] - public async Task GrabarVoucher([FromBody] Models.VoucherRequest request) + public async Task GrabarVoucher([FromBody] VoucherRequest request) { var result = await _transbankService.GrabarVoucherAsync( request.Token, request.AccountingDate, request.BuyOrder, - request.CardNumber, request.AuthorizationCode, request.PaymentType, - request.SharesNumber, request.Amount); + request.CardNumber, request.CardExpiration, request.AuthorizationCode, + request.PaymentType, request.ResponseCode, request.SharesNumber, + request.Amount, request.CommerceCode, request.DetailBuyOrder, + request.SessionId, request.TransactionDate, request.Vci); return Ok(new { mensaje = result }); } diff --git a/services-externos/src/ServicesExternos.API/Models/TransbankModels.cs b/services-externos/src/ServicesExternos.API/Models/TransbankModels.cs index a4ca90f..2158cea 100644 --- a/services-externos/src/ServicesExternos.API/Models/TransbankModels.cs +++ b/services-externos/src/ServicesExternos.API/Models/TransbankModels.cs @@ -19,8 +19,15 @@ public class VoucherRequest public string AccountingDate { get; set; } = string.Empty; public string BuyOrder { get; set; } = string.Empty; public string CardNumber { get; set; } = string.Empty; + public string CardExpiration { get; set; } = string.Empty; public string AuthorizationCode { get; set; } = string.Empty; public string PaymentType { get; set; } = string.Empty; + public string ResponseCode { get; set; } = string.Empty; public string SharesNumber { get; set; } = string.Empty; public int Amount { get; set; } + public string CommerceCode { get; set; } = string.Empty; + public string DetailBuyOrder { get; set; } = string.Empty; + public string SessionId { get; set; } = string.Empty; + public string TransactionDate { get; set; } = string.Empty; + public string Vci { get; set; } = string.Empty; } diff --git a/services-externos/src/ServicesExternos.API/Services/EmailService.cs b/services-externos/src/ServicesExternos.API/Services/EmailService.cs index 839892e..3446c21 100644 --- a/services-externos/src/ServicesExternos.API/Services/EmailService.cs +++ b/services-externos/src/ServicesExternos.API/Services/EmailService.cs @@ -22,6 +22,7 @@ public class EmailService public async Task SendAsync(EmailRequest request) { + var client = new SmtpClient(); try { var message = new MimeMessage(); @@ -34,7 +35,7 @@ public class EmailService Text = request.Body }; - if (request.AttachmentPaths != null && request.AttachmentPaths.Count > 0) + if (request.AttachmentPaths?.Count > 0) { var multipart = new Multipart("mixed") { body }; foreach (var path in request.AttachmentPaths) @@ -54,17 +55,20 @@ public class EmailService message.Body = body; } - using var client = new SmtpClient(); await client.ConnectAsync(_smtpHost, _smtpPort, SecureSocketOptions.StartTls); await client.AuthenticateAsync(_smtpUser, _smtpPassword); await client.SendAsync(message); - await client.DisconnectAsync(true); - return "ok"; } catch (Exception ex) { return $"Error: {ex.Message}"; } + finally + { + if (client.IsConnected) + await client.DisconnectAsync(true); + client.Dispose(); + } } } diff --git a/services-externos/src/ServicesExternos.API/Services/TransbankService.cs b/services-externos/src/ServicesExternos.API/Services/TransbankService.cs index 843039e..f763427 100644 --- a/services-externos/src/ServicesExternos.API/Services/TransbankService.cs +++ b/services-externos/src/ServicesExternos.API/Services/TransbankService.cs @@ -1,8 +1,8 @@ using System.Text; using Newtonsoft.Json; using ServicesExternos.API.Models; +using MySqlConnector; using Dapper; -using Npgsql; namespace ServicesExternos.API.Services; @@ -11,7 +11,7 @@ public class TransbankService private readonly HttpClient _client; private readonly string _apiKey; private readonly string _commerceCode; - private readonly string _connectionString; + private readonly string _mysqlConnectionString; private readonly string _environment; public TransbankService(HttpClient client, IConfiguration configuration) @@ -19,7 +19,7 @@ public class TransbankService _client = client; _apiKey = configuration["Transbank:ApiKey"] ?? throw new Exception("Transbank:ApiKey required"); _commerceCode = configuration["Transbank:CommerceCode"] ?? throw new Exception("Transbank:CommerceCode required"); - _connectionString = configuration.GetConnectionString("Default")!; + _mysqlConnectionString = configuration.GetConnectionString("CajaTbk")!; _environment = configuration["Transbank:Environment"] ?? "integration"; var baseUrl = _environment == "production" @@ -49,26 +49,38 @@ public class TransbankService public async Task ConfirmarTransaccionAsync(string tokenWs) { + if (string.IsNullOrEmpty(tokenWs)) + throw new ArgumentException("TokenWs es requerido"); + var response = await _client.PutAsync($"/rswebpaytransaction/api/webpay/v1.2/transactions/{tokenWs}", null); return await response.Content.ReadAsStringAsync(); } public async Task GrabarVoucherAsync(string token, string accountingDate, string buyOrder, - string cardNumber, string authCode, string paymentType, string sharesNumber, int amount) + string cardNumber, string cardExpiration, string authCode, string paymentType, string responseCode, + string sharesNumber, int amount, string commerceCode, string detailBuyOrder, string sessionId, + string transactionDate, string vci) { - using var connection = new NpgsqlConnection(_connectionString); + using var connection = new MySqlConnection(_mysqlConnectionString); await connection.ExecuteAsync( "caja_tbk.GrabaVoucher", new { token_id = token, - xaccountingDate = accountingDate, - xbuyOrder = buyOrder, + XaccountingDate = accountingDate, + XbuyOrder = buyOrder, xcardDetailcardNumber = cardNumber, + xcardDetailcardExpirationDate = cardExpiration, xdetailOutputAuthorizationCode = authCode, xdetailOutputPaymentTypeCode = paymentType, + xdetailOutputResponseCode = responseCode, xdetailOutputSharesNumber = sharesNumber, - xdetailOutputAmount = amount + xdetailOutputAmount = amount, + xdetailOutputcommerceCode = commerceCode, + xdetailOutputBuyOrder = detailBuyOrder, + xsessionId = sessionId, + xtransactionDate = transactionDate, + xVCI = vci }, commandType: System.Data.CommandType.StoredProcedure); return "ok"; @@ -76,7 +88,7 @@ public class TransbankService public async Task BuscarTransaccionAsync(string token) { - using var connection = new NpgsqlConnection(_connectionString); + using var connection = new MySqlConnection(_mysqlConnectionString); var rows = await connection.QueryAsync( "caja_tbk.BuscarInfoToken", new { tokenid = token }, diff --git a/services-externos/src/ServicesExternos.API/ServicesExternos.API.csproj b/services-externos/src/ServicesExternos.API/ServicesExternos.API.csproj index 7a69b1b..007c712 100644 --- a/services-externos/src/ServicesExternos.API/ServicesExternos.API.csproj +++ b/services-externos/src/ServicesExternos.API/ServicesExternos.API.csproj @@ -10,6 +10,7 @@ + diff --git a/services-externos/src/ServicesExternos.API/ServicesExternos.API.http b/services-externos/src/ServicesExternos.API/ServicesExternos.API.http deleted file mode 100644 index ddbcd85..0000000 --- a/services-externos/src/ServicesExternos.API/ServicesExternos.API.http +++ /dev/null @@ -1,6 +0,0 @@ -@ServicesExternos.API_HostAddress = http://localhost:5194 - -GET {{ServicesExternos.API_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/services-externos/src/ServicesExternos.API/appsettings.json b/services-externos/src/ServicesExternos.API/appsettings.json index 011d5dc..cedef09 100644 --- a/services-externos/src/ServicesExternos.API/appsettings.json +++ b/services-externos/src/ServicesExternos.API/appsettings.json @@ -7,7 +7,8 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "Default": "Host=192.168.0.254;Port=5432;Database=ichn;Username=postgres;Password=apoca11" + "Default": "Host=192.168.0.254;Port=5432;Database=ichn;Username=postgres;Password=apoca11", + "CajaTbk": "Server=192.168.0.254;Port=3306;Database=caja_tbk;User=postgres;Password=apoca11" }, "LibreDTE": { "UserHash": "",