feat: Implementación CRUD Canillitas, Distribuidores y Precios de Publicación
Backend API:
- Canillitas (`dist_dtCanillas`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Lógica para manejo de `Accionista`, `Baja`, `FechaBaja`.
- Auditoría en `dist_dtCanillas_H`.
- Validación de legajo único y lógica de empresa vs accionista.
- Distribuidores (`dist_dtDistribuidores`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Auditoría en `dist_dtDistribuidores_H`.
- Creación de saldos iniciales para el nuevo distribuidor en todas las empresas.
- Verificación de NroDoc único y Nombre opcionalmente único.
- Precios de Publicación (`dist_Precios`):
- Implementado CRUD básico (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/precios`.
- Lógica de negocio para cerrar período de precio anterior al crear uno nuevo.
- Lógica de negocio para reabrir período de precio anterior al eliminar el último.
- Auditoría en `dist_Precios_H`.
- Auditoría en Eliminación de Publicaciones:
- Extendido `PublicacionService.EliminarAsync` para eliminar en cascada registros de precios, recargos, porcentajes de pago (distribuidores y canillitas) y secciones de publicación.
- Repositorios correspondientes (`PrecioRepository`, `RecargoZonaRepository`, `PorcPagoRepository`, `PorcMonCanillaRepository`, `PubliSeccionRepository`) actualizados con métodos `DeleteByPublicacionIdAsync` que registran en sus respectivas tablas `_H` (si existen y se implementó la lógica).
- Asegurada la correcta propagación del `idUsuario` para la auditoría en cascada.
- Correcciones de Nulabilidad:
- Ajustados los métodos `MapToDto` y su uso en `CanillaService` y `PublicacionService` para manejar correctamente tipos anulables.
Frontend React:
- Canillitas:
- `canillaService.ts`.
- `CanillaFormModal.tsx` con selectores para Zona y Empresa, y lógica de Accionista.
- `GestionarCanillitasPage.tsx` con filtros, paginación, y acciones (editar, toggle baja).
- Distribuidores:
- `distribuidorService.ts`.
- `DistribuidorFormModal.tsx` con múltiples campos y selector de Zona.
- `GestionarDistribuidoresPage.tsx` con filtros, paginación, y acciones (editar, eliminar).
- Precios de Publicación:
- `precioService.ts`.
- `PrecioFormModal.tsx` para crear/editar períodos de precios (VigenciaD, VigenciaH opcional, precios por día).
- `GestionarPreciosPublicacionPage.tsx` accesible desde la gestión de publicaciones, para listar y gestionar los períodos de precios de una publicación específica.
- Layout:
- Reemplazado el uso de `Grid` por `Box` con Flexbox en `CanillaFormModal`, `GestionarCanillitasPage` (filtros), `DistribuidorFormModal` y `PrecioFormModal` para resolver problemas de tipos y mejorar la consistencia del layout de formularios.
- Navegación:
- Actualizadas las rutas y pestañas para los nuevos módulos y sub-módulos.
2025-05-20 12:38:55 -03:00
using Dapper ;
2025-06-30 15:26:14 -03:00
using GestionIntegral.Api.Dtos.Distribucion ;
feat: Implementación CRUD Canillitas, Distribuidores y Precios de Publicación
Backend API:
- Canillitas (`dist_dtCanillas`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Lógica para manejo de `Accionista`, `Baja`, `FechaBaja`.
- Auditoría en `dist_dtCanillas_H`.
- Validación de legajo único y lógica de empresa vs accionista.
- Distribuidores (`dist_dtDistribuidores`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Auditoría en `dist_dtDistribuidores_H`.
- Creación de saldos iniciales para el nuevo distribuidor en todas las empresas.
- Verificación de NroDoc único y Nombre opcionalmente único.
- Precios de Publicación (`dist_Precios`):
- Implementado CRUD básico (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/precios`.
- Lógica de negocio para cerrar período de precio anterior al crear uno nuevo.
- Lógica de negocio para reabrir período de precio anterior al eliminar el último.
- Auditoría en `dist_Precios_H`.
- Auditoría en Eliminación de Publicaciones:
- Extendido `PublicacionService.EliminarAsync` para eliminar en cascada registros de precios, recargos, porcentajes de pago (distribuidores y canillitas) y secciones de publicación.
- Repositorios correspondientes (`PrecioRepository`, `RecargoZonaRepository`, `PorcPagoRepository`, `PorcMonCanillaRepository`, `PubliSeccionRepository`) actualizados con métodos `DeleteByPublicacionIdAsync` que registran en sus respectivas tablas `_H` (si existen y se implementó la lógica).
- Asegurada la correcta propagación del `idUsuario` para la auditoría en cascada.
- Correcciones de Nulabilidad:
- Ajustados los métodos `MapToDto` y su uso en `CanillaService` y `PublicacionService` para manejar correctamente tipos anulables.
Frontend React:
- Canillitas:
- `canillaService.ts`.
- `CanillaFormModal.tsx` con selectores para Zona y Empresa, y lógica de Accionista.
- `GestionarCanillitasPage.tsx` con filtros, paginación, y acciones (editar, toggle baja).
- Distribuidores:
- `distribuidorService.ts`.
- `DistribuidorFormModal.tsx` con múltiples campos y selector de Zona.
- `GestionarDistribuidoresPage.tsx` con filtros, paginación, y acciones (editar, eliminar).
- Precios de Publicación:
- `precioService.ts`.
- `PrecioFormModal.tsx` para crear/editar períodos de precios (VigenciaD, VigenciaH opcional, precios por día).
- `GestionarPreciosPublicacionPage.tsx` accesible desde la gestión de publicaciones, para listar y gestionar los períodos de precios de una publicación específica.
- Layout:
- Reemplazado el uso de `Grid` por `Box` con Flexbox en `CanillaFormModal`, `GestionarCanillitasPage` (filtros), `DistribuidorFormModal` y `PrecioFormModal` para resolver problemas de tipos y mejorar la consistencia del layout de formularios.
- Navegación:
- Actualizadas las rutas y pestañas para los nuevos módulos y sub-módulos.
2025-05-20 12:38:55 -03:00
using GestionIntegral.Api.Models.Distribucion ;
using Microsoft.Extensions.Logging ;
using System.Collections.Generic ;
using System.Data ;
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
namespace GestionIntegral.Api.Data.Repositories.Distribucion
{
public class OtroDestinoRepository : IOtroDestinoRepository
{
private readonly DbConnectionFactory _connectionFactory ;
private readonly ILogger < OtroDestinoRepository > _logger ;
public OtroDestinoRepository ( DbConnectionFactory connectionFactory , ILogger < OtroDestinoRepository > logger )
{
_connectionFactory = connectionFactory ;
_logger = logger ;
}
public async Task < IEnumerable < OtroDestino > > GetAllAsync ( string? nombreFilter )
{
var sqlBuilder = new StringBuilder ( "SELECT Id_Destino AS IdDestino, Nombre, Obs FROM dbo.dist_dtOtrosDestinos WHERE 1=1" ) ;
var parameters = new DynamicParameters ( ) ;
if ( ! string . IsNullOrWhiteSpace ( nombreFilter ) )
{
sqlBuilder . Append ( " AND Nombre LIKE @NombreFilter" ) ;
parameters . Add ( "NombreFilter" , $"%{nombreFilter}%" ) ;
}
sqlBuilder . Append ( " ORDER BY Nombre;" ) ;
try
{
using var connection = _connectionFactory . CreateConnection ( ) ;
return await connection . QueryAsync < OtroDestino > ( sqlBuilder . ToString ( ) , parameters ) ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error al obtener todos los Otros Destinos. Filtro: {Nombre}" , nombreFilter ) ;
return Enumerable . Empty < OtroDestino > ( ) ;
}
}
2025-06-30 15:26:14 -03:00
public async Task < IEnumerable < OtroDestinoDropdownDto > > GetAllDropdownAsync ( )
{
const string sql = "SELECT Id_Destino AS IdDestino, Nombre FROM dbo.dist_dtOtrosDestinos ORDER BY Nombre;" ;
try
{
using var connection = _connectionFactory . CreateConnection ( ) ;
return await connection . QueryAsync < OtroDestinoDropdownDto > ( sql ) ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error al obtener Otros Destinos para dropdown." ) ;
return Enumerable . Empty < OtroDestinoDropdownDto > ( ) ;
}
}
feat: Implementación CRUD Canillitas, Distribuidores y Precios de Publicación
Backend API:
- Canillitas (`dist_dtCanillas`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Lógica para manejo de `Accionista`, `Baja`, `FechaBaja`.
- Auditoría en `dist_dtCanillas_H`.
- Validación de legajo único y lógica de empresa vs accionista.
- Distribuidores (`dist_dtDistribuidores`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Auditoría en `dist_dtDistribuidores_H`.
- Creación de saldos iniciales para el nuevo distribuidor en todas las empresas.
- Verificación de NroDoc único y Nombre opcionalmente único.
- Precios de Publicación (`dist_Precios`):
- Implementado CRUD básico (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/precios`.
- Lógica de negocio para cerrar período de precio anterior al crear uno nuevo.
- Lógica de negocio para reabrir período de precio anterior al eliminar el último.
- Auditoría en `dist_Precios_H`.
- Auditoría en Eliminación de Publicaciones:
- Extendido `PublicacionService.EliminarAsync` para eliminar en cascada registros de precios, recargos, porcentajes de pago (distribuidores y canillitas) y secciones de publicación.
- Repositorios correspondientes (`PrecioRepository`, `RecargoZonaRepository`, `PorcPagoRepository`, `PorcMonCanillaRepository`, `PubliSeccionRepository`) actualizados con métodos `DeleteByPublicacionIdAsync` que registran en sus respectivas tablas `_H` (si existen y se implementó la lógica).
- Asegurada la correcta propagación del `idUsuario` para la auditoría en cascada.
- Correcciones de Nulabilidad:
- Ajustados los métodos `MapToDto` y su uso en `CanillaService` y `PublicacionService` para manejar correctamente tipos anulables.
Frontend React:
- Canillitas:
- `canillaService.ts`.
- `CanillaFormModal.tsx` con selectores para Zona y Empresa, y lógica de Accionista.
- `GestionarCanillitasPage.tsx` con filtros, paginación, y acciones (editar, toggle baja).
- Distribuidores:
- `distribuidorService.ts`.
- `DistribuidorFormModal.tsx` con múltiples campos y selector de Zona.
- `GestionarDistribuidoresPage.tsx` con filtros, paginación, y acciones (editar, eliminar).
- Precios de Publicación:
- `precioService.ts`.
- `PrecioFormModal.tsx` para crear/editar períodos de precios (VigenciaD, VigenciaH opcional, precios por día).
- `GestionarPreciosPublicacionPage.tsx` accesible desde la gestión de publicaciones, para listar y gestionar los períodos de precios de una publicación específica.
- Layout:
- Reemplazado el uso de `Grid` por `Box` con Flexbox en `CanillaFormModal`, `GestionarCanillitasPage` (filtros), `DistribuidorFormModal` y `PrecioFormModal` para resolver problemas de tipos y mejorar la consistencia del layout de formularios.
- Navegación:
- Actualizadas las rutas y pestañas para los nuevos módulos y sub-módulos.
2025-05-20 12:38:55 -03:00
public async Task < OtroDestino ? > GetByIdAsync ( int id )
{
const string sql = "SELECT Id_Destino AS IdDestino, Nombre, Obs FROM dbo.dist_dtOtrosDestinos WHERE Id_Destino = @Id" ;
try
{
using var connection = _connectionFactory . CreateConnection ( ) ;
return await connection . QuerySingleOrDefaultAsync < OtroDestino > ( sql , new { Id = id } ) ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error al obtener Otro Destino por ID: {IdDestino}" , id ) ;
return null ;
}
}
public async Task < bool > ExistsByNameAsync ( string nombre , int? excludeId = null )
{
var sqlBuilder = new StringBuilder ( "SELECT COUNT(1) FROM dbo.dist_dtOtrosDestinos WHERE Nombre = @Nombre" ) ;
var parameters = new DynamicParameters ( ) ;
parameters . Add ( "Nombre" , nombre ) ;
if ( excludeId . HasValue )
{
sqlBuilder . Append ( " AND Id_Destino != @ExcludeId" ) ;
parameters . Add ( "ExcludeId" , excludeId . Value ) ;
}
try
{
using var connection = _connectionFactory . CreateConnection ( ) ;
var count = await connection . ExecuteScalarAsync < int > ( sqlBuilder . ToString ( ) , parameters ) ;
return count > 0 ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error en ExistsByNameAsync para Otro Destino con nombre: {Nombre}" , nombre ) ;
return true ;
}
}
public async Task < bool > IsInUseAsync ( int id )
{
const string sqlCheckSalidas = "SELECT TOP 1 1 FROM dbo.dist_SalidasOtrosDestinos WHERE Id_Destino = @IdDestino" ;
try
{
using var connection = _connectionFactory . CreateConnection ( ) ;
var inUse = await connection . ExecuteScalarAsync < int? > ( sqlCheckSalidas , new { IdDestino = id } ) ;
return inUse . HasValue & & inUse . Value = = 1 ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error en IsInUseAsync para Otro Destino ID: {IdDestino}" , id ) ;
return true ;
}
}
public async Task < OtroDestino ? > CreateAsync ( OtroDestino nuevoDestino , int idUsuario , IDbTransaction transaction )
{
const string sqlInsert = @ "
INSERT INTO dbo . dist_dtOtrosDestinos ( Nombre , Obs )
OUTPUT INSERTED . Id_Destino AS IdDestino , INSERTED . Nombre , INSERTED . Obs
VALUES ( @Nombre , @Obs ) ; ";
const string sqlInsertHistorico = @ "
INSERT INTO dbo . dist_dtOtrosDestinos_H ( Id_Destino , Nombre , Obs , Id_Usuario , FechaMod , TipoMod )
VALUES ( @IdDestino , @Nombre , @Obs , @IdUsuario , @FechaMod , @TipoMod ) ; ";
var connection = transaction . Connection ! ;
var insertedDestino = await connection . QuerySingleAsync < OtroDestino > ( sqlInsert , nuevoDestino , transaction ) ;
if ( insertedDestino = = null | | insertedDestino . IdDestino < = 0 )
{
throw new DataException ( "No se pudo obtener el ID del otro destino insertado." ) ;
}
await connection . ExecuteAsync ( sqlInsertHistorico , new
{
IdDestino = insertedDestino . IdDestino ,
insertedDestino . Nombre ,
insertedDestino . Obs ,
IdUsuario = idUsuario ,
FechaMod = DateTime . Now ,
TipoMod = "Insertada"
} , transaction ) ;
return insertedDestino ;
}
public async Task < bool > UpdateAsync ( OtroDestino destinoAActualizar , int idUsuario , IDbTransaction transaction )
{
var connection = transaction . Connection ! ;
var destinoActual = await connection . QuerySingleOrDefaultAsync < OtroDestino > (
"SELECT Id_Destino AS IdDestino, Nombre, Obs FROM dbo.dist_dtOtrosDestinos WHERE Id_Destino = @Id" ,
new { Id = destinoAActualizar . IdDestino } , transaction ) ;
if ( destinoActual = = null ) throw new KeyNotFoundException ( $"Otro Destino con ID {destinoAActualizar.IdDestino} no encontrado." ) ;
const string sqlUpdate = "UPDATE dbo.dist_dtOtrosDestinos SET Nombre = @Nombre, Obs = @Obs WHERE Id_Destino = @IdDestino;" ;
const string sqlInsertHistorico = @ "
INSERT INTO dbo . dist_dtOtrosDestinos_H ( Id_Destino , Nombre , Obs , Id_Usuario , FechaMod , TipoMod )
VALUES ( @IdDestino , @NombreActual , @ObsActual , @IdUsuario , @FechaMod , @TipoMod ) ; ";
await connection . ExecuteAsync ( sqlInsertHistorico , new
{
IdDestino = destinoActual . IdDestino ,
NombreActual = destinoActual . Nombre ,
ObsActual = destinoActual . Obs ,
IdUsuario = idUsuario ,
FechaMod = DateTime . Now ,
TipoMod = "Modificada"
} , transaction ) ;
var rowsAffected = await connection . ExecuteAsync ( sqlUpdate , destinoAActualizar , transaction ) ;
return rowsAffected = = 1 ;
}
public async Task < bool > DeleteAsync ( int id , int idUsuario , IDbTransaction transaction )
{
var connection = transaction . Connection ! ;
var destinoActual = await connection . QuerySingleOrDefaultAsync < OtroDestino > (
"SELECT Id_Destino AS IdDestino, Nombre, Obs FROM dbo.dist_dtOtrosDestinos WHERE Id_Destino = @Id" ,
new { Id = id } , transaction ) ;
if ( destinoActual = = null ) throw new KeyNotFoundException ( $"Otro Destino con ID {id} no encontrado." ) ;
const string sqlDelete = "DELETE FROM dbo.dist_dtOtrosDestinos WHERE Id_Destino = @Id" ;
const string sqlInsertHistorico = @ "
INSERT INTO dbo . dist_dtOtrosDestinos_H ( Id_Destino , Nombre , Obs , Id_Usuario , FechaMod , TipoMod )
VALUES ( @IdDestino , @Nombre , @Obs , @IdUsuario , @FechaMod , @TipoMod ) ; ";
await connection . ExecuteAsync ( sqlInsertHistorico , new
{
IdDestino = destinoActual . IdDestino ,
destinoActual . Nombre ,
destinoActual . Obs ,
IdUsuario = idUsuario ,
FechaMod = DateTime . Now ,
TipoMod = "Eliminada"
} , transaction ) ;
var rowsAffected = await connection . ExecuteAsync ( sqlDelete , new { Id = id } , transaction ) ;
return rowsAffected = = 1 ;
}
2025-06-12 19:36:21 -03:00
public async Task < IEnumerable < ( OtroDestinoHistorico Historial , string NombreUsuarioModifico ) > > GetHistorialAsync (
DateTime ? fechaDesde , DateTime ? fechaHasta ,
int? idUsuarioModifico , string? tipoModificacion ,
int? idOtroDestinoOriginal )
{
using var connection = _connectionFactory . CreateConnection ( ) ;
var sqlBuilder = new StringBuilder ( @ "
SELECT
h . Id_Destino , h . Nombre , h . Obs ,
h . Id_Usuario , h . FechaMod , h . TipoMod ,
u . Nombre + ' ' + u . Apellido AS NombreUsuarioModifico
FROM dbo . dist_dtOtrosDestinos_H h
JOIN dbo . gral_Usuarios u ON h . Id_Usuario = u . Id
WHERE 1 = 1 ");
var parameters = new DynamicParameters ( ) ;
if ( fechaDesde . HasValue ) { sqlBuilder . Append ( " AND h.FechaMod >= @FechaDesdeParam" ) ; parameters . Add ( "FechaDesdeParam" , fechaDesde . Value . Date ) ; }
if ( fechaHasta . HasValue ) { sqlBuilder . Append ( " AND h.FechaMod <= @FechaHastaParam" ) ; parameters . Add ( "FechaHastaParam" , fechaHasta . Value . Date . AddDays ( 1 ) . AddTicks ( - 1 ) ) ; }
if ( idUsuarioModifico . HasValue ) { sqlBuilder . Append ( " AND h.Id_Usuario = @IdUsuarioModificoParam" ) ; parameters . Add ( "IdUsuarioModificoParam" , idUsuarioModifico . Value ) ; }
if ( ! string . IsNullOrWhiteSpace ( tipoModificacion ) ) { sqlBuilder . Append ( " AND h.TipoMod = @TipoModParam" ) ; parameters . Add ( "TipoModParam" , tipoModificacion ) ; }
if ( idOtroDestinoOriginal . HasValue ) { sqlBuilder . Append ( " AND h.Id_Destino = @IdOtroDestinoOriginalParam" ) ; parameters . Add ( "IdOtroDestinoOriginalParam" , idOtroDestinoOriginal . Value ) ; }
sqlBuilder . Append ( " ORDER BY h.FechaMod DESC;" ) ;
try
{
var result = await connection . QueryAsync < OtroDestinoHistorico , string , ( OtroDestinoHistorico , string ) > (
sqlBuilder . ToString ( ) ,
( hist , userName ) = > ( hist , userName ) ,
parameters ,
splitOn : "NombreUsuarioModifico"
) ;
return result ;
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error al obtener historial de Otros Destinos (Maestro)." ) ;
return Enumerable . Empty < ( OtroDestinoHistorico , string ) > ( ) ;
}
}
feat: Implementación CRUD Canillitas, Distribuidores y Precios de Publicación
Backend API:
- Canillitas (`dist_dtCanillas`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Lógica para manejo de `Accionista`, `Baja`, `FechaBaja`.
- Auditoría en `dist_dtCanillas_H`.
- Validación de legajo único y lógica de empresa vs accionista.
- Distribuidores (`dist_dtDistribuidores`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Auditoría en `dist_dtDistribuidores_H`.
- Creación de saldos iniciales para el nuevo distribuidor en todas las empresas.
- Verificación de NroDoc único y Nombre opcionalmente único.
- Precios de Publicación (`dist_Precios`):
- Implementado CRUD básico (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/precios`.
- Lógica de negocio para cerrar período de precio anterior al crear uno nuevo.
- Lógica de negocio para reabrir período de precio anterior al eliminar el último.
- Auditoría en `dist_Precios_H`.
- Auditoría en Eliminación de Publicaciones:
- Extendido `PublicacionService.EliminarAsync` para eliminar en cascada registros de precios, recargos, porcentajes de pago (distribuidores y canillitas) y secciones de publicación.
- Repositorios correspondientes (`PrecioRepository`, `RecargoZonaRepository`, `PorcPagoRepository`, `PorcMonCanillaRepository`, `PubliSeccionRepository`) actualizados con métodos `DeleteByPublicacionIdAsync` que registran en sus respectivas tablas `_H` (si existen y se implementó la lógica).
- Asegurada la correcta propagación del `idUsuario` para la auditoría en cascada.
- Correcciones de Nulabilidad:
- Ajustados los métodos `MapToDto` y su uso en `CanillaService` y `PublicacionService` para manejar correctamente tipos anulables.
Frontend React:
- Canillitas:
- `canillaService.ts`.
- `CanillaFormModal.tsx` con selectores para Zona y Empresa, y lógica de Accionista.
- `GestionarCanillitasPage.tsx` con filtros, paginación, y acciones (editar, toggle baja).
- Distribuidores:
- `distribuidorService.ts`.
- `DistribuidorFormModal.tsx` con múltiples campos y selector de Zona.
- `GestionarDistribuidoresPage.tsx` con filtros, paginación, y acciones (editar, eliminar).
- Precios de Publicación:
- `precioService.ts`.
- `PrecioFormModal.tsx` para crear/editar períodos de precios (VigenciaD, VigenciaH opcional, precios por día).
- `GestionarPreciosPublicacionPage.tsx` accesible desde la gestión de publicaciones, para listar y gestionar los períodos de precios de una publicación específica.
- Layout:
- Reemplazado el uso de `Grid` por `Box` con Flexbox en `CanillaFormModal`, `GestionarCanillitasPage` (filtros), `DistribuidorFormModal` y `PrecioFormModal` para resolver problemas de tipos y mejorar la consistencia del layout de formularios.
- Navegación:
- Actualizadas las rutas y pestañas para los nuevos módulos y sub-módulos.
2025-05-20 12:38:55 -03:00
}
}