Backend API: - Implementado CRUD completo para Empresas (DE001-DE004): - EmpresaRepository, EmpresaService, EmpresasController. - Lógica de creación/eliminación de saldos iniciales en EmpresaService. - Transacciones y registro en tablas _H. - Verificación de permisos específicos. - Implementado CRUD completo para Plantas de Impresión (IP001-IP004): - PlantaRepository, PlantaService, PlantasController. - Transacciones y registro en tablas _H. - Verificación de permisos. - Implementado CRUD completo para Tipos de Bobina (IB006-IB009): - TipoBobinaRepository, TipoBobinaService, TiposBobinaController. - Transacciones y registro en tablas _H. - Verificación de permisos. - Implementado CRUD completo para Estados de Bobina (IB010-IB013): - EstadoBobinaRepository, EstadoBobinaService, EstadosBobinaController. - Transacciones y registro en tablas _H. - Verificación de permisos. Frontend React: - Módulo Empresas: - empresaService.ts para interactuar con la API. - EmpresaFormModal.tsx para crear/editar empresas. - GestionarEmpresasPage.tsx con tabla, filtro, paginación y menú de acciones. - Integración con el hook usePermissions para control de acceso. - Módulo Plantas de Impresión: - plantaService.ts. - PlantaFormModal.tsx. - GestionarPlantasPage.tsx con tabla, filtro, paginación y acciones. - Integración con usePermissions. - Módulo Tipos de Bobina: - tipoBobinaService.ts. - TipoBobinaFormModal.tsx. - GestionarTiposBobinaPage.tsx con tabla, filtro, paginación y acciones. - Integración con usePermissions. - Módulo Estados de Bobina: - estadoBobinaService.ts. - EstadoBobinaFormModal.tsx. - GestionarEstadosBobinaPage.tsx con tabla, filtro, paginación y acciones. - Integración con usePermissions. - Navegación: - Añadidas sub-pestañas y rutas para los nuevos módulos dentro de "Distribución" (Empresas) e "Impresión" (Plantas, Tipos Bobina, Estados Bobina). - Creado ImpresionIndexPage.tsx para la navegación interna del módulo de Impresión. Correcciones: - Corregido el uso de CommitAsync/RollbackAsync a Commit/Rollback síncronos en PlantaService.cs debido a que IDbTransaction no los soporta asíncronamente.
		
			
				
	
	
		
			238 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using GestionIntegral.Api.Dtos.Empresas; // Para los DTOs (EmpresaDto, CreateEmpresaDto, UpdateEmpresaDto)
 | |
| using Microsoft.Extensions.Logging; // Para ILogger
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using System.Threading.Tasks;
 | |
| using System.Data;
 | |
| using GestionIntegral.Api.Data;
 | |
| using GestionIntegral.Api.Models.Distribucion;
 | |
| using GestionIntegral.Api.Data.Repositories.Distribucion;
 | |
| using GestionIntegral.Api.Data.Repositories.Contables; // Para IDbTransaction, ConnectionState
 | |
| 
 | |
| namespace GestionIntegral.Api.Services.Distribucion
 | |
| {
 | |
|     public class EmpresaService : IEmpresaService
 | |
|     {
 | |
|         private readonly IEmpresaRepository _empresaRepository;
 | |
|         private readonly ISaldoRepository _saldoRepository;
 | |
|         private readonly DbConnectionFactory _connectionFactory; // Para manejar la transacción
 | |
|         private readonly ILogger<EmpresaService> _logger;
 | |
| 
 | |
|         public EmpresaService(
 | |
|             IEmpresaRepository empresaRepository,
 | |
|             ISaldoRepository saldoRepository,
 | |
|             DbConnectionFactory connectionFactory,
 | |
|             ILogger<EmpresaService> logger)
 | |
|         {
 | |
|             _empresaRepository = empresaRepository;
 | |
|             _saldoRepository = saldoRepository;
 | |
|             _connectionFactory = connectionFactory;
 | |
|             _logger = logger;
 | |
|         }
 | |
| 
 | |
|         public async Task<IEnumerable<EmpresaDto>> ObtenerTodasAsync(string? nombreFilter, string? detalleFilter)
 | |
|         {
 | |
|             // El repositorio ya devuelve solo las activas si es necesario
 | |
|             var empresas = await _empresaRepository.GetAllAsync(nombreFilter, detalleFilter);
 | |
|             // Mapeo Entidad -> DTO
 | |
|             return empresas.Select(e => new EmpresaDto
 | |
|             {
 | |
|                 IdEmpresa = e.IdEmpresa,
 | |
|                 Nombre = e.Nombre,
 | |
|                 Detalle = e.Detalle
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         public async Task<EmpresaDto?> ObtenerPorIdAsync(int id)
 | |
|         {
 | |
|              // El repositorio ya devuelve solo las activas si es necesario
 | |
|             var empresa = await _empresaRepository.GetByIdAsync(id);
 | |
|             if (empresa == null) return null;
 | |
|             // Mapeo Entidad -> DTO
 | |
|             return new EmpresaDto
 | |
|             {
 | |
|                 IdEmpresa = empresa.IdEmpresa,
 | |
|                 Nombre = empresa.Nombre,
 | |
|                 Detalle = empresa.Detalle
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         public async Task<(EmpresaDto? Empresa, string? Error)> CrearAsync(CreateEmpresaDto createDto, int idUsuario)
 | |
|         {
 | |
|             // Validación de negocio: Nombre duplicado
 | |
|             if (await _empresaRepository.ExistsByNameAsync(createDto.Nombre))
 | |
|             {
 | |
|                 return (null, "El nombre de la empresa ya existe.");
 | |
|             }
 | |
| 
 | |
|             var nuevaEmpresa = new Empresa
 | |
|             {
 | |
|                 Nombre = createDto.Nombre,
 | |
|                 Detalle = createDto.Detalle
 | |
|             };
 | |
| 
 | |
|             // --- Transacción ---
 | |
|             using (var connection = _connectionFactory.CreateConnection())
 | |
|             {
 | |
|                 // Abrir conexión de forma asíncrona
 | |
|                 if (connection is System.Data.Common.DbConnection dbConnection)
 | |
|                 {
 | |
|                     await dbConnection.OpenAsync();
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     connection.Open(); // Fallback síncrono
 | |
|                 }
 | |
| 
 | |
|                 using (var transaction = connection.BeginTransaction())
 | |
|                 {
 | |
|                     try
 | |
|                     {
 | |
|                         // 1. Crear Empresa (Repo maneja su historial dentro de esta transacción)
 | |
|                         var empresaCreada = await _empresaRepository.CreateAsync(nuevaEmpresa, idUsuario, transaction);
 | |
|                         if (empresaCreada == null)
 | |
|                         {
 | |
|                             throw new InvalidOperationException("No se pudo crear la empresa en el repositorio.");
 | |
|                         }
 | |
| 
 | |
|                         // 2. Obtener IDs de Distribuidores
 | |
|                         var distribuidoresIds = await _saldoRepository.GetAllDistribuidorIdsAsync(); // No necesita transacción si solo lee
 | |
| 
 | |
|                         // 3. Crear Saldos Iniciales (CERO) para cada distribuidor en esta nueva empresa
 | |
|                         foreach (var idDistribuidor in distribuidoresIds)
 | |
|                         {
 | |
|                             bool saldoCreado = await _saldoRepository.CreateSaldoInicialAsync("Distribuidores", idDistribuidor, empresaCreada.IdEmpresa, transaction);
 | |
|                             if (!saldoCreado)
 | |
|                             {
 | |
|                                 throw new InvalidOperationException($"Falló al crear saldo inicial para distribuidor {idDistribuidor} y nueva empresa {empresaCreada.IdEmpresa}.");
 | |
|                             }
 | |
|                              _logger.LogInformation("Saldo inicial creado para Distribuidor ID {IdDistribuidor}, Empresa ID {IdEmpresa}", idDistribuidor, empresaCreada.IdEmpresa);
 | |
|                         }
 | |
| 
 | |
|                         transaction.Commit();
 | |
| 
 | |
|                         var empresaDto = new EmpresaDto
 | |
|                         {
 | |
|                             IdEmpresa = empresaCreada.IdEmpresa,
 | |
|                             Nombre = empresaCreada.Nombre,
 | |
|                             Detalle = empresaCreada.Detalle
 | |
|                         };
 | |
|                         _logger.LogInformation("Empresa ID {IdEmpresa} creada exitosamente por Usuario ID {IdUsuario}.", empresaCreada.IdEmpresa, idUsuario);
 | |
|                         return (empresaDto, null); // Éxito
 | |
|                     }
 | |
|                     catch (Exception ex)
 | |
|                     {
 | |
|                         try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error al intentar hacer rollback en CrearAsync Empresa."); }
 | |
|                         _logger.LogError(ex, "Error en transacción CrearAsync para Empresa. Nombre: {Nombre}", createDto.Nombre);
 | |
|                         return (null, "Error interno al procesar la creación de la empresa.");
 | |
|                     }
 | |
|                 } // La transacción se dispone aquí (y cierra la conexión si no hubo commit/rollback explícito, aunque ya lo hacemos)
 | |
|             } // La conexión se cierra/dispone aquí
 | |
|             // --- Fin Transacción ---
 | |
|         }
 | |
| 
 | |
|         public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateEmpresaDto updateDto, int idUsuario)
 | |
|         {
 | |
|              var empresaExistente = await _empresaRepository.GetByIdAsync(id);
 | |
|              if (empresaExistente == null)
 | |
|              {
 | |
|                  return (false, "Empresa no encontrada.");
 | |
|              }
 | |
| 
 | |
|              if (await _empresaRepository.ExistsByNameAsync(updateDto.Nombre, id))
 | |
|              {
 | |
|                  return (false, "El nombre de la empresa ya existe para otro registro.");
 | |
|              }
 | |
| 
 | |
|              empresaExistente.Nombre = updateDto.Nombre;
 | |
|              empresaExistente.Detalle = updateDto.Detalle;
 | |
| 
 | |
|             // --- Transacción ---
 | |
|             using (var connection = _connectionFactory.CreateConnection())
 | |
|             {
 | |
|                  if (connection is System.Data.Common.DbConnection dbConnection) { await dbConnection.OpenAsync(); } else { connection.Open(); }
 | |
|                  using (var transaction = connection.BeginTransaction())
 | |
|                 {
 | |
|                     try
 | |
|                     {
 | |
|                          var actualizado = await _empresaRepository.UpdateAsync(empresaExistente, idUsuario, transaction);
 | |
|                          if (!actualizado)
 | |
|                          {
 | |
|                              throw new InvalidOperationException("La actualización en el repositorio de empresas devolvió false.");
 | |
|                          }
 | |
|                          transaction.Commit();
 | |
|                          _logger.LogInformation("Empresa ID {IdEmpresa} actualizada exitosamente por Usuario ID {IdUsuario}.", id, idUsuario);
 | |
|                          return (true, null); // Éxito
 | |
|                     }
 | |
|                     catch (Exception ex)
 | |
|                     {
 | |
|                         try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error al intentar hacer rollback en ActualizarAsync Empresa."); }
 | |
|                          _logger.LogError(ex, "Error en transacción ActualizarAsync para Empresa ID: {Id}", id);
 | |
|                         return (false, "Error interno al actualizar la empresa.");
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             // --- Fin Transacción ---
 | |
|         }
 | |
| 
 | |
|         public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
 | |
|         {
 | |
|             // Primero verificamos si existe, incluso inactiva, para evitar errores 404 si no existe
 | |
|             var empresaExistente = await _empresaRepository.GetByIdAsync(id);
 | |
|             if (empresaExistente == null)
 | |
|             {
 | |
|                 return (false, "Empresa no encontrada.");
 | |
|             }
 | |
| 
 | |
|             // Validación: ¿Está en uso?
 | |
|             if (await _empresaRepository.IsInUseAsync(id))
 | |
|             {
 | |
|                 return (false, "No se puede eliminar. Existen publicaciones relacionadas a la empresa.");
 | |
|             }
 | |
| 
 | |
|              // --- Transacción ---
 | |
|             using (var connection = _connectionFactory.CreateConnection())
 | |
|             {
 | |
|                  if (connection is System.Data.Common.DbConnection dbConnection) { await dbConnection.OpenAsync(); } else { connection.Open(); }
 | |
|                  using (var transaction = connection.BeginTransaction())
 | |
|                 {
 | |
|                     try
 | |
|                     {
 | |
|                         // 1. Eliminar Saldos asociados
 | |
|                         bool saldosEliminados = await _saldoRepository.DeleteSaldosByEmpresaAsync(id, transaction);
 | |
|                         // No lanzamos error si saldosEliminados es false, podría no haber tenido saldos. Loggeamos si es necesario.
 | |
|                          if (!saldosEliminados && await _saldoRepository.CheckIfSaldosExistForEmpresaAsync(id)) // Necesitarías este método en ISaldoRepository
 | |
|                          {
 | |
|                              _logger.LogWarning("Se intentó eliminar Empresa ID {IdEmpresa} pero falló la eliminación de saldos asociados.", id);
 | |
|                              // Decidir si continuar o fallar. Por ahora, continuamos pero loggeamos.
 | |
|                              // throw new InvalidOperationException("Error al intentar eliminar los saldos asociados a la empresa.");
 | |
|                          } else if (!saldosEliminados) {
 | |
|                               _logger.LogInformation("No se encontraron saldos para eliminar de la Empresa ID {IdEmpresa}.", id);
 | |
|                          } else {
 | |
|                               _logger.LogInformation("Saldos eliminados para Empresa ID {IdEmpresa}.", id);
 | |
|                          }
 | |
| 
 | |
| 
 | |
|                         // 2. Eliminar Empresa (Repo maneja historial)
 | |
|                          var eliminado = await _empresaRepository.DeleteAsync(id, idUsuario, transaction);
 | |
|                          if (!eliminado)
 | |
|                          {
 | |
|                              throw new InvalidOperationException("La eliminación en el repositorio de empresas devolvió false.");
 | |
|                          }
 | |
| 
 | |
|                         transaction.Commit();
 | |
|                         _logger.LogInformation("Empresa ID {IdEmpresa} eliminada exitosamente por Usuario ID {IdUsuario}.", id, idUsuario);
 | |
|                         return (true, null); // Éxito
 | |
|                     }
 | |
|                     catch (Exception ex)
 | |
|                     {
 | |
|                         try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error al intentar hacer rollback en EliminarAsync Empresa."); }
 | |
|                          _logger.LogError(ex, "Error en transacción EliminarAsync para Empresa ID: {Id}", id);
 | |
|                         return (false, "Error interno al eliminar la empresa.");
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             // --- Fin Transacción ---
 | |
|         }
 | |
|     }
 | |
| } |