Backend API:
- Recargos por Zona (`dist_RecargoZona`):
  - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Endpoints anidados bajo `/publicaciones/{idPublicacion}/recargos`.
  - Lógica de negocio para vigencias (cierre/reapertura de períodos).
  - Auditoría en `dist_RecargoZona_H`.
- Porcentajes de Pago Distribuidores (`dist_PorcPago`):
  - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajespago`.
  - Lógica de negocio para vigencias.
  - Auditoría en `dist_PorcPago_H`.
- Porcentajes/Montos Pago Canillitas (`dist_PorcMonPagoCanilla`):
  - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajesmoncanilla`.
  - Lógica de negocio para vigencias.
  - Auditoría en `dist_PorcMonPagoCanilla_H`.
- Secciones de Publicación (`dist_dtPubliSecciones`):
  - CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Endpoints anidados bajo `/publicaciones/{idPublicacion}/secciones`.
  - Auditoría en `dist_dtPubliSecciones_H`.
- Entradas/Salidas Distribuidores (`dist_EntradasSalidas`):
  - Implementado backend (Modelos, DTOs, Repositorio, Servicio, Controlador).
  - Lógica para determinar precios/recargos/porcentajes aplicables.
  - Cálculo de monto y afectación de saldos de distribuidores en `cue_Saldos`.
  - Auditoría en `dist_EntradasSalidas_H`.
- Correcciones de Mapeo Dapper:
  - Aplicados alias explícitos en repositorios de RecargoZona, PorcPago, PorcMonCanilla, PubliSeccion,
    Canilla, Distribuidor y Precio para asegurar mapeo correcto de IDs y columnas.
Frontend React:
- Recargos por Zona:
  - `recargoZonaService.ts`.
  - `RecargoZonaFormModal.tsx` para crear/editar períodos de recargos.
  - `GestionarRecargosPublicacionPage.tsx` para listar y gestionar recargos por publicación.
- Porcentajes de Pago Distribuidores:
  - `porcPagoService.ts`.
  - `PorcPagoFormModal.tsx`.
  - `GestionarPorcentajesPagoPage.tsx`.
- Porcentajes/Montos Pago Canillitas:
  - `porcMonCanillaService.ts`.
  - `PorcMonCanillaFormModal.tsx`.
  - `GestionarPorcMonCanillaPage.tsx`.
- Secciones de Publicación:
  - `publiSeccionService.ts`.
  - `PubliSeccionFormModal.tsx`.
  - `GestionarSeccionesPublicacionPage.tsx`.
- Navegación:
  - Actualizadas rutas y menús para acceder a la gestión de recargos, porcentajes (dist. y canillita) y secciones desde la vista de una publicación.
- Layout:
  - Uso consistente de `Box` con Flexbox en lugar de `Grid` en nuevos modales y páginas para evitar errores de tipo.
		
	
		
			
				
	
	
		
			240 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			240 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using GestionIntegral.Api.Data;
 | |
| using GestionIntegral.Api.Data.Repositories.Distribucion;
 | |
| using GestionIntegral.Api.Dtos.Distribucion;
 | |
| using GestionIntegral.Api.Models.Distribucion;
 | |
| using Microsoft.Extensions.Logging;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Data;
 | |
| using System.Linq;
 | |
| using System.Threading.Tasks;
 | |
| 
 | |
| namespace GestionIntegral.Api.Services.Distribucion
 | |
| {
 | |
|     public class PorcMonCanillaService : IPorcMonCanillaService
 | |
|     {
 | |
|         private readonly IPorcMonCanillaRepository _porcMonCanillaRepository;
 | |
|         private readonly IPublicacionRepository _publicacionRepository;
 | |
|         private readonly ICanillaRepository _canillaRepository; // Para validar IdCanilla y obtener nombre
 | |
|         private readonly DbConnectionFactory _connectionFactory;
 | |
|         private readonly ILogger<PorcMonCanillaService> _logger;
 | |
| 
 | |
|         public PorcMonCanillaService(
 | |
|             IPorcMonCanillaRepository porcMonCanillaRepository,
 | |
|             IPublicacionRepository publicacionRepository,
 | |
|             ICanillaRepository canillaRepository,
 | |
|             DbConnectionFactory connectionFactory,
 | |
|             ILogger<PorcMonCanillaService> logger)
 | |
|         {
 | |
|             _porcMonCanillaRepository = porcMonCanillaRepository;
 | |
|             _publicacionRepository = publicacionRepository;
 | |
|             _canillaRepository = canillaRepository;
 | |
|             _connectionFactory = connectionFactory;
 | |
|             _logger = logger;
 | |
|         }
 | |
| 
 | |
|         private PorcMonCanillaDto MapToDto((PorcMonCanilla Item, string NomApeCanilla) data) => new PorcMonCanillaDto
 | |
|         {
 | |
|             IdPorcMon = data.Item.IdPorcMon,
 | |
|             IdPublicacion = data.Item.IdPublicacion,
 | |
|             IdCanilla = data.Item.IdCanilla,
 | |
|             NomApeCanilla = data.NomApeCanilla,
 | |
|             VigenciaD = data.Item.VigenciaD.ToString("yyyy-MM-dd"),
 | |
|             VigenciaH = data.Item.VigenciaH?.ToString("yyyy-MM-dd"),
 | |
|             PorcMon = data.Item.PorcMon,
 | |
|             EsPorcentaje = data.Item.EsPorcentaje
 | |
|         };
 | |
|         
 | |
|         private async Task<PorcMonCanillaDto?> MapToDtoWithLookup(PorcMonCanilla? item)
 | |
|         {
 | |
|             if (item == null) return null; // Si el item es null, devuelve null
 | |
| 
 | |
|             var canillaData = await _canillaRepository.GetByIdAsync(item.IdCanilla);
 | |
|             return new PorcMonCanillaDto
 | |
|             {
 | |
|                 IdPorcMon = item.IdPorcMon,
 | |
|                 IdPublicacion = item.IdPublicacion,
 | |
|                 IdCanilla = item.IdCanilla,
 | |
|                 NomApeCanilla = canillaData.Canilla?.NomApe ?? "Canillita Desconocido",
 | |
|                 VigenciaD = item.VigenciaD.ToString("yyyy-MM-dd"),
 | |
|                 VigenciaH = item.VigenciaH?.ToString("yyyy-MM-dd"),
 | |
|                 PorcMon = item.PorcMon,
 | |
|                 EsPorcentaje = item.EsPorcentaje
 | |
|             };
 | |
|         }
 | |
| 
 | |
| 
 | |
|         public async Task<IEnumerable<PorcMonCanillaDto>> ObtenerPorPublicacionIdAsync(int idPublicacion)
 | |
|         {
 | |
|             var data = await _porcMonCanillaRepository.GetByPublicacionIdAsync(idPublicacion);
 | |
|             // Filtrar los nulos que MapToDto podría devolver (aunque no debería en este caso si GetAllWithProfileNameAsync no devuelve usuarios nulos en la tupla)
 | |
|             return data.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!);
 | |
|         }
 | |
| 
 | |
|         public async Task<PorcMonCanillaDto?> ObtenerPorIdAsync(int idPorcMon)
 | |
|         {
 | |
|             var item = await _porcMonCanillaRepository.GetByIdAsync(idPorcMon);
 | |
|             return await MapToDtoWithLookup(item);
 | |
|         }
 | |
| 
 | |
|         public async Task<(PorcMonCanillaDto? Item, string? Error)> CrearAsync(CreatePorcMonCanillaDto createDto, int idUsuario)
 | |
|         {
 | |
|             if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
 | |
|                 return (null, "La publicación especificada no existe.");
 | |
|             
 | |
|             var canillaData = await _canillaRepository.GetByIdAsync(createDto.IdCanilla);
 | |
|             if (canillaData.Canilla == null) // GetByIdAsync devuelve una tupla
 | |
|                 return (null, "El canillita especificado no existe o no está activo.");
 | |
|             // Validar que solo canillitas accionistas pueden tener porcentaje/monto (o la regla de negocio que aplique)
 | |
|             // Por ejemplo, si solo los Accionistas pueden tener esta configuración:
 | |
|             // if (!canillaData.Canilla.Accionista) {
 | |
|             //     return (null, "Solo los canillitas accionistas pueden tener un porcentaje/monto de pago configurado.");
 | |
|             // }
 | |
| 
 | |
| 
 | |
|             var itemActivo = await _porcMonCanillaRepository.GetActiveByPublicacionCanillaAndDateAsync(createDto.IdPublicacion, createDto.IdCanilla, createDto.VigenciaD.Date);
 | |
|             if (itemActivo != null)
 | |
|             {
 | |
|                 return (null, $"Ya existe un porcentaje/monto activo para esta publicación y canillita en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Cierre el período anterior.");
 | |
|             }
 | |
| 
 | |
|             var nuevoItem = new PorcMonCanilla
 | |
|             {
 | |
|                 IdPublicacion = createDto.IdPublicacion,
 | |
|                 IdCanilla = createDto.IdCanilla,
 | |
|                 VigenciaD = createDto.VigenciaD.Date,
 | |
|                 VigenciaH = null,
 | |
|                 PorcMon = createDto.PorcMon,
 | |
|                 EsPorcentaje = createDto.EsPorcentaje
 | |
|             };
 | |
| 
 | |
|             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 itemAnterior = await _porcMonCanillaRepository.GetPreviousActiveAsync(createDto.IdPublicacion, createDto.IdCanilla, nuevoItem.VigenciaD, transaction);
 | |
|                 if (itemAnterior != null)
 | |
|                 {
 | |
|                      if (itemAnterior.VigenciaD.Date >= nuevoItem.VigenciaD.Date)
 | |
|                     {
 | |
|                         transaction.Rollback();
 | |
|                         return (null, $"La fecha de inicio ({nuevoItem.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último período vigente ({itemAnterior.VigenciaD:dd/MM/yyyy}).");
 | |
|                     }
 | |
|                     itemAnterior.VigenciaH = nuevoItem.VigenciaD.AddDays(-1);
 | |
|                     await _porcMonCanillaRepository.UpdateAsync(itemAnterior, idUsuario, transaction);
 | |
|                 }
 | |
| 
 | |
|                 var itemCreado = await _porcMonCanillaRepository.CreateAsync(nuevoItem, idUsuario, transaction);
 | |
|                 if (itemCreado == null) throw new DataException("Error al crear el registro.");
 | |
| 
 | |
|                 transaction.Commit();
 | |
|                 _logger.LogInformation("PorcMonCanilla ID {Id} creado por Usuario ID {UserId}.", itemCreado.IdPorcMon, idUsuario);
 | |
|                 return (await MapToDtoWithLookup(itemCreado), null);
 | |
|             }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 try { transaction.Rollback(); } catch { }
 | |
|                 _logger.LogError(ex, "Error CrearAsync PorcMonCanilla para Pub ID {IdPub}, Canilla ID {IdCan}", createDto.IdPublicacion, createDto.IdCanilla);
 | |
|                 return (null, $"Error interno: {ex.Message}");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPorcMon, UpdatePorcMonCanillaDto updateDto, int idUsuario)
 | |
|         {
 | |
|             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 itemExistente = await _porcMonCanillaRepository.GetByIdAsync(idPorcMon);
 | |
|                 if (itemExistente == null) return (false, "Registro de porcentaje/monto no encontrado.");
 | |
| 
 | |
|                 if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < itemExistente.VigenciaD.Date)
 | |
|                     return (false, "Vigencia Hasta no puede ser anterior a Vigencia Desde.");
 | |
|                 
 | |
|                 if (updateDto.VigenciaH.HasValue)
 | |
|                 {
 | |
|                      var itemsPubCanillaData = await _porcMonCanillaRepository.GetByPublicacionIdAsync(itemExistente.IdPublicacion);
 | |
|                      var itemsPosteriores = itemsPubCanillaData
 | |
|                         .Where(i => i.Item.IdCanilla == itemExistente.IdCanilla &&
 | |
|                                     i.Item.IdPorcMon != idPorcMon &&
 | |
|                                     i.Item.VigenciaD.Date <= updateDto.VigenciaH.Value.Date &&
 | |
|                                     i.Item.VigenciaD.Date > itemExistente.VigenciaD.Date);
 | |
|                      if(itemsPosteriores.Any())
 | |
|                      {
 | |
|                         return (false, "No se puede cerrar este período porque existen configuraciones posteriores para este canillita que se solaparían.");
 | |
|                      }
 | |
|                 }
 | |
| 
 | |
|                 itemExistente.PorcMon = updateDto.PorcMon;
 | |
|                 itemExistente.EsPorcentaje = updateDto.EsPorcentaje;
 | |
|                 if (updateDto.VigenciaH.HasValue)
 | |
|                 {
 | |
|                     itemExistente.VigenciaH = updateDto.VigenciaH.Value.Date;
 | |
|                 }
 | |
| 
 | |
|                 var actualizado = await _porcMonCanillaRepository.UpdateAsync(itemExistente, idUsuario, transaction);
 | |
|                 if (!actualizado) throw new DataException("Error al actualizar registro.");
 | |
| 
 | |
|                 transaction.Commit();
 | |
|                 _logger.LogInformation("PorcMonCanilla ID {Id} actualizado por Usuario ID {UserId}.", idPorcMon, idUsuario);
 | |
|                 return (true, null);
 | |
|             }
 | |
|             catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Registro no encontrado."); }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 try { transaction.Rollback(); } catch { }
 | |
|                 _logger.LogError(ex, "Error ActualizarAsync PorcMonCanilla ID: {Id}", idPorcMon);
 | |
|                 return (false, $"Error interno: {ex.Message}");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public async Task<(bool Exito, string? Error)> EliminarAsync(int idPorcMon, int idUsuario)
 | |
|         {
 | |
|             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 itemAEliminar = await _porcMonCanillaRepository.GetByIdAsync(idPorcMon);
 | |
|                 if (itemAEliminar == null) return (false, "Registro no encontrado.");
 | |
| 
 | |
|                 if (itemAEliminar.VigenciaH == null) 
 | |
|                 {
 | |
|                     var todosItemsPubCanillaData = await _porcMonCanillaRepository.GetByPublicacionIdAsync(itemAEliminar.IdPublicacion);
 | |
|                     var todosItemsPubCanilla = todosItemsPubCanillaData
 | |
|                                                 .Where(i => i.Item.IdCanilla == itemAEliminar.IdCanilla)
 | |
|                                                 .Select(i => i.Item)
 | |
|                                                 .OrderByDescending(i => i.VigenciaD).ToList();
 | |
|                     
 | |
|                     var indiceActual = todosItemsPubCanilla.FindIndex(i => i.IdPorcMon == idPorcMon);
 | |
|                     if(indiceActual != -1 && (indiceActual + 1) < todosItemsPubCanilla.Count)
 | |
|                     {
 | |
|                         var itemAnteriorDirecto = todosItemsPubCanilla[indiceActual + 1];
 | |
|                         if(itemAnteriorDirecto.VigenciaH.HasValue && 
 | |
|                            itemAnteriorDirecto.VigenciaH.Value.Date == itemAEliminar.VigenciaD.AddDays(-1).Date)
 | |
|                         {
 | |
|                             itemAnteriorDirecto.VigenciaH = null;
 | |
|                             await _porcMonCanillaRepository.UpdateAsync(itemAnteriorDirecto, idUsuario, transaction);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 var eliminado = await _porcMonCanillaRepository.DeleteAsync(idPorcMon, idUsuario, transaction);
 | |
|                 if (!eliminado) throw new DataException("Error al eliminar registro.");
 | |
| 
 | |
|                 transaction.Commit();
 | |
|                 _logger.LogInformation("PorcMonCanilla ID {Id} eliminado por Usuario ID {UserId}.", idPorcMon, idUsuario);
 | |
|                 return (true, null);
 | |
|             }
 | |
|             catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Registro no encontrado."); }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 try { transaction.Rollback(); } catch { }
 | |
|                 _logger.LogError(ex, "Error EliminarAsync PorcMonCanilla ID: {Id}", idPorcMon);
 | |
|                 return (false, $"Error interno: {ex.Message}");
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| } |