Se introduce un sistema completo para auditar los envíos masivos de correos durante el cierre mensual y se refactoriza la interfaz de usuario de procesos para una mayor claridad y escalabilidad. Además, se mejora la lógica de negocio para la gestión de bajas de suscripciones. ### ✨ Nuevas Características - **Auditoría de Envíos Masivos (Cierre Mensual):** - Se crea una nueva tabla `com_LotesDeEnvio` para registrar cada ejecución del proceso de facturación mensual. - El `FacturacionService` ahora crea un "lote" al iniciar el cierre, registra el resultado de cada envío de email individual asociándolo a dicho lote, y actualiza las estadísticas finales (enviados, fallidos) al terminar. - Se implementa un nuevo `LotesEnvioController` con un endpoint para consultar los detalles de cualquier lote de envío histórico. ### 🔄 Refactorización y Mejoras - **Rediseño de la Página de Procesos:** - La antigua página "Facturación" se renombra a `CierreYProcesosPage` y se rediseña completamente utilizando una interfaz de Pestañas (Tabs). - **Pestaña "Procesos Mensuales":** Aisla las acciones principales (Generar Cierre, Archivo de Débito, Procesar Respuesta), mostrando un resumen del resultado del último envío. - **Pestaña "Historial de Cierres":** Muestra una tabla con todos los lotes de envío pasados y permite al usuario ver los detalles de cada uno en un modal. - **Filtros para el Historial de Cierres:** - Se añaden filtros por Mes y Año a la pestaña de "Historial de Cierres", permitiendo al usuario buscar y auditar procesos pasados de manera eficiente. El filtrado se realiza en el backend para un rendimiento óptimo. - **Lógica de `FechaFin` Obligatoria para Bajas:** - Se implementa una regla de negocio crucial: al cambiar el estado de una suscripción a "Pausada" o "Cancelada", ahora es obligatorio establecer una `FechaFin`. - **Frontend:** El modal de suscripciones ahora gestiona esto automáticamente, haciendo el campo `FechaFin` requerido y visible según el estado seleccionado. - **Backend:** Se añade una validación en `SuscripcionService` como segunda capa de seguridad para garantizar la integridad de los datos. ### 🐛 Corrección de Errores - **Reporte de Distribución:** Se corrigió un bug en la generación del PDF donde la columna de fecha no mostraba la "Fecha de Baja" para las suscripciones finalizadas. Ahora se muestra la fecha correcta según la sección (Altas o Bajas). - **Errores de Compilación y Dependencias:** Se solucionaron varios errores de compilación en el backend, principalmente relacionados con la falta de registro de los nuevos repositorios (`ILoteDeEnvioRepository`, `IEmailLogService`, etc.) en el contenedor de inyección de dependencias (`Program.cs`). - **Errores de Tipado en Frontend:** Se corrigieron múltiples errores de TypeScript en `CierreYProcesosPage` debidos a la inconsistencia entre `PascalCase` (C#) y `camelCase` (JSON/TypeScript), asegurando un mapeo correcto de los datos de la API.
283 lines
14 KiB
C#
283 lines
14 KiB
C#
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using System.Text;
|
|
using GestionIntegral.Api.Data;
|
|
using GestionIntegral.Api.Services.Contables;
|
|
using GestionIntegral.Api.Services.Distribucion;
|
|
using GestionIntegral.Api.Services.Radios;
|
|
using GestionIntegral.Api.Data.Repositories.Contables;
|
|
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
|
using GestionIntegral.Api.Data.Repositories.Impresion;
|
|
using GestionIntegral.Api.Data.Repositories.Radios;
|
|
using GestionIntegral.Api.Services.Impresion;
|
|
using GestionIntegral.Api.Services.Usuarios;
|
|
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
|
using Microsoft.OpenApi.Models;
|
|
using GestionIntegral.Api.Data.Repositories.Reportes;
|
|
using GestionIntegral.Api.Services.Reportes;
|
|
using GestionIntegral.Api.Services.Pdf;
|
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
using GestionIntegral.Api.Services.Anomalia;
|
|
using GestionIntegral.Api.Data.Repositories.Suscripciones;
|
|
using GestionIntegral.Api.Services.Suscripciones;
|
|
using GestionIntegral.Api.Models.Comunicaciones;
|
|
using GestionIntegral.Api.Services.Comunicaciones;
|
|
using GestionIntegral.Api.Data.Repositories.Comunicaciones;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// --- Registros de Servicios ---
|
|
builder.Services.AddSingleton<DbConnectionFactory>();
|
|
builder.Services.AddScoped<PasswordHasherService>();
|
|
builder.Services.AddScoped<IAuthRepository, AuthRepository>();
|
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
|
builder.Services.AddScoped<ITipoPagoRepository, TipoPagoRepository>();
|
|
builder.Services.AddScoped<ITipoPagoService, TipoPagoService>();
|
|
builder.Services.AddScoped<IZonaRepository, ZonaRepository>();
|
|
builder.Services.AddScoped<IZonaService, ZonaService>();
|
|
builder.Services.AddScoped<IEmpresaRepository, EmpresaRepository>();
|
|
builder.Services.AddScoped<IEmpresaService, EmpresaService>();
|
|
builder.Services.AddScoped<ISaldoRepository, SaldoRepository>();
|
|
builder.Services.AddScoped<IPlantaRepository, PlantaRepository>();
|
|
builder.Services.AddScoped<IPlantaService, PlantaService>();
|
|
builder.Services.AddScoped<ITipoBobinaRepository, TipoBobinaRepository>();
|
|
builder.Services.AddScoped<ITipoBobinaService, TipoBobinaService>();
|
|
builder.Services.AddScoped<IEstadoBobinaRepository, EstadoBobinaRepository>();
|
|
builder.Services.AddScoped<IEstadoBobinaService, EstadoBobinaService>();
|
|
builder.Services.AddScoped<IOtroDestinoRepository, OtroDestinoRepository>();
|
|
builder.Services.AddScoped<IOtroDestinoService, OtroDestinoService>();
|
|
builder.Services.AddScoped<IPerfilRepository, PerfilRepository>();
|
|
builder.Services.AddScoped<IPerfilService, PerfilService>();
|
|
builder.Services.AddScoped<IPermisoRepository, PermisoRepository>();
|
|
builder.Services.AddScoped<IPermisoService, PermisoService>();
|
|
builder.Services.AddScoped<IUsuarioRepository, UsuarioRepository>();
|
|
builder.Services.AddScoped<IUsuarioService, UsuarioService>();
|
|
builder.Services.AddScoped<ICanillaRepository, CanillaRepository>();
|
|
builder.Services.AddScoped<ICanillaService, CanillaService>();
|
|
builder.Services.AddScoped<IDistribuidorRepository, DistribuidorRepository>();
|
|
builder.Services.AddScoped<IDistribuidorService, DistribuidorService>();
|
|
builder.Services.AddScoped<IPublicacionRepository, PublicacionRepository>();
|
|
builder.Services.AddScoped<IPublicacionService, PublicacionService>();
|
|
builder.Services.AddScoped<IRecargoZonaRepository, RecargoZonaRepository>();
|
|
builder.Services.AddScoped<IRecargoZonaService, RecargoZonaService>();
|
|
builder.Services.AddScoped<IPorcPagoRepository, PorcPagoRepository>();
|
|
builder.Services.AddScoped<IPorcPagoService, PorcPagoService>();
|
|
builder.Services.AddScoped<IPorcMonCanillaRepository, PorcMonCanillaRepository>();
|
|
builder.Services.AddScoped<IPorcMonCanillaService, PorcMonCanillaService>();
|
|
builder.Services.AddScoped<IPubliSeccionRepository, PubliSeccionRepository>();
|
|
builder.Services.AddScoped<IPubliSeccionService, PubliSeccionService>();
|
|
builder.Services.AddScoped<IPrecioRepository, PrecioRepository>();
|
|
builder.Services.AddScoped<IPrecioService, PrecioService>();
|
|
builder.Services.AddScoped<IStockBobinaRepository, StockBobinaRepository>();
|
|
builder.Services.AddScoped<IStockBobinaService, StockBobinaService>();
|
|
builder.Services.AddScoped<IRegTiradaRepository, RegTiradaRepository>();
|
|
builder.Services.AddScoped<IRegPublicacionSeccionRepository, RegPublicacionSeccionRepository>();
|
|
builder.Services.AddScoped<ITiradaService, TiradaService>();
|
|
builder.Services.AddScoped<ISalidaOtroDestinoRepository, SalidaOtroDestinoRepository>();
|
|
builder.Services.AddScoped<ISalidaOtroDestinoService, SalidaOtroDestinoService>();
|
|
builder.Services.AddScoped<IEntradaSalidaDistRepository, EntradaSalidaDistRepository>();
|
|
builder.Services.AddScoped<IEntradaSalidaDistService, EntradaSalidaDistService>();
|
|
builder.Services.AddScoped<IEntradaSalidaCanillaRepository, EntradaSalidaCanillaRepository>();
|
|
builder.Services.AddScoped<IEntradaSalidaCanillaService, EntradaSalidaCanillaService>();
|
|
builder.Services.AddScoped<IControlDevolucionesRepository, ControlDevolucionesRepository>();
|
|
builder.Services.AddScoped<IControlDevolucionesService, ControlDevolucionesService>();
|
|
builder.Services.AddScoped<IPagoDistribuidorRepository, PagoDistribuidorRepository>();
|
|
builder.Services.AddScoped<IPagoDistribuidorService, PagoDistribuidorService>();
|
|
builder.Services.AddScoped<INotaCreditoDebitoRepository, NotaCreditoDebitoRepository>();
|
|
builder.Services.AddScoped<INotaCreditoDebitoService, NotaCreditoDebitoService>();
|
|
builder.Services.AddScoped<IRitmoRepository, RitmoRepository>();
|
|
builder.Services.AddScoped<IRitmoService, RitmoService>();
|
|
builder.Services.AddScoped<ICancionRepository, CancionRepository>();
|
|
builder.Services.AddScoped<ICancionService, CancionService>();
|
|
builder.Services.AddScoped<IRadioListaService, RadioListaService>();
|
|
builder.Services.AddScoped<INovedadCanillaRepository, NovedadCanillaRepository>();
|
|
builder.Services.AddScoped<INovedadCanillaService, NovedadCanillaService>();
|
|
builder.Services.AddScoped<ICambioParadaRepository, CambioParadaRepository>();
|
|
builder.Services.AddScoped<ICambioParadaService, CambioParadaService>();
|
|
// Servicio de Saldos
|
|
builder.Services.AddScoped<ISaldoService, SaldoService>();
|
|
// Repositorios de Reportes
|
|
builder.Services.AddScoped<IReportesRepository, ReportesRepository>();
|
|
// Servicios de Reportes
|
|
builder.Services.AddScoped<IReportesService, ReportesService>();
|
|
// QuestPDF
|
|
builder.Services.AddScoped<IQuestPdfGenerator, QuestPdfGenerator>();
|
|
// Servicio de Alertas
|
|
builder.Services.AddScoped<IAlertaService, AlertaService>();
|
|
|
|
// --- Suscripciones ---
|
|
builder.Services.AddScoped<IFormaPagoRepository, FormaPagoRepository>();
|
|
builder.Services.AddScoped<ISuscriptorRepository, SuscriptorRepository>();
|
|
builder.Services.AddScoped<ISuscripcionRepository, SuscripcionRepository>();
|
|
builder.Services.AddScoped<IFacturaRepository, FacturaRepository>();
|
|
builder.Services.AddScoped<ILoteDebitoRepository, LoteDebitoRepository>();
|
|
builder.Services.AddScoped<IPagoRepository, PagoRepository>();
|
|
builder.Services.AddScoped<IPromocionRepository, PromocionRepository>();
|
|
builder.Services.AddScoped<IAjusteRepository, AjusteRepository>();
|
|
builder.Services.AddScoped<IFacturaDetalleRepository, FacturaDetalleRepository>();
|
|
|
|
builder.Services.AddScoped<IFormaPagoService, FormaPagoService>();
|
|
builder.Services.AddScoped<ISuscriptorService, SuscriptorService>();
|
|
builder.Services.AddScoped<ISuscripcionService, SuscripcionService>();
|
|
builder.Services.AddScoped<IFacturacionService, FacturacionService>();
|
|
builder.Services.AddScoped<IDebitoAutomaticoService, DebitoAutomaticoService>();
|
|
builder.Services.AddScoped<IPagoService, PagoService>();
|
|
builder.Services.AddScoped<IPromocionService, PromocionService>();
|
|
builder.Services.AddScoped<IAjusteService, AjusteService>();
|
|
|
|
// --- Comunicaciones ---
|
|
builder.Services.Configure<MailSettings>(builder.Configuration.GetSection("MailSettings"));
|
|
builder.Services.AddTransient<IEmailService, EmailService>();
|
|
builder.Services.AddScoped<IEmailLogRepository, EmailLogRepository>();
|
|
builder.Services.AddScoped<IEmailLogService, EmailLogService>();
|
|
builder.Services.AddScoped<ILoteDeEnvioRepository, LoteDeEnvioRepository>();
|
|
|
|
// --- SERVICIO DE HEALTH CHECKS ---
|
|
// Añadimos una comprobación específica para SQL Server.
|
|
// El sistema usará la cadena de conexión configurada en appsettings.json o variables de entorno.
|
|
builder.Services.AddHealthChecks()
|
|
.AddSqlServer(
|
|
connectionString: builder.Configuration.GetConnectionString("DefaultConnection") ?? "",
|
|
healthQuery: "SELECT 1;",
|
|
name: "sql-server",
|
|
failureStatus: HealthStatus.Unhealthy,
|
|
tags: new string[] { "database" }
|
|
);
|
|
|
|
// --- Configuración de Autenticación JWT ---
|
|
var jwtSettings = builder.Configuration.GetSection("Jwt");
|
|
|
|
// Le decimos que busque la clave JWT en la raíz de la configuración (donde están las variables de entorno).
|
|
// Si no la encuentra, como respaldo, busca en la sección "Jwt" del appsettings.
|
|
var jwtKey = builder.Configuration["JWT_KEY"] ?? jwtSettings["Key"] ?? throw new ArgumentNullException("JWT_KEY or Jwt:Key not configured");
|
|
|
|
var keyBytes = Encoding.ASCII.GetBytes(jwtKey);
|
|
|
|
builder.Services.AddAuthentication(options =>
|
|
{
|
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
})
|
|
.AddJwtBearer(options =>
|
|
{
|
|
options.RequireHttpsMetadata = builder.Environment.IsProduction();
|
|
options.SaveToken = true;
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuerSigningKey = true,
|
|
IssuerSigningKey = new SymmetricSecurityKey(keyBytes),
|
|
ValidateIssuer = true,
|
|
ValidIssuer = jwtSettings["Issuer"],
|
|
ValidateAudience = true,
|
|
ValidAudience = jwtSettings["Audience"],
|
|
ValidateLifetime = true,
|
|
ClockSkew = TimeSpan.Zero
|
|
};
|
|
});
|
|
|
|
// --- Configuración de Autorización ---
|
|
builder.Services.AddAuthorization();
|
|
|
|
// --- Configuración de CORS --- //
|
|
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddPolicy(name: MyAllowSpecificOrigins,
|
|
policy =>
|
|
{
|
|
policy.WithOrigins(
|
|
"http://localhost:5173", // Para desarrollo local
|
|
"https://gestion.eldiaservicios.com" // Para producción
|
|
)
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod();
|
|
});
|
|
});
|
|
// --- Fin CORS ---
|
|
|
|
// --- Servicios del Contenedor ---
|
|
builder.Services.AddControllers();
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
|
|
// --- Configuración Avanzada de Swagger/OpenAPI ---
|
|
builder.Services.AddSwaggerGen(options =>
|
|
{
|
|
// Definición general del documento Swagger
|
|
options.SwaggerDoc("v1", new OpenApiInfo
|
|
{
|
|
Version = "v1",
|
|
Title = "GestionIntegral API",
|
|
Description = "API para el sistema de Gestión Integral (Migración VB.NET)",
|
|
// Puedes añadir TermsOfService, Contact, License si lo deseas
|
|
});
|
|
|
|
// (Opcional) Incluir comentarios XML si los usas en tus controladores/DTOs
|
|
// var xmlFilename = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
|
// options.IncludeXmlComments(System.IO.Path.Combine(AppContext.BaseDirectory, xmlFilename));
|
|
|
|
// Definición de Seguridad para JWT en Swagger UI
|
|
// Esto añade el botón "Authorize" en la UI de Swagger para poder pegar el token Bearer
|
|
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
|
{
|
|
In = ParameterLocation.Header,
|
|
Description = "Por favor, introduce 'Bearer' seguido de un espacio y luego tu token JWT.",
|
|
Name = "Authorization",
|
|
Type = SecuritySchemeType.ApiKey, // Usar ApiKey para el formato Bearer simple
|
|
Scheme = "Bearer" // Esquema a usar
|
|
});
|
|
|
|
options.AddSecurityRequirement(new OpenApiSecurityRequirement
|
|
{
|
|
{
|
|
new OpenApiSecurityScheme
|
|
{
|
|
Reference = new OpenApiReference
|
|
{
|
|
Type = ReferenceType.SecurityScheme,
|
|
Id = "Bearer" // Debe coincidir con el Id definido en AddSecurityDefinition
|
|
},
|
|
Scheme = "oauth2", // Aunque el scheme sea ApiKey, a veces se usa oauth2 aquí para la UI
|
|
Name = "Bearer",
|
|
In = ParameterLocation.Header,
|
|
},
|
|
new List<string>() // Lista de scopes (vacía si no usas scopes OAuth2 complejos)
|
|
}
|
|
});
|
|
});
|
|
// --- Fin Configuración Swagger ---
|
|
|
|
var app = builder.Build();
|
|
|
|
// Habilitamos Swagger SIEMPRE, sin importar el entorno.
|
|
app.UseSwagger(); // Habilita el middleware para servir el JSON de Swagger
|
|
app.UseSwaggerUI(options => // Habilita el middleware para servir la UI de Swagger
|
|
{
|
|
options.SwaggerEndpoint("/swagger/v1/swagger.json", "GestionIntegral API v1");
|
|
// Opcional: Para que Swagger aparezca en la raíz de la API (ej: http://192.168.4.128:8081/)
|
|
options.RoutePrefix = string.Empty;
|
|
});
|
|
|
|
|
|
// Mantenemos esta lógica solo para la página de error de desarrollo, si quieres.
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
// Aquí puedes dejar otras herramientas solo para desarrollo si las tuvieras.
|
|
}
|
|
|
|
// ¡¡¡NO USAR UseHttpsRedirection si la API corre en HTTP!!!
|
|
// Comenta o elimina la siguiente línea si SÓLO usas http://localhost:5183
|
|
//app.UseHttpsRedirection();
|
|
|
|
app.UseCors(MyAllowSpecificOrigins);
|
|
|
|
app.UseAuthentication(); // Debe ir ANTES de UseAuthorization
|
|
app.UseAuthorization();
|
|
|
|
// Mapeamos la ruta "/health". Cuando se acceda a ella, se ejecutarán todas las comprobaciones registradas.
|
|
// Esto va ANTES de app.MapControllers().
|
|
app.MapHealthChecks("/health");
|
|
|
|
app.MapControllers();
|
|
|
|
app.Run(); |