Feat: Implementar modificación de Tiradas y mejorar UX/UI
All checks were successful
Optimized Build and Deploy / remote-build-and-deploy (push) Successful in 12m54s

**Backend:**
- Se ha añadido el endpoint `PUT /api/tiradas` para manejar la modificación de una Tirada, identificada por su clave única (fecha, idPublicacion, idPlanta).
- Se implementó un mecanismo de actualización granular para las secciones de la tirada (`bob_RegPublicaciones`), reemplazando la estrategia anterior de "eliminar todo y recrear".
  - La nueva lógica reconcilia el estado entrante con el de la base de datos, realizando operaciones individuales de `INSERT`, `UPDATE` y `DELETE` para cada sección.
  - Esto mejora significativamente el rendimiento y proporciona un historial de auditoría mucho más preciso.
- Se añadieron los DTOs `UpdateTiradaRequestDto` y `UpdateDetalleSeccionTiradaDto` para soportar el nuevo payload de modificación.
- Se expandieron los repositorios `IRegPublicacionSeccionRepository` y `IPubliSeccionRepository` con métodos para operaciones granulares (`UpdateAsync`, `DeleteByIdAsync`, `GetByIdsAndPublicacionAsync`).

**Frontend:**
- El componente `TiradaFormModal` ha sido refactorizado para funcionar tanto en modo "Crear" como en modo "Editar", recibiendo una prop `tiradaToEdit`.
- Se implementó una lógica de carga asíncrona robusta que obtiene los datos completos de una tirada antes de abrir el modal en modo edición.

**Mejoras de UI/UX:**
- Se ha rediseñado el layout de la lista de tiradas en `GestionarTiradasPage`:
  - Los botones de acción (Editar, Borrar) y los datos clave (chips de ejemplares y páginas) ahora se encuentran en una cabecera estática.
  - Estos elementos permanecen fijos en la parte superior y no se desplazan al expandir el acordeón, mejorando la consistencia visual.
- Se ha mejorado la tabla de secciones dentro del `TiradaFormModal`:
  - El botón "+ AGREGAR SECCIÓN" ahora está fijo en la parte inferior de la tabla, permaneciendo siempre visible incluso cuando la lista de secciones tiene scroll.
  - Al agregar una nueva sección, la lista se desplaza automáticamente hacia abajo para mostrar la nueva fila.
This commit is contained in:
2025-07-23 14:05:58 -03:00
parent 052141a45b
commit 28c1b88a92
14 changed files with 884 additions and 320 deletions

View File

@@ -10,6 +10,7 @@ namespace GestionIntegral.Api.Services.Impresion
{
Task<IEnumerable<TiradaDto>> ObtenerTiradasAsync(DateTime? fecha, int? idPublicacion, int? idPlanta);
Task<(TiradaDto? TiradaCreada, string? Error)> RegistrarTiradaCompletaAsync(CreateTiradaRequestDto createDto, int idUsuario);
Task<(TiradaDto? TiradaActualizada, string? Error)> ModificarTiradaCompletaAsync(DateTime fecha, int idPublicacion, int idPlanta, UpdateTiradaRequestDto updateDto, int idUsuario); // <-- AÑADIDO
Task<(bool Exito, string? Error)> EliminarTiradaCompletaAsync(DateTime fecha, int idPublicacion, int idPlanta, int idUsuario);
Task<IEnumerable<RegTiradaHistorialDto>> ObtenerRegTiradasHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,

View File

@@ -182,6 +182,104 @@ namespace GestionIntegral.Api.Services.Impresion
}
}
public async Task<(TiradaDto? TiradaActualizada, string? Error)> ModificarTiradaCompletaAsync(DateTime fecha, int idPublicacion, int idPlanta, UpdateTiradaRequestDto updateDto, int idUsuario)
{
// 1. Validar que la tirada a modificar exista.
var tiradaExistente = await _regTiradaRepository.GetByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta);
if (tiradaExistente == null)
{
return (null, "No se encontró la tirada que intenta modificar.");
}
// 2. Validaciones de DTO (secciones válidas, etc.)
var idsSeccionesUnicasDto = updateDto.Secciones.Select(s => s.IdSeccion).Distinct();
if (idsSeccionesUnicasDto.Any()) // Solo validar si se enviaron secciones
{
var seccionesValidasDb = await _publiSeccionRepository.GetByIdsAndPublicacionAsync(idsSeccionesUnicasDto, idPublicacion, soloActivas: true);
if (seccionesValidasDb.Count() != idsSeccionesUnicasDto.Count())
{
var idsEncontrados = seccionesValidasDb.Select(s => s.IdSeccion).ToHashSet();
var idsFaltantes = string.Join(", ", idsSeccionesUnicasDto.Where(id => !idsEncontrados.Contains(id)));
return (null, $"Las siguientes secciones no son válidas, no pertenecen a la publicación o no están activas: {idsFaltantes}.");
}
}
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
{
// 3. Actualizar registro principal (bob_RegTiradas) si cambió el número de ejemplares
if (tiradaExistente.Ejemplares != updateDto.Ejemplares)
{
tiradaExistente.Ejemplares = updateDto.Ejemplares;
bool actualizado = await _regTiradaRepository.UpdateAsync(tiradaExistente, idUsuario, transaction);
if (!actualizado) throw new DataException("No se pudo actualizar el registro principal de la tirada.");
}
// 4. Lógica de reconciliación de secciones
// 4.1. Obtener estado actual de las secciones en la BBDD
var seccionesActualesDb = await _regPublicacionSeccionRepository.GetByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta);
// 4.2. Procesar Adiciones y Actualizaciones
foreach (var seccionDto in updateDto.Secciones)
{
if (seccionDto.IdRegPublicacionSeccion == 0) // Es una nueva sección
{
var nuevaRegPubSeccion = new RegPublicacionSeccion
{
IdPublicacion = idPublicacion,
IdSeccion = seccionDto.IdSeccion,
CantPag = seccionDto.CantPag,
Fecha = fecha.Date,
IdPlanta = idPlanta
};
await _regPublicacionSeccionRepository.CreateAsync(nuevaRegPubSeccion, idUsuario, transaction);
}
else // Es una sección existente, verificar si hay cambios
{
var seccionDb = seccionesActualesDb.FirstOrDefault(s => s.IdTirada == seccionDto.IdRegPublicacionSeccion);
if (seccionDb == null) throw new InvalidOperationException($"La sección con ID de registro {seccionDto.IdRegPublicacionSeccion} no pertenece a esta tirada.");
if (seccionDb.CantPag != seccionDto.CantPag) // Solo actualizamos si cambió la cantidad de páginas
{
seccionDb.CantPag = seccionDto.CantPag;
await _regPublicacionSeccionRepository.UpdateAsync(seccionDb, idUsuario, transaction);
}
}
}
// 4.3. Procesar Eliminaciones
var idsSeccionesEntrantes = updateDto.Secciones
.Where(s => s.IdRegPublicacionSeccion != 0)
.Select(s => s.IdRegPublicacionSeccion)
.ToHashSet();
var seccionesAEliminar = seccionesActualesDb
.Where(s => !idsSeccionesEntrantes.Contains(s.IdTirada));
foreach (var seccionParaBorrar in seccionesAEliminar)
{
await _regPublicacionSeccionRepository.DeleteByIdAsync(seccionParaBorrar.IdTirada, idUsuario, transaction);
}
transaction.Commit();
_logger.LogInformation("Tirada completa modificada (granular) para Pub ID {IdPub}, Fecha {Fecha}, por Usuario ID {UserId}.", idPublicacion, fecha.Date, idUsuario);
// 5. Devolver el DTO actualizado (tendríamos que volver a consultar o construirlo)
var tiradaActualizadaResult = await ObtenerTiradasAsync(fecha.Date, idPublicacion, idPlanta);
return (tiradaActualizadaResult.FirstOrDefault(), null);
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error en ModificarTiradaCompletaAsync (granular) para Pub ID {IdPub}, Fecha {Fecha}", idPublicacion, fecha);
return (null, $"Error interno al modificar la tirada: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarTiradaCompletaAsync(DateTime fecha, int idPublicacion, int idPlanta, int idUsuario)
{
// Verificar que la tirada principal exista