Fase 5 Completa: Implementada la generación de CSV automática y manual.

This commit is contained in:
2025-10-28 13:19:24 -03:00
parent 75d06820aa
commit 3c12a89f76
7 changed files with 150 additions and 5 deletions

View File

@@ -0,0 +1,36 @@
// backend/src/Titulares.Api/Controllers/AccionesController.cs
using Microsoft.AspNetCore.Mvc;
using Titulares.Api.Data;
using Titulares.Api.Services;
namespace Titulares.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AccionesController : ControllerBase
{
private readonly TitularRepositorio _repositorio;
private readonly CsvService _csvService;
public AccionesController(TitularRepositorio repositorio, CsvService csvService)
{
_repositorio = repositorio;
_csvService = csvService;
}
[HttpPost("generar-csv")]
public async Task<IActionResult> GenerarCsvManual()
{
try
{
var titulares = await _repositorio.ObtenerTodosAsync();
await _csvService.GenerarCsvAsync(titulares);
return Ok(new { message = "CSV generado manualmente con éxito." });
}
catch (Exception ex)
{
return StatusCode(500, $"Error al generar el CSV: {ex.Message}");
}
}
}

View File

@@ -14,6 +14,7 @@ public class Titular
public string? Encabezado { get; set; }
public string? Tipo { get; set; }
public string? Fuente { get; set; }
public string? Viñeta { get; set; }
}
// DTO (Data Transfer Object) para la creación de un titular manual

View File

@@ -19,6 +19,7 @@ builder.Services.Configure<ConfiguracionApp>(builder.Configuration);
// Añadimos nuestro repositorio personalizado
builder.Services.AddSingleton<TitularRepositorio>();
builder.Services.AddScoped<ScrapingService>();
builder.Services.AddScoped<CsvService>();
// Añadimos la política de CORS
builder.Services.AddCors(options =>

View File

@@ -0,0 +1,83 @@
// backend/src/Titulares.Api/Services/CsvService.cs
using CsvHelper;
using CsvHelper.Configuration;
using Microsoft.Extensions.Options;
using System.Globalization;
using Titulares.Api.Models;
namespace Titulares.Api.Services;
public class CsvService
{
private readonly ILogger<CsvService> _logger;
private readonly IOptionsMonitor<ConfiguracionApp> _configuracion;
public CsvService(ILogger<CsvService> logger, IOptionsMonitor<ConfiguracionApp> configuracion)
{
_logger = logger;
_configuracion = configuracion;
}
public async Task GenerarCsvAsync(IEnumerable<Titular> titulares)
{
var rutaArchivo = _configuracion.CurrentValue.RutaCsv;
_logger.LogInformation("Iniciando generación de CSV en: {Ruta}", rutaArchivo);
try
{
// La cláusula Where asegura que los Encabezados no sean nulos o vacíos.
var grupos = titulares
.Where(t => !string.IsNullOrEmpty(t.Encabezado))
.GroupBy(t => t.Encabezado);
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ";",
};
await using var writer = new StreamWriter(rutaArchivo);
await using var csv = new CsvWriter(writer, config);
// Escribimos la cabecera principal
csv.WriteField("Text");
csv.WriteField("Heading");
csv.WriteField("Value");
csv.WriteField("Up");
csv.WriteField("Down");
csv.WriteField("Change");
csv.WriteField("Bullet");
await csv.NextRecordAsync();
foreach (var grupo in grupos)
{
csv.WriteField("");
csv.WriteField($" {grupo.Key!.ToUpper()} ");
csv.WriteField("");
await csv.NextRecordAsync();
await csv.NextRecordAsync();
foreach (var titular in grupo)
{
csv.WriteField(titular.Texto);
csv.WriteField(""); // Heading
csv.WriteField(""); // Value
csv.WriteField(""); // Up
csv.WriteField(""); // Down
csv.WriteField(""); // Change
csv.WriteField(titular.Viñeta ?? "•");
await csv.NextRecordAsync();
}
await csv.NextRecordAsync();
}
_logger.LogInformation("CSV generado exitosamente.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al generar el archivo CSV.");
throw;
}
}
}

View File

@@ -39,6 +39,7 @@ public class ProcesoScrapingWorker : BackgroundService
var repositorio = scope.ServiceProvider.GetRequiredService<TitularRepositorio>();
var scrapingService = scope.ServiceProvider.GetRequiredService<ScrapingService>();
var hubContext = scope.ServiceProvider.GetRequiredService<IHubContext<TitularesHub>>();
var csvService = scope.ServiceProvider.GetRequiredService<CsvService>();
// Obtener estos valores desde la configuración
int cantidadAObtener = configActual.CantidadTitularesAScrapear;
@@ -49,14 +50,14 @@ public class ProcesoScrapingWorker : BackgroundService
if (articulosScrapeados.Any())
{
// 2. Sincronizar con la base de datos
await repositorio.SincronizarDesdeScraping(articulosScrapeados, limiteTotalEnDb);
_logger.LogInformation("Sincronización con la base de datos completada.");
// 3. Notificar a todos los clientes a través de SignalR
var titularesActualizados = await repositorio.ObtenerTodosAsync();
await hubContext.Clients.All.SendAsync("TitularesActualizados", titularesActualizados, stoppingToken);
_logger.LogInformation("Notificación enviada a los clientes.");
await csvService.GenerarCsvAsync(titularesActualizados);
}
else
{