Ya perdí el hilo de los cambios pero ahi van.

This commit is contained in:
2025-05-23 15:47:39 -03:00
parent e7e185a9cb
commit 3c1fe15b1f
141 changed files with 9764 additions and 190 deletions

View File

@@ -0,0 +1,173 @@
using GestionIntegral.Api.Data.Repositories.Radios;
using GestionIntegral.Api.Dtos.Radios;
using GestionIntegral.Api.Models.Radios;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
// using GestionIntegral.Api.Data; // Para DbConnectionFactory si se usa transacción
// using System.Data; // Para IsolationLevel si se usa transacción
namespace GestionIntegral.Api.Services.Radios
{
public class CancionService : ICancionService
{
private readonly ICancionRepository _cancionRepository;
private readonly IRitmoRepository _ritmoRepository;
private readonly ILogger<CancionService> _logger;
public CancionService(
ICancionRepository cancionRepository,
IRitmoRepository ritmoRepository,
ILogger<CancionService> logger)
{
_cancionRepository = cancionRepository;
_ritmoRepository = ritmoRepository;
_logger = logger;
}
private async Task<CancionDto> MapToDto(Cancion cancion)
{
if (cancion == null) return null!;
string? nombreRitmo = null;
if (cancion.Ritmo.HasValue && cancion.Ritmo.Value > 0)
{
var ritmoDb = await _ritmoRepository.GetByIdAsync(cancion.Ritmo.Value);
nombreRitmo = ritmoDb?.NombreRitmo;
}
return new CancionDto
{
Id = cancion.Id,
Tema = cancion.Tema,
CompositorAutor = cancion.CompositorAutor,
Interprete = cancion.Interprete,
Sello = cancion.Sello,
Placa = cancion.Placa,
Pista = cancion.Pista,
Introduccion = cancion.Introduccion,
IdRitmo = cancion.Ritmo, // Pasar el valor de cancion.Ritmo
NombreRitmo = nombreRitmo,
Formato = cancion.Formato,
Album = cancion.Album
};
}
public async Task<IEnumerable<CancionDto>> ObtenerTodasAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter)
{
var canciones = await _cancionRepository.GetAllAsync(temaFilter, interpreteFilter, idRitmoFilter);
var dtos = new List<CancionDto>();
foreach (var cancion in canciones)
{
dtos.Add(await MapToDto(cancion));
}
return dtos;
}
public async Task<CancionDto?> ObtenerPorIdAsync(int id)
{
var cancion = await _cancionRepository.GetByIdAsync(id);
return cancion == null ? null : await MapToDto(cancion);
}
public async Task<(CancionDto? Cancion, string? Error)> CrearAsync(CreateCancionDto createDto, int idUsuario)
{
if (createDto.IdRitmo.HasValue && createDto.IdRitmo.Value > 0) // Asegurar que > 0 para evitar buscar ritmo con ID 0
{
if(await _ritmoRepository.GetByIdAsync(createDto.IdRitmo.Value) == null)
return (null, "El ritmo seleccionado no es válido.");
}
if (!string.IsNullOrWhiteSpace(createDto.Tema) && !string.IsNullOrWhiteSpace(createDto.Interprete) &&
await _cancionRepository.ExistsByTemaAndInterpreteAsync(createDto.Tema, createDto.Interprete))
{
return (null, "Ya existe una canción con el mismo tema e intérprete.");
}
var nuevaCancion = new Cancion
{
Tema = createDto.Tema, CompositorAutor = createDto.CompositorAutor, Interprete = createDto.Interprete,
Sello = createDto.Sello, Placa = createDto.Placa, Pista = createDto.Pista,
Introduccion = createDto.Introduccion,
Ritmo = createDto.IdRitmo, // Asignar createDto.IdRitmo a la propiedad Ritmo del modelo
Formato = createDto.Formato, Album = createDto.Album
};
try
{
var cancionCreada = await _cancionRepository.CreateAsync(nuevaCancion);
if (cancionCreada == null) return (null, "Error al crear la canción.");
_logger.LogInformation("Canción ID {Id} creada por Usuario ID {UserId}.", cancionCreada.Id, idUsuario);
return (await MapToDto(cancionCreada), null);
}
catch (System.Exception ex)
{
_logger.LogError(ex, "Error CrearAsync Cancion: {Tema}", createDto.Tema);
return (null, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateCancionDto updateDto, int idUsuario)
{
var cancionExistente = await _cancionRepository.GetByIdAsync(id);
if (cancionExistente == null) return (false, "Canción no encontrada.");
if (updateDto.IdRitmo.HasValue && updateDto.IdRitmo.Value > 0) // Asegurar que > 0
{
if (await _ritmoRepository.GetByIdAsync(updateDto.IdRitmo.Value) == null)
return (false, "El ritmo seleccionado no es válido.");
}
if ((!string.IsNullOrWhiteSpace(updateDto.Tema) && !string.IsNullOrWhiteSpace(updateDto.Interprete)) &&
(cancionExistente.Tema != updateDto.Tema || cancionExistente.Interprete != updateDto.Interprete) &&
await _cancionRepository.ExistsByTemaAndInterpreteAsync(updateDto.Tema!, updateDto.Interprete!, id))
{
return (false, "Ya existe otra canción con el mismo tema e intérprete."); // Devolver tupla bool,string
}
cancionExistente.Tema = updateDto.Tema; cancionExistente.CompositorAutor = updateDto.CompositorAutor;
cancionExistente.Interprete = updateDto.Interprete; cancionExistente.Sello = updateDto.Sello;
cancionExistente.Placa = updateDto.Placa; cancionExistente.Pista = updateDto.Pista;
cancionExistente.Introduccion = updateDto.Introduccion;
cancionExistente.Ritmo = updateDto.IdRitmo; // Asignar updateDto.IdRitmo a la propiedad Ritmo del modelo
cancionExistente.Formato = updateDto.Formato; cancionExistente.Album = updateDto.Album;
try
{
var actualizado = await _cancionRepository.UpdateAsync(cancionExistente);
if (!actualizado) return (false, "Error al actualizar la canción.");
_logger.LogInformation("Canción ID {Id} actualizada por Usuario ID {UserId}.", id, idUsuario);
return (true, null);
}
catch (System.Exception ex)
{
_logger.LogError(ex, "Error ActualizarAsync Canción ID: {Id}", id);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
{
var cancionExistente = await _cancionRepository.GetByIdAsync(id);
if (cancionExistente == null) return (false, "Canción no encontrada.");
try
{
var eliminado = await _cancionRepository.DeleteAsync(id);
if (!eliminado) return (false, "Error al eliminar la canción.");
_logger.LogInformation("Canción ID {Id} eliminada por Usuario ID {UserId}.", id, idUsuario);
return (true, null);
}
catch (System.Exception ex)
{
_logger.LogError(ex, "Error EliminarAsync Canción ID: {Id}", id);
return (false, $"Error interno: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,15 @@
using GestionIntegral.Api.Dtos.Radios;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Radios
{
public interface ICancionService
{
Task<IEnumerable<CancionDto>> ObtenerTodasAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter);
Task<CancionDto?> ObtenerPorIdAsync(int id);
Task<(CancionDto? Cancion, string? Error)> CrearAsync(CreateCancionDto createDto, int idUsuario);
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateCancionDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
}
}

View File

@@ -0,0 +1,12 @@
using GestionIntegral.Api.Dtos.Radios;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; // Para IActionResult (o devolver byte[])
namespace GestionIntegral.Api.Services.Radios
{
public interface IRadioListaService
{
// Devuelve byte[] para el archivo y el nombre del archivo sugerido
Task<(byte[] FileContents, string ContentType, string FileName, string? Error)> GenerarListaRadioAsync(GenerarListaRadioRequestDto request);
}
}

View File

@@ -0,0 +1,15 @@
using GestionIntegral.Api.Dtos.Radios;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Radios
{
public interface IRitmoService
{
Task<IEnumerable<RitmoDto>> ObtenerTodosAsync(string? nombreFilter);
Task<RitmoDto?> ObtenerPorIdAsync(int id);
Task<(RitmoDto? Ritmo, string? Error)> CrearAsync(CreateRitmoDto createDto, int idUsuario);
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateRitmoDto updateDto, int idUsuario);
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
}
}

View File

@@ -0,0 +1,224 @@
using GestionIntegral.Api.Data.Repositories.Radios;
using GestionIntegral.Api.Dtos.Radios;
using GestionIntegral.Api.Models.Radios; // Para Cancion
using Microsoft.Extensions.Logging;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Radios
{
public class RadioListaService : IRadioListaService
{
private readonly ICancionRepository _cancionRepository;
// private readonly IRitmoRepository _ritmoRepository; // No se usa en la nueva lógica de generación de Excel
private readonly ILogger<RadioListaService> _logger;
public RadioListaService(
ICancionRepository cancionRepository,
// IRitmoRepository ritmoRepository, // Puede ser removido si no se usa en otro lado
ILogger<RadioListaService> logger)
{
_cancionRepository = cancionRepository;
// _ritmoRepository = ritmoRepository;
_logger = logger;
}
public async Task<(byte[] FileContents, string ContentType, string FileName, string? Error)> GenerarListaRadioAsync(GenerarListaRadioRequestDto request)
{
try
{
_logger.LogInformation("Iniciando generación de lista de radio para Mes: {Mes}, Año: {Anio}, Institución: {Institucion}, Radio: {Radio}",
request.Mes, request.Anio, request.Institucion, request.Radio);
var programacionParaExcel = new List<ProgramacionHorariaExcelDto>();
int diasEnMes = DateTime.DaysInMonth(request.Anio, request.Mes);
for (int dia = 1; dia <= diasEnMes; dia++)
{
int horaInicio = (request.Radio == "FM 99.1") ? 0 : 9;
int horaFin = 23;
for (int hora = horaInicio; hora <= horaFin; hora++)
{
int cantidadRegistros;
if (request.Radio == "FM 99.1")
{
cantidadRegistros = (hora == 23 || hora == 7 || hora == 15) ? 1 : 5;
}
else // FM 100.3
{
cantidadRegistros = (hora == 23 || hora == 15) ? 1 : 2;
}
if (cantidadRegistros > 0)
{
var cancionesSeleccionadas = await _cancionRepository.GetRandomCancionesAsync(cantidadRegistros);
foreach (var cancion in cancionesSeleccionadas)
{
programacionParaExcel.Add(new ProgramacionHorariaExcelDto
{
Dia = dia,
Hora = hora,
TituloObra = cancion.Tema,
CompositorAutor = cancion.CompositorAutor,
Interprete = cancion.Interprete,
Sello = cancion.Sello,
Album = cancion.Album
});
}
}
}
}
if (!programacionParaExcel.Any())
{
_logger.LogWarning("No se generaron datos para la lista de radio con los criterios: {@Request}", request);
return (Array.Empty<byte>(), "", "", "No se generaron datos para la lista con los criterios dados.");
}
string mesText = request.Mes.ToString("00");
string anioFullText = request.Anio.ToString();
string anioShortText = anioFullText.Length > 2 ? anioFullText.Substring(anioFullText.Length - 2) : anioFullText;
byte[] excelBytes = GenerarExcel(programacionParaExcel, request.Institucion, request.Radio, mesText, anioFullText, anioShortText);
string baseFileName;
if (request.Institucion == "AADI")
{
baseFileName = request.Radio == "FM 99.1" ? $"AADI-FM99.1-FM-{mesText}{anioShortText}" : $"AADI-FM100.3-FM-{mesText}{anioShortText}";
}
else // SADAIC
{
baseFileName = request.Radio == "FM 99.1" ? $"FM99.1-FM-{mesText}{anioShortText}" : $"FM100.3-FM-{mesText}{anioShortText}";
}
string excelFileNameInZip = $"{baseFileName}.xlsx";
string zipFileName = $"{baseFileName}.xlsx.zip"; // Para replicar nombre original del zip
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var excelEntry = archive.CreateEntry(excelFileNameInZip, CompressionLevel.Optimal);
using (var entryStream = excelEntry.Open())
{
await entryStream.WriteAsync(excelBytes, 0, excelBytes.Length);
}
}
_logger.LogInformation("Lista de radio generada y empaquetada en ZIP exitosamente: {ZipFileName}", zipFileName);
return (memoryStream.ToArray(), "application/zip", zipFileName, null);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al generar la lista de radio para Mes: {Mes}, Año: {Anio}, Institucion: {Institucion}, Radio: {Radio}", request.Mes, request.Anio, request.Institucion, request.Radio);
return (Array.Empty<byte>(), "", "", $"Error interno al generar la lista: {ex.Message}");
}
}
private byte[] GenerarExcel(List<ProgramacionHorariaExcelDto> programacion, string institucion, string radio, string mesText, string anioFullText, string anioShortText)
{
IWorkbook workbook = new XSSFWorkbook();
var coreProps = ((XSSFWorkbook)workbook).GetProperties().CoreProperties;
coreProps.Creator = "GestionIntegral"; // Puedes cambiarlo
coreProps.Title = $"Reporte {institucion} - {radio} - {mesText}/{anioFullText}";
// coreProps.Description = "Descripción del reporte"; // Opcional
// var extendedProps = ((XSSFWorkbook)workbook).GetProperties().ExtendedProperties.GetUnderlyingProperties();
// extendedProps.Application = "GestionIntegral System"; // Opcional
// extendedProps.AppVersion = "1.0.0"; // Opcional
string sheetName;
if (institucion == "AADI")
{
sheetName = radio == "FM 99.1" ? $"SA99{mesText}{anioShortText}" : $"SARE{mesText}{anioShortText}";
}
else // SADAIC
{
sheetName = radio == "FM 99.1" ? $"FM99{mesText}{anioShortText}" : $"FMRE{mesText}{anioShortText}";
}
ISheet sheet = workbook.CreateSheet(sheetName);
int currentRowIdx = 0;
if (institucion == "AADI")
{
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue(radio == "FM 99.1" ? "Nombre: FM LA 99.1" : "Nombre: FM 100.3 LA REDONDA");
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue("Localidad: La Plata");
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue("FM");
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue(radio == "FM 99.1" ? "Frecuencia: 99.1" : "Frecuencia: 100.3");
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue($"Mes: {mesText}/{anioFullText}");
}
IRow headerDataRow = sheet.CreateRow(currentRowIdx++);
headerDataRow.CreateCell(0).SetCellValue("Día");
headerDataRow.CreateCell(1).SetCellValue("Hora");
headerDataRow.CreateCell(2).SetCellValue("Título de la Obra");
headerDataRow.CreateCell(3).SetCellValue("Compositor-Autor");
headerDataRow.CreateCell(4).SetCellValue("Intérprete");
headerDataRow.CreateCell(5).SetCellValue("Sello");
headerDataRow.CreateCell(6).SetCellValue("Álbum");
IFont font = workbook.CreateFont();
font.FontHeightInPoints = 10;
font.FontName = "Arial";
ICellStyle cellStyle = workbook.CreateCellStyle();
cellStyle.SetFont(font);
foreach (var item in programacion)
{
IRow dataRow = sheet.CreateRow(currentRowIdx++);
dataRow.CreateCell(0).SetCellValue(item.Dia);
dataRow.CreateCell(1).SetCellValue(item.Hora);
dataRow.CreateCell(2).SetCellValue(item.TituloObra);
dataRow.CreateCell(3).SetCellValue(item.CompositorAutor);
dataRow.CreateCell(4).SetCellValue(item.Interprete);
dataRow.CreateCell(5).SetCellValue(item.Sello);
dataRow.CreateCell(6).SetCellValue(item.Album);
for (int i = 0; i < 7; i++)
{
ICell cell = dataRow.GetCell(i) ?? dataRow.CreateCell(i); // Asegurarse que la celda exista
cell.CellStyle = cellStyle;
}
}
sheet.SetColumnWidth(0, 4 * 256);
sheet.SetColumnWidth(1, 5 * 256);
sheet.SetColumnWidth(2, 25 * 256);
sheet.SetColumnWidth(3, 14 * 256);
sheet.SetColumnWidth(4, 11 * 256);
sheet.SetColumnWidth(5, 11 * 256);
sheet.SetColumnWidth(6, 30 * 256);
short rowHeight = 255; // 12.75 puntos
for (int i = 0; i < currentRowIdx; i++)
{
IRow row = sheet.GetRow(i) ?? sheet.CreateRow(i);
row.Height = rowHeight;
// Aplicar estilo a todas las celdas de las filas de encabezado también
if (i < (institucion == "AADI" ? 5 : 0) || i == (institucion == "AADI" ? 5 : 0)) // Filas de cabecera de AADI o la fila de títulos de columnas
{
// Iterar sobre las celdas que realmente existen o deberían existir
int lastCellNum = (institucion == "AADI" && i < 5) ? 1 : 7; // Cabecera AADI solo tiene 1 celda, títulos de datos tienen 7
for (int j = 0; j < lastCellNum; j++)
{
ICell cell = row.GetCell(j) ?? row.CreateCell(j);
cell.CellStyle = cellStyle;
}
}
}
using (var memoryStream = new MemoryStream())
{
workbook.Write(memoryStream, true); // El 'true' es para dejar el stream abierto si se necesita
return memoryStream.ToArray();
}
}
}
}

View File

@@ -0,0 +1,133 @@
using GestionIntegral.Api.Data.Repositories.Radios;
using GestionIntegral.Api.Dtos.Radios;
using GestionIntegral.Api.Models.Radios;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
// using GestionIntegral.Api.Data; // Para DbConnectionFactory si se usa transacción
// using System.Data; // Para IsolationLevel si se usa transacción
namespace GestionIntegral.Api.Services.Radios
{
public class RitmoService : IRitmoService
{
private readonly IRitmoRepository _ritmoRepository;
private readonly ILogger<RitmoService> _logger;
// private readonly DbConnectionFactory _connectionFactory; // Si se implementa historial
public RitmoService(IRitmoRepository ritmoRepository, ILogger<RitmoService> logger /*, DbConnectionFactory cf */)
{
_ritmoRepository = ritmoRepository;
_logger = logger;
// _connectionFactory = cf;
}
private RitmoDto MapToDto(Ritmo ritmo) => new RitmoDto
{
Id = ritmo.Id,
NombreRitmo = ritmo.NombreRitmo
};
public async Task<IEnumerable<RitmoDto>> ObtenerTodosAsync(string? nombreFilter)
{
var ritmos = await _ritmoRepository.GetAllAsync(nombreFilter);
return ritmos.Select(MapToDto);
}
public async Task<RitmoDto?> ObtenerPorIdAsync(int id)
{
var ritmo = await _ritmoRepository.GetByIdAsync(id);
return ritmo == null ? null : MapToDto(ritmo);
}
public async Task<(RitmoDto? Ritmo, string? Error)> CrearAsync(CreateRitmoDto createDto, int idUsuario)
{
if (await _ritmoRepository.ExistsByNameAsync(createDto.NombreRitmo))
{
return (null, "El nombre del ritmo ya existe.");
}
var nuevoRitmo = new Ritmo { NombreRitmo = createDto.NombreRitmo };
// Sin historial, la transacción es opcional para una sola operación,
// pero se deja la estructura por si se añade más lógica o historial.
// using var connection = _connectionFactory.CreateConnection();
// if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
// using var transaction = connection.BeginTransaction();
try
{
var ritmoCreado = await _ritmoRepository.CreateAsync(nuevoRitmo /*, idUsuario, transaction */);
if (ritmoCreado == null) return (null, "Error al crear el ritmo.");
// transaction.Commit();
_logger.LogInformation("Ritmo ID {Id} creado por Usuario ID {UserId}.", ritmoCreado.Id, idUsuario);
return (MapToDto(ritmoCreado), null);
}
catch (System.Exception ex)
{
// try { transaction.Rollback(); } catch {}
_logger.LogError(ex, "Error CrearAsync Ritmo: {NombreRitmo}", createDto.NombreRitmo);
return (null, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateRitmoDto updateDto, int idUsuario)
{
var ritmoExistente = await _ritmoRepository.GetByIdAsync(id);
if (ritmoExistente == null) return (false, "Ritmo no encontrado.");
if (ritmoExistente.NombreRitmo != updateDto.NombreRitmo && await _ritmoRepository.ExistsByNameAsync(updateDto.NombreRitmo, id))
{
return (false, "El nombre del ritmo ya existe para otro registro.");
}
ritmoExistente.NombreRitmo = updateDto.NombreRitmo;
// Sin historial, la transacción es opcional...
try
{
var actualizado = await _ritmoRepository.UpdateAsync(ritmoExistente /*, idUsuario, transaction */);
if (!actualizado) return (false, "Error al actualizar el ritmo.");
// transaction.Commit();
_logger.LogInformation("Ritmo ID {Id} actualizado por Usuario ID {UserId}.", id, idUsuario);
return (true, null);
}
catch (System.Exception ex)
{
// try { transaction.Rollback(); } catch {}
_logger.LogError(ex, "Error ActualizarAsync Ritmo ID: {Id}", id);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
{
var ritmoExistente = await _ritmoRepository.GetByIdAsync(id);
if (ritmoExistente == null) return (false, "Ritmo no encontrado.");
if (await _ritmoRepository.IsInUseAsync(id))
{
return (false, "No se puede eliminar. El ritmo está asignado a una o más canciones.");
}
// Sin historial, la transacción es opcional...
try
{
var eliminado = await _ritmoRepository.DeleteAsync(id /*, idUsuario, transaction */);
if (!eliminado) return (false, "Error al eliminar el ritmo.");
// transaction.Commit();
_logger.LogInformation("Ritmo ID {Id} eliminado por Usuario ID {UserId}.", id, idUsuario);
return (true, null);
}
catch (System.Exception ex)
{
// try { transaction.Rollback(); } catch {}
_logger.LogError(ex, "Error EliminarAsync Ritmo ID: {Id}", id);
return (false, $"Error interno: {ex.Message}");
}
}
}
}