From 1ca3949c097968b92d9ac0b2c5ffbf9247f8795e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Wed, 4 Jun 2025 20:54:50 -0600 Subject: [PATCH 01/21] feat: regex y ya no se puede poner 0 --- .../Validaciones/validarProducto.js | 27 ++++++----- .../Organismos/Formularios/CamposOpcion.jsx | 21 ++++---- .../Organismos/Formularios/CamposProducto.jsx | 20 ++++---- .../Organismos/Formularios/CamposVariante.jsx | 8 +++- .../Formularios/FormularioProducto.jsx | 6 +++ src/hooks/Productos/ProductoFormProvider.jsx | 48 ++++++++++++------- 6 files changed, 79 insertions(+), 51 deletions(-) diff --git a/src/Utilidades/Validaciones/validarProducto.js b/src/Utilidades/Validaciones/validarProducto.js index 63ef7165..7122d513 100644 --- a/src/Utilidades/Validaciones/validarProducto.js +++ b/src/Utilidades/Validaciones/validarProducto.js @@ -26,9 +26,9 @@ export const validarProducto = (producto) => { errores.precioCliente = 'El precio para el cliente es obligatorio.'; } else if (typeof normalizados.precioCliente !== 'number' || normalizados.precioCliente <= 0) { errores.precioCliente = 'El precio para el cliente debe ser un número positivo.'; - } else if (!/^\d{1,8}(\.\d{1,2})?$/.test(normalizados.precioCliente.toString())) { - errores.precioCliente - = 'El precio para el cliente debe tener máximo 8 dígitos antes del punto y 2 después.'; + } else if (!/^[1-9]\d{0,7}(\.\d{1,2})?$/.test(normalizados.precioCliente.toString())) { + errores.precioCliente = + 'El precio para el cliente debe tener máximo 8 dígitos antes del punto y 2 después, y no puede comenzar con 0.'; } // Validación de precio de venta @@ -36,9 +36,9 @@ export const validarProducto = (producto) => { errores.precioVenta = 'El precio de venta es obligatorio.'; } else if (typeof normalizados.precioVenta !== 'number' || normalizados.precioVenta <= 0) { errores.precioVenta = 'El precio de venta debe ser un número positivo.'; - } else if (!/^\d{1,8}(\.\d{1,2})?$/.test(normalizados.precioVenta.toString())) { - errores.precioVenta - = 'El precio de venta debe tener máximo 8 dígitos antes del punto y 2 después.'; + } else if (!/^[1-9]\d{0,7}(\.\d{1,2})?$/.test(normalizados.precioVenta.toString())) { + errores.precioVenta = + 'El precio de venta debe tener máximo 8 dígitos antes del punto y 2 después, y no puede comenzar con 0.'; } // Validación de costo @@ -46,8 +46,9 @@ export const validarProducto = (producto) => { errores.costo = 'El costo es obligatorio.'; } else if (typeof normalizados.costo !== 'number' || normalizados.costo <= 0) { errores.costo = 'El costo debe ser un número positivo.'; - } else if (!/^\d{1,8}(\.\d{1,2})?$/.test(normalizados.costo.toString())) { - errores.costo = 'El costo debe tener máximo 8 dígitos antes del punto y 2 después.'; + } else if (!/^[1-9]\d{0,7}(\.\d{1,2})?$/.test(normalizados.costo.toString())) { + errores.costo = + 'El costo debe tener máximo 8 dígitos antes del punto y 2 después, y no puede comenzar con 0.'; } // Validación de impuesto @@ -55,8 +56,9 @@ export const validarProducto = (producto) => { errores.impuesto = 'El impuesto es obligatorio.'; } else if (typeof normalizados.impuesto !== 'number' || normalizados.impuesto <= 0) { errores.impuesto = 'El impuesto debe ser un número positivo.'; - } else if (!/^\d{1,8}(\.\d{1,2})?$/.test(normalizados.impuesto.toString())) { - errores.impuesto = 'El impuesto debe tener máximo 8 dígitos antes del punto y 2 después.'; + } else if (!/^[1-9]\d{0,7}(\.\d{1,2})?$/.test(normalizados.impuesto.toString())) { + errores.impuesto = + 'El impuesto debe tener máximo 8 dígitos antes del punto y 2 después, y no puede comenzar con 0.'; } // Validación de precio en puntos @@ -64,8 +66,9 @@ export const validarProducto = (producto) => { errores.precioPuntos = 'El precio en puntos es obligatorio.'; } else if (typeof normalizados.precioPuntos !== 'number' || normalizados.precioPuntos <= 0) { errores.precioPuntos = 'El precio en puntos debe ser un número entero positivo.'; - } else if (!/^\d{1,10}$/.test(normalizados.precioPuntos.toString())) { - errores.precioPuntos = 'El precio en puntos debe tener máximo 10 dígitos.'; + } else if (!/^[1-9]\d{0,9}$/.test(normalizados.precioPuntos.toString())) { + errores.precioPuntos = + 'El precio en puntos debe tener máximo 10 dígitos y no puede comenzar con 0.'; } return errores; diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposOpcion.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposOpcion.jsx index 61eebcbc..cb417aad 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/CamposOpcion.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposOpcion.jsx @@ -106,7 +106,16 @@ const BotonForm = memo(({ selected, fullWidth, backgroundColor, outlineColor, la )); const CamposOpcion = memo( - ({ opcion, index, varianteId, erroresOpciones, alActualizarOpcion, alEliminarOpcion }) => { + ({ + opcion, + index, + varianteId, + erroresOpciones, + alActualizarOpcion, + alEliminarOpcion, + prevenirNumerosNegativos, + prevenirNumerosNoDecimales, + }) => { const manejarActualizarOpcion = useCallback( (campo, valor) => { alActualizarOpcion(varianteId, index, campo, valor); @@ -180,7 +189,7 @@ const CamposOpcion = memo( textoAyuda={errores?.costoAdicional} error={errores?.costoAdicional} min={1} - onKeyDown={prevenirNumerosNegativos} + onKeyDown={prevenirNumerosNoDecimales} onInput={(evento) => { if (evento.target.value && evento.target.value < 1) { evento.target.value = 1; @@ -197,7 +206,7 @@ const CamposOpcion = memo( textoAyuda={errores?.descuento} error={errores?.descuento} min={1} - onKeyDown={prevenirNumerosNegativos} + onKeyDown={prevenirNumerosNoDecimales} onInput={(evento) => { if (evento.target.value && evento.target.value < 1) { evento.target.value = 1; @@ -222,10 +231,4 @@ const CamposOpcion = memo( } ); -const prevenirNumerosNegativos = (evento) => { - if (['-', 'e', 'E', '+'].includes(evento.key)) { - evento.preventDefault(); - } -}; - export default CamposOpcion; diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposProducto.jsx index 44b599f3..2c979ed2 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/CamposProducto.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposProducto.jsx @@ -15,7 +15,7 @@ const CampoTextoFormulario = memo( nombre, valor, onChange, - helperText, // Cambiar textoAyuda por helperText + helperText, error, placeholder, tipo = 'text', @@ -172,6 +172,8 @@ const CamposProducto = memo( alAgregarImagenProducto, setImagenes, alMostrarFormularioProveedor, + prevenirNumerosNegativos, + prevenirNumerosNoDecimales, }) => { return ( <> @@ -299,7 +301,7 @@ const CamposProducto = memo( tipo='number' required min={1} - onKeyDown={prevenirNumerosNegativos} + onKeyDown={prevenirNumerosNoDecimales} onInput={(evento) => { if (evento.target.value && evento.target.value < 1) { evento.target.value = 1; @@ -318,7 +320,7 @@ const CamposProducto = memo( tipo='number' required min={1} - onKeyDown={prevenirNumerosNegativos} + onKeyDown={prevenirNumerosNoDecimales} onInput={(evento) => { if (evento.target.value && evento.target.value < 1) { evento.target.value = 1; @@ -337,7 +339,7 @@ const CamposProducto = memo( tipo='number' required min={1} - onKeyDown={prevenirNumerosNegativos} + onKeyDown={prevenirNumerosNoDecimales} onInput={(evento) => { if (evento.target.value && evento.target.value < 1) { evento.target.value = 1; @@ -356,7 +358,7 @@ const CamposProducto = memo( tipo='number' required={false} min={1} - onKeyDown={prevenirNumerosNegativos} + onKeyDown={prevenirNumerosNoDecimales} onInput={(evento) => { if (evento.target.value && evento.target.value < 1) { evento.target.value = 1; @@ -375,7 +377,7 @@ const CamposProducto = memo( tipo='number' required={false} min={1} - onKeyDown={prevenirNumerosNegativos} + onKeyDown={prevenirNumerosNoDecimales} onInput={(evento) => { if (evento.target.value && evento.target.value < 1) { evento.target.value = 1; @@ -424,10 +426,4 @@ const CamposProducto = memo( } ); -const prevenirNumerosNegativos = (evento) => { - if (['-', 'e', 'E', '+'].includes(evento.key)) { - evento.preventDefault(); - } -}; - export default CamposProducto; diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx index d85ac127..7fcfd591 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx @@ -171,6 +171,8 @@ const CamposVariante = memo( alEliminarOpcion, alAgregarImagenVariante, alEliminarImagenVariante, + prevenirNumerosNegativos, + prevenirNumerosNoDecimales, }) => { const manejarActualizarVariante = useCallback( (campo, valor) => { @@ -187,8 +189,8 @@ const CamposVariante = memo( alAgregarOpcion(varianteId); }, [varianteId, alAgregarOpcion]); - const errores - = erroresVariantes && erroresVariantes[varianteId] ? erroresVariantes[varianteId] : {}; + const errores = + erroresVariantes && erroresVariantes[varianteId] ? erroresVariantes[varianteId] : {}; return ( <> @@ -244,6 +246,8 @@ const CamposVariante = memo( intentoEnviar={intentoEnviar} alActualizarOpcion={alActualizarOpcion} alEliminarOpcion={alEliminarOpcion} + prevenirNumerosNegativos={prevenirNumerosNegativos} + prevenirNumerosNoDecimales={prevenirNumerosNoDecimales} /> ))} diff --git a/src/Vistas/Componentes/Organismos/Formularios/FormularioProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/FormularioProducto.jsx index 2555a9bc..f947aea2 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/FormularioProducto.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/FormularioProducto.jsx @@ -44,6 +44,8 @@ const ContenidoFormulario = memo(({ alMostrarFormularioProveedor }) => { manejarEliminarImagenVariante, manejarActualizarProducto, manejarAgregarImagenProducto, + prevenirNumerosNegativos, + prevenirNumerosNoDecimales, } = useProductoForm(); return ( @@ -72,6 +74,8 @@ const ContenidoFormulario = memo(({ alMostrarFormularioProveedor }) => { alAgregarImagenProducto={manejarAgregarImagenProducto} setImagenes={setImagenes} alMostrarFormularioProveedor={alMostrarFormularioProveedor} + prevenirNumerosNegativos={prevenirNumerosNegativos} + prevenirNumerosNoDecimales={prevenirNumerosNoDecimales} /> {idsVariantes.map((idVariante) => ( @@ -91,6 +95,8 @@ const ContenidoFormulario = memo(({ alMostrarFormularioProveedor }) => { alEliminarOpcion={manejarEliminarOpcion} alAgregarImagenVariante={manejarAgregarImagenVariante} alEliminarImagenVariante={manejarEliminarImagenVariante} + prevenirNumerosNegativos={prevenirNumerosNegativos} + prevenirNumerosNoDecimales={prevenirNumerosNoDecimales} /> ))} diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index 64d12a3e..a733224d 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -6,6 +6,20 @@ import { v4 as uuidv4 } from 'uuid'; const ProductoFormContext = createContext(); +const prevenirNumerosNegativos = (evento) => { + const regex = /^[0-9]$/; + if (!regex.test(evento.key) && evento.key !== 'Backspace' && evento.key !== 'Tab') { + evento.preventDefault(); + } +}; + +const prevenirNumerosNoDecimales = (evento) => { + const regex = /^[0-9.]$/; + if (!regex.test(evento.key) && evento.key !== 'Backspace' && evento.key !== 'Tab') { + evento.preventDefault(); + } +}; + export const useProductoForm = () => { const context = useContext(ProductoFormContext); if (!context) { @@ -39,15 +53,15 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = marca: '', modelo: '', tipoProducto: '', - precioPuntos: 0, - precioCliente: 0, - precioVenta: 0, - costo: 0, + precioPuntos: undefined, + precioCliente: undefined, + precioVenta: undefined, + costo: undefined, impuesto: 16, - descuento: 0, + descuento: undefined, estado: 1, - envio: 1, - idProveedor: -1, + envio: undefined, + idProveedor: undefined, }); const [imagenes, setImagenes] = useState({ @@ -171,12 +185,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const nuevaOpcion = { id: Date.now(), - cantidad: 0, + cantidad: undefined, valorOpcion: '', SKUautomatico: sku, SKUcomercial: '', - costoAdicional: 0, - descuento: 0, + costoAdicional: undefined, + descuento: undefined, estado: 1, }; @@ -385,15 +399,15 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = marca: '', modelo: '', tipoProducto: '', - precioPuntos: 0, - precioCliente: 0, - precioVenta: 0, - costo: 0, + precioPuntos: undefined, + precioCliente: undefined, + precioVenta: undefined, + costo: undefined, impuesto: 16, - descuento: 0, + descuento: undefined, estado: 1, envio: 1, - idProveedor: -1, + idProveedor: undefined, }); setVariantes({ @@ -495,6 +509,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = manejarCrearProducto, manejarActualizarProducto, manejarAgregarImagenProducto, + prevenirNumerosNegativos, + prevenirNumerosNoDecimales, }; return ( From ecf77149981f3481e6dc057a80572425a21fc6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Thu, 5 Jun 2025 08:51:43 -0600 Subject: [PATCH 02/21] =?UTF-8?q?fix:=20duplicado=20de=20m=C3=A1ximo=20de?= =?UTF-8?q?=20caracteres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Organismos/Formularios/CamposVariante.jsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx index 7fcfd591..20d313cc 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx @@ -226,14 +226,8 @@ const CamposVariante = memo( onChange={(evento) => manejarActualizarVariante('descripcion', evento.target.value)} placeholder='Descripción de la variante' error={errores?.descripcion} - helperText={ - variante.descripcion - ? `${variante.descripcion.length}/${300} - Máximo de caracteres. ${ - errores?.descripcion || '' - }` - : errores?.descripcion - } - maxLongitudDescripcion={300} + helperText={errores?.descripcion || ''} + opcion={300} /> {(variante.opciones || []).map((opcion, index) => ( From 33d95f9243ab6e6f762fef39dcb35fa75be4006b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Fri, 6 Jun 2025 16:25:47 -0600 Subject: [PATCH 03/21] =?UTF-8?q?fix:=20bot=C3=B3n=20que=20te=20lleva=20al?= =?UTF-8?q?=20formulario=20producto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Paginas/Productos/ListaProductos.jsx | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index 2ef98251..092b244d 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -35,10 +35,11 @@ const ListaProductos = () => { const [alerta, setAlerta] = useState(null); const [openModalEliminar, setAbrirPopUp] = useState(false); const [abrirModalDetalle, setAbrirModalDetalle] = useState(false); - const [imagenProducto, setImagenProducto] = useState('') + const [imagenProducto, setImagenProducto] = useState(''); const [openModalExportar, setAbrirPopUpExportar] = useState(false); - const MENSAJE_POPUP_EXPORTAR = '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; + const MENSAJE_POPUP_EXPORTAR = + '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; const manejarCancelarExportar = () => { setAbrirPopUpExportar(false); }; @@ -60,7 +61,7 @@ const ListaProductos = () => { }; const { exportar, error: errorExportar, mensaje } = useExportarProductos(); - + useEffect(() => { if (errorExportar) { setAlerta({ @@ -159,7 +160,7 @@ const ListaProductos = () => { renderCell: (params) => ( Producto ), @@ -186,10 +187,10 @@ const ListaProductos = () => { renderCell: ({ row: { estado } }) => ( @@ -254,8 +255,8 @@ const ListaProductos = () => { return ( <> {mostrarModalProducto && ( @@ -271,10 +272,8 @@ const ListaProductos = () => { alCerrarFormularioProveedor={cerrarFormularioProveedor} /> )} - - {error && ( - - )} + + {error && } { checkboxSelection rowHeight={80} onRowSelectionModelChange={(nuevosIds) => { - const ids = (Array.isArray(nuevosIds) ? nuevosIds : Array.from(nuevosIds?.ids || [])) - .map(id => parseInt(id)); + const ids = ( + Array.isArray(nuevosIds) ? nuevosIds : Array.from(nuevosIds?.ids || []) + ).map((id) => parseInt(id)); setProductosSeleccionados(ids); }} onRowClick={(parametros) => { setProductoDetalleSeleccionado(parametros.row.id); - setImagenProducto(parametros.row.urlImagen) + setImagenProducto(parametros.row.urlImagen); setAbrirModalDetalle(true); }} /> @@ -312,7 +312,7 @@ const ListaProductos = () => { abrir={openModalEliminar} cerrar={manejarCancelarEliminar} confirmar={manejarConfirmarEliminar} - dialogo="¿Estás seguro de que deseas eliminar los productos seleccionados? Esta acción no se puede deshacer." + dialogo='¿Estás seguro de que deseas eliminar los productos seleccionados? Esta acción no se puede deshacer.' /> { cerrar={manejarCancelarExportar} confirmar={manejarConfirmarExportar} dialogo={MENSAJE_POPUP_EXPORTAR} - labelCancelar = 'Cancelar' - labelConfirmar = 'Confirmar' + labelCancelar='Cancelar' + labelConfirmar='Confirmar' disabledConfirmar={cargando} /> @@ -338,8 +338,8 @@ const ListaProductos = () => { variant: 'contained', color: 'primary', backgroundColor: colores.altertex[1], - onClick: () => console.log('Editar producto'), - construccion: true, + onClick: mostrarFormularioProducto, + disabled: !usuario?.permisos?.includes(PERMISOS.ACTUALIZAR_PRODUCTO), }, { label: 'Salir', @@ -355,7 +355,7 @@ const ListaProductos = () => { ) : errorDetalle ? (

Error al cargar la información del producto: {errorDetalle}

) : ( - + )} )} From 1f37163344c83b90aa97cce23f3b9776375bdafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Fri, 6 Jun 2025 17:05:29 -0600 Subject: [PATCH 04/21] =?UTF-8?q?feat:=20agregu=C3=A9=20el=20modal=20para?= =?UTF-8?q?=20renderizar=20el=20formularioactualizarproducto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Formularios/CamposActualizarProducto.jsx | 404 ++++++++++++++++++ .../FormularioActualizarProducto.jsx | 147 +++++++ .../Paginas/Productos/ListaProductos.jsx | 17 +- 3 files changed, 566 insertions(+), 2 deletions(-) create mode 100644 src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx create mode 100644 src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx new file mode 100644 index 00000000..6072eed4 --- /dev/null +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx @@ -0,0 +1,404 @@ +//RF[29] Actualiza Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF29] +import { memo } from 'react'; +import { Grid } from '@mui/material'; +import Texto from '@Atomos/Texto'; +import CampoTexto from '@Atomos/CampoTexto'; +import CampoSelect from '@Atomos/CampoSelect'; +import TarjetaAccion from '@Moleculas/TarjetaAccion'; +import TarjetaElementoAccion from '@Moleculas/TarjetaElementoAccion'; + +const CampoTextoFormulario = memo( + ({ + etiqueta, + nombre, + valor, + onChange, + helperText, + error, + placeholder, + tipo = 'text', + multilinea = false, + filas = 1, + maxLongitud = 100, + maxLongitudDescripcion = 300, + ...rest + }) => { + const limiteCaracteres = nombre === 'descripcion' ? maxLongitudDescripcion : maxLongitud; + + return ( + + { + const nuevoValor = evento.target.value.slice(0, limiteCaracteres); + onChange({ target: { name: nombre, value: nuevoValor } }); + }} + helperText={ + tipo === 'text' && limiteCaracteres + ? `${valor.length}/${limiteCaracteres} - Máximo de caracteres. ${helperText || ''}` + : helperText + } + type={tipo} + size='medium' + required={true} + placeholder={placeholder} + multiline={multilinea} + rows={filas} + error={error} + inputProps={{ maxLength: tipo === 'text' ? limiteCaracteres : undefined }} + {...rest} + /> + + ); + } +); + +const CampoSelectFormulario = memo( + ({ + etiqueta, + nombre, + opciones, + valor, + onChange, + placeholder, + helperText, + error, + tamano = 6, + required = false, + }) => ( + + + + ) +); + +const TituloFormulario = memo(({ titulo, varianteTitulo, tamano = 12 }) => ( + + + {titulo} + + +)); + +const CampoImagenProducto = memo( + ({ imagenProducto, setImagenes, refInputArchivo, alAgregarImagenProducto }) => ( + <> + {imagenProducto ? ( + + setImagenes((prev) => ({ ...prev, imagenProducto: null }))} + tooltipEliminar='Eliminar' + borderColor='primary.light' + backgroundColor='primary.lighter' + iconColor='primary' + iconSize='large' + textoVariant='caption' + tabIndex={0} + disabled={false} + /> + + ) : ( + <> + + Sube la Imagen Principal del Producto Aquí + + + refInputArchivo.current.click()} + hoverScale={false} + /> + + + + )} + + ) +); + +const CamposActualizarProducto = memo( + ({ + producto, + imagenProducto, + refInputArchivo, + erroresProducto, + listaProveedores, + alActualizarProducto, + alAgregarImagenProducto, + setImagenes, + prevenirNumerosNegativos, + prevenirNumerosNoDecimales, + }) => { + return ( + <> + + + + + + + + + + + + + + + + + + + { + prevenirNumerosNegativos(evento); + if (evento.key === '.' || evento.key === ',') { + evento.preventDefault(); + } + }} + onInput={(evento) => { + const valor = evento.target.value; + if (valor === '' || /^\d+$/.test(valor)) { + evento.target.value = valor; + } else { + evento.target.value = valor.replace(/\D/g, ''); + } + }} + /> + + { + const valor = evento.target.value; + if ((valor.match(/\./g) || []).length > 1) { + evento.target.value = valor.slice(0, -1); + } + if (valor && parseFloat(valor) < 1) { + evento.target.value = 1; + } + }} + /> + + { + if (evento.target.value && evento.target.value < 1) { + evento.target.value = 1; + } + }} + /> + + { + if (evento.target.value && evento.target.value < 1) { + evento.target.value = 1; + } + }} + /> + + + + + + + + + + + + ); + } +); + +export default CamposActualizarProducto; diff --git a/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx new file mode 100644 index 00000000..f897bfb7 --- /dev/null +++ b/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx @@ -0,0 +1,147 @@ +//RF[29] Actualiza Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF29] +import { memo } from 'react'; +import { Box, Grid } from '@mui/material'; +import Texto from '@Atomos/Texto'; +import TarjetaAccion from '@Moleculas/TarjetaAccion'; +import ModalFlotante from '@Organismos/ModalFlotante'; +import CamposVariante from '@Organismos/Formularios/CamposVariante'; +import CamposActualizarProducto from '@Organismos/Formularios/CamposActualizarProducto'; +import ProductoFormProvider, { useProductoForm } from '@Hooks/Productos/ProductoFormProvider'; + +const TituloFormulario = memo(({ titulo, varianteTitulo, tamano = 12 }) => ( + + + {titulo} + + +)); + +const CampoCrear = memo(({ etiqueta, onClick }) => ( + + + +)); + +const ContenidoFormulario = memo(() => { + const { + refInputArchivo, + variantes, + idsVariantes, + producto, + imagenes, + setImagenes, + erroresProducto, + erroresVariantes, + intentosEnviar, + listaProveedores, + manejarCrearVariante, + manejarActualizarVariante, + manejarEliminarVariante, + manejarAgregarOpcion, + manejarActualizarOpcion, + manejarEliminarOpcion, + manejarAgregarImagenVariante, + manejarEliminarImagenVariante, + manejarActualizarProducto, + manejarAgregarImagenProducto, + prevenirNumerosNegativos, + prevenirNumerosNoDecimales, + } = useProductoForm(); + + return ( + <> + + + + + {idsVariantes.map((idVariante) => ( + + ))} + + + + + ); +}); + +const FormularioActualizarProducto = memo(({ formularioAbierto, alCerrarFormularioProducto }) => { + return ( + + + + ); +}); + +const FormularioModal = memo(({ formularioAbierto, alCerrarFormularioProducto }) => { + const { manejarCrearProducto, alerta, cargando, setAlerta } = useProductoForm(); + + return ( + <> + setAlerta(null), + } + : null + } + > + + + + ); +}); + +export default FormularioActualizarProducto; diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index 092b244d..e4550734 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -10,6 +10,7 @@ import ContenedorLista from '@Organismos/ContenedorLista'; import Alerta from '@Moleculas/Alerta'; import PopUp from '@Moleculas/PopUp'; import FormularioProducto from '@Organismos/Formularios/FormularioProducto'; +import FormularioActualizarProducto from '@Organismos/Formularios/FormularioActualizarProducto'; import FormularioProveedor from '@Organismos/Formularios/FormularioProveedor'; import { useConsultarProductos } from '@Hooks/Productos/useConsultarProductos'; import { useEliminarProductos } from '@Hooks/Productos/useEliminarProductos'; @@ -36,7 +37,7 @@ const ListaProductos = () => { const [openModalEliminar, setAbrirPopUp] = useState(false); const [abrirModalDetalle, setAbrirModalDetalle] = useState(false); const [imagenProducto, setImagenProducto] = useState(''); - + const [mostrarModalActualizarProducto, setMostrarModalActualizarProducto] = useState(false); const [openModalExportar, setAbrirPopUpExportar] = useState(false); const MENSAJE_POPUP_EXPORTAR = '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; @@ -104,6 +105,10 @@ const ListaProductos = () => { setMostrarModalProveedor(false); }, []); + const mostrarFormularioActualizarProducto = useCallback(() => { + setMostrarModalActualizarProducto(true); + }, []); + const mostrarFormularioProveedor = useCallback(() => { setMostrarModalProducto(false); setMostrarModalProveedor(true); @@ -266,6 +271,14 @@ const ListaProductos = () => { alMostrarFormularioProveedor={mostrarFormularioProveedor} /> )} + + {mostrarModalActualizarProducto && ( + + )} + {mostrarModalProveedor && ( { variant: 'contained', color: 'primary', backgroundColor: colores.altertex[1], - onClick: mostrarFormularioProducto, + onClick: mostrarFormularioActualizarProducto, disabled: !usuario?.permisos?.includes(PERMISOS.ACTUALIZAR_PRODUCTO), }, { From eb81894053f522b0c61bbd0b1bb4b058de3c8596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Fri, 6 Jun 2025 18:56:58 -0600 Subject: [PATCH 05/21] feat:implementacion del actualizar producto, falta las validaciones de campo --- src/Dominio/Modelos/Productos/InfoProducto.js | 17 +-- .../Formularios/CamposActualizarProducto.jsx | 5 +- .../FormularioActualizarProducto.jsx | 101 +++++++------ .../Paginas/Productos/ListaProductos.jsx | 2 +- src/hooks/Productos/ProductoFormProvider.jsx | 133 +++++++++++++++++- 5 files changed, 203 insertions(+), 55 deletions(-) diff --git a/src/Dominio/Modelos/Productos/InfoProducto.js b/src/Dominio/Modelos/Productos/InfoProducto.js index d9ef51e9..6dde4a9d 100644 --- a/src/Dominio/Modelos/Productos/InfoProducto.js +++ b/src/Dominio/Modelos/Productos/InfoProducto.js @@ -21,6 +21,7 @@ export class InfoProducto { this.envio = producto.envio ?? 0; this.impuesto = producto.impuesto ?? 0; this.descuento = producto.descuento ?? 0; + this.descripcion = producto.descripcion ?? ''; // Variantes del producto (puede ser un arreglo vacío) this.variantes = Array.isArray(producto.variantes) @@ -42,14 +43,14 @@ class VarianteProducto { class OpcionVariante { constructor({ - estado, - cantidad, - descuento, - valorOpcion, - SKUcomercial, - SKUautomatico, - costoAdicional, - }) { + estado, + cantidad, + descuento, + valorOpcion, + SKUcomercial, + SKUautomatico, + costoAdicional, + }) { this.estado = estado ?? null; this.cantidad = cantidad ?? 0; this.descuento = descuento ?? 0; diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx index 6072eed4..5df23474 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx @@ -30,13 +30,13 @@ const CampoTextoFormulario = memo( { const nuevoValor = evento.target.value.slice(0, limiteCaracteres); onChange({ target: { name: nombre, value: nuevoValor } }); }} helperText={ - tipo === 'text' && limiteCaracteres + tipo === 'text' && limiteCaracteres && valor ? `${valor.length}/${limiteCaracteres} - Máximo de caracteres. ${helperText || ''}` : helperText } @@ -152,6 +152,7 @@ const CamposActualizarProducto = memo( prevenirNumerosNegativos, prevenirNumerosNoDecimales, }) => { + if (!producto) return null; return ( <> diff --git a/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx index f897bfb7..a121d813 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx @@ -1,5 +1,5 @@ //RF[29] Actualiza Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF29] -import { memo } from 'react'; +import React, { memo } from 'react'; import { Box, Grid } from '@mui/material'; import Texto from '@Atomos/Texto'; import TarjetaAccion from '@Moleculas/TarjetaAccion'; @@ -22,7 +22,7 @@ const CampoCrear = memo(({ etiqueta, onClick }) => ( )); -const ContenidoFormulario = memo(() => { +const ContenidoFormulario = memo(({ detalleProducto }) => { const { refInputArchivo, variantes, @@ -47,7 +47,6 @@ const ContenidoFormulario = memo(() => { prevenirNumerosNegativos, prevenirNumerosNoDecimales, } = useProductoForm(); - return ( <> { mb: 3, }} > + {' '} { { ); }); -const FormularioActualizarProducto = memo(({ formularioAbierto, alCerrarFormularioProducto }) => { - return ( - - - - ); -}); +const FormularioActualizarProducto = memo( + ({ formularioAbierto, alCerrarFormularioProducto, detalleProducto }) => { + return ( + + + + ); + } +); -const FormularioModal = memo(({ formularioAbierto, alCerrarFormularioProducto }) => { - const { manejarCrearProducto, alerta, cargando, setAlerta } = useProductoForm(); +const FormularioModal = memo( + ({ formularioAbierto, alCerrarFormularioProducto, detalleProducto }) => { + const { + manejarGuardarProductoActualizado, + alerta, + cargando, + setAlerta, + inicializarDatosProducto, + } = useProductoForm(); - return ( - <> - setAlerta(null), - } - : null - } - > - - - - ); -}); + // Inicializar datos del producto cuando el formulario se abre + React.useEffect(() => { + if (formularioAbierto && detalleProducto) { + inicializarDatosProducto(detalleProducto); + } + }, [formularioAbierto, detalleProducto, inicializarDatosProducto]); + + return ( + <> + {' '} + setAlerta(null), + } + : null + } + > + + + + ); + } +); export default FormularioActualizarProducto; diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index e4550734..b5963ba0 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -127,7 +127,6 @@ const ListaProductos = () => { const manejarCancelarEliminar = () => { setAbrirPopUp(false); }; - const manejarConfirmarEliminar = async () => { try { const urlsImagenes = productos @@ -276,6 +275,7 @@ const ListaProductos = () => { )} diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index 3c861d9b..676c6361 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -288,12 +288,58 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }; }); }, []); - + // Esta función se usa para actualizar un campo individual del producto const manejarActualizarProducto = useCallback((evento) => { const { name, value } = evento.target; - setProducto((prev) => ({ ...prev, [name]: value })); + console.log(`Actualizando campo ${name} con valor ${value}`); + setProducto((prev) => { + const nuevoProducto = { ...prev, [name]: value }; + console.log('Estado actualizado del producto:', nuevoProducto); + return nuevoProducto; + }); }, []); + // Esta función se usa para guardar el producto actualizado cuando se presiona "Guardar" + const manejarGuardarProductoActualizado = useCallback(async () => { + try { + setCargando(true); + setAlerta({ + tipo: 'info', + mensaje: 'Actualizando producto...', + }); + + console.log('Guardando producto con datos:', { + producto, + variantes, + imagenes, + }); + + // Aquí iría la lógica para guardar el producto actualizado + // Por ahora, simulamos una actualización exitosa + + setTimeout(() => { + setAlerta({ + tipo: 'success', + mensaje: 'Producto actualizado con éxito', + }); + + setCargando(false); + + // Cerrar el formulario después de unos segundos + setTimeout(() => { + alCerrarFormularioProducto(); + }, 2000); + }, 1000); + } catch (error) { + console.error('Error al actualizar producto:', error); + setAlerta({ + tipo: 'error', + mensaje: 'Ocurrió un error al actualizar el producto', + }); + setCargando(false); + } + }, [producto, variantes, imagenes, alCerrarFormularioProducto]); + const manejarAgregarImagenVariante = useCallback( (idVariante, archivos) => { setImagenes((prev) => { @@ -490,7 +536,88 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = return imagenesActualizadas; }); }, [idsVariantes]); + // Función para inicializar los datos del formulario a partir de detalleProducto + const inicializarDatosProducto = useCallback((detalleProducto) => { + if (!detalleProducto) return; + + // Inicializar datos básicos del producto + setProducto({ + nombreComun: detalleProducto.nombreComun || '', + nombreComercial: detalleProducto.nombreComercial || '', + descripcion: detalleProducto.descripcion || '', + marca: detalleProducto.marca || '', + modelo: detalleProducto.modelo || '', + tipoProducto: detalleProducto.tipoProducto || '', + precioPuntos: detalleProducto.precioPuntos || undefined, + precioCliente: detalleProducto.precioCliente || undefined, + precioVenta: detalleProducto.precioVenta || undefined, + costo: detalleProducto.costo || undefined, + impuesto: detalleProducto.impuesto || 16, + descuento: detalleProducto.descuento || undefined, + estado: detalleProducto.estado || 1, + envio: detalleProducto.envio || 1, + idProveedor: detalleProducto.idProveedor || undefined, + }); + + // Inicializar las variantes si existen + if (detalleProducto.variantes && detalleProducto.variantes.length > 0) { + // Transformar el arreglo de variantes a un objeto con idVariante como clave + const variantesObj = {}; + const varianteIds = []; + + detalleProducto.variantes.forEach((variante) => { + const idVariante = variante.idVariante; + varianteIds.push(idVariante); + + variantesObj[idVariante] = { + nombreVariante: variante.nombreVariante || '', + descripcion: variante.descripcion || '', + opciones: + variante.opciones?.map((opcion) => ({ + id: Date.now() + Math.random(), + cantidad: opcion.cantidad || 0, + valorOpcion: opcion.valorOpcion || '', + SKUautomatico: opcion.SKUautomatico || '', + SKUcomercial: opcion.SKUcomercial || '', + costoAdicional: opcion.costoAdicional || undefined, + descuento: opcion.descuento || undefined, + estado: opcion.estado || 1, + })) || [], + }; + }); + + setVariantes(variantesObj); + setIdsVariantes(varianteIds); + + // Inicializar el objeto de imágenes para cada variante + const nuevasImagenes = { + imagenProducto: detalleProducto.imagenProducto || null, + imagenesVariantes: {}, + }; + varianteIds.forEach((id) => { + nuevasImagenes.imagenesVariantes[id] = []; + }); + + setImagenes(nuevasImagenes); + } else { + // Si no hay variantes, inicializar con una variante vacía + setVariantes({ + 1: { + nombreVariante: '', + descripcion: '', + opciones: [], + }, + }); + + setIdsVariantes([1]); + + setImagenes({ + imagenProducto: null, + imagenesVariantes: { 1: [] }, + }); + } + }, []); const contextValue = { refInputArchivo, alerta, @@ -514,9 +641,11 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = manejarEliminarImagenVariante, manejarCrearProducto, manejarActualizarProducto, + manejarGuardarProductoActualizado, manejarAgregarImagenProducto, prevenirNumerosNegativos, prevenirNumerosNoDecimales, + inicializarDatosProducto, }; return ( From 95180b8d8bb7cd9186e44ba6944d208ca0da7600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Fri, 6 Jun 2025 19:49:57 -0600 Subject: [PATCH 06/21] feat: mejorar validaciones en formularios de producto y variantes --- .../Validaciones/validarProducto.js | 35 +- .../Validaciones/validarVariantes.js | 44 +- .../Formularios/CamposActualizarProducto.jsx | 61 ++- .../Organismos/Formularios/CamposOpcion.jsx | 67 ++- src/hooks/Productos/ProductoFormProvider.jsx | 418 ++++++++++++++---- 5 files changed, 464 insertions(+), 161 deletions(-) diff --git a/src/Utilidades/Validaciones/validarProducto.js b/src/Utilidades/Validaciones/validarProducto.js index d2fadc4f..d1f814ae 100644 --- a/src/Utilidades/Validaciones/validarProducto.js +++ b/src/Utilidades/Validaciones/validarProducto.js @@ -51,28 +51,35 @@ export const validarProducto = (producto) => { } else if (!/^[1-9]\d{0,7}(\.\d{1,2})?$/.test(producto.costo.toString())) { errores.costo = 'El costo debe tener máximo 10 dígitos.'; } - // Validación de impuesto if (producto.impuesto === false) { errores.impuesto = 'El impuesto no es válido o el campo está vacío.'; - } - if (typeof producto.impuesto === 'number') { - if (!/^(0|[1-9]\d{0,4})(\.\d{1,2})?$/.test(producto.impuesto.toString())) { - errores.impuesto = 'El impuesto debe ser un número válido con máximo 5 dígitos.'; + } else if (producto.impuesto !== undefined && producto.impuesto !== null) { + if (typeof producto.impuesto !== 'number' && isNaN(Number(producto.impuesto))) { + errores.impuesto = 'El impuesto debe ser un número válido.'; + } else if (Number(producto.impuesto) < 0) { + errores.impuesto = 'El impuesto no puede ser negativo.'; + } else if (Number(producto.impuesto) > 99999.99) { + errores.impuesto = 'El impuesto no puede ser mayor a 99999.99.'; + } else if (!/^(0|[1-9]\d{0,4})(\.\d{1,2})?$/.test(producto.impuesto.toString())) { + errores.impuesto = + 'El impuesto debe ser un número válido con máximo 5 dígitos enteros y 2 decimales.'; } } - // Validación de descuento - - if (producto.descuento > 100) { - errores.descuento = 'El descuento debe estar entre 0 y 100.'; - } + // Validación de descuento if (producto.descuento === false) { errores.descuento = 'El descuento no es válido o el campo está vacío.'; - } - if (typeof producto.descuento === 'number') { - if (!/^(0|[1-9]\d{0,4})(\.\d{1,2})?$/.test(producto.descuento.toString())) { - errores.descuento = 'El descuento debe ser un número válido con máximo 5 dígitos.'; + } else if (producto.descuento !== undefined && producto.descuento !== null) { + if (typeof producto.descuento !== 'number' && isNaN(Number(producto.descuento))) { + errores.descuento = 'El descuento debe ser un número válido.'; + } else if (Number(producto.descuento) < 0) { + errores.descuento = 'El descuento no puede ser negativo.'; + } else if (Number(producto.descuento) > 100) { + errores.descuento = 'El descuento debe estar entre 0 y 100.'; + } else if (!/^(0|[1-9]?\d|100)(\.\d{1,2})?$/.test(producto.descuento.toString())) { + errores.descuento = + 'El descuento debe ser un número válido entre 0 y 100 con hasta 2 decimales.'; } } diff --git a/src/Utilidades/Validaciones/validarVariantes.js b/src/Utilidades/Validaciones/validarVariantes.js index 5e048413..88793f9e 100644 --- a/src/Utilidades/Validaciones/validarVariantes.js +++ b/src/Utilidades/Validaciones/validarVariantes.js @@ -41,13 +41,17 @@ export const validarVariantes = (variantes) => { erroresOpcion.valorOpcion = 'El valor de la opción es obligatorio'; } else if (opcion.valorOpcion.trim().length > 50) { erroresOpcion.valorOpcion = 'El valor de la opción debe tener máximo 50 caracteres'; - } - - // Validación de cantidad - if (!Number.isFinite(opcion.cantidad) || opcion.cantidad <= 0) { - erroresOpcion.cantidad = 'La cantidad debe ser un número mayor a 0'; - } else if (!/^\d{1,10}$/.test(opcion.cantidad.toString())) { - erroresOpcion.cantidad = 'La cantidad debe tener máximo 10 dígitos.'; + } // Validación de cantidad + if (opcion.cantidad === undefined || opcion.cantidad === null || opcion.cantidad === '') { + erroresOpcion.cantidad = 'La cantidad es obligatoria'; + } else { + const cantidadNum = Number(opcion.cantidad); + if (isNaN(cantidadNum) || cantidadNum <= 0) { + erroresOpcion.cantidad = 'La cantidad debe ser un número mayor a 0'; + } else if (cantidadNum > 9999999999) { + // 10 dígitos máximo (10^10 - 1) + erroresOpcion.cantidad = 'La cantidad no puede ser mayor a 9,999,999,999'; + } } // Validación de descuento @@ -62,13 +66,25 @@ export const validarVariantes = (variantes) => { if (!/^(0|[1-9]\d{0,4})(\.\d{1,2})?$/.test(opcion.descuento.toString())) { erroresOpcion.descuento = 'El descuento debe ser un número válido con máximo 5 dígitos.'; } - } - - // Validación de costo adicional con formato (10,2) - if (!Number.isFinite(opcion.costoAdicional) || opcion.costoAdicional < 0) { - erroresOpcion.costoAdicional = 'El costo adicional no es válido o el campo está vacío.'; - } else if (!/^\d{1,8}(\.\d{1,2})?$/.test(opcion.costoAdicional.toString())) { - erroresOpcion.costoAdicional = 'El costo adicional debe tener máximo 10 dígitos.'; + } // Validación de costo adicional con formato (10,2) + if ( + opcion.costoAdicional !== undefined && + opcion.costoAdicional !== null && + opcion.costoAdicional !== '' + ) { + const costoNum = Number(opcion.costoAdicional); + if (isNaN(costoNum)) { + erroresOpcion.costoAdicional = 'El costo adicional debe ser un número válido'; + } else if (costoNum < 0) { + erroresOpcion.costoAdicional = 'El costo adicional no puede ser negativo'; + } else { + const partes = costoNum.toString().split('.'); + if (partes[0] && partes[0].length > 8) { + erroresOpcion.costoAdicional = 'El costo adicional debe tener máximo 8 dígitos enteros'; + } else if (partes[1] && partes[1].length > 2) { + erroresOpcion.costoAdicional = 'El costo adicional debe tener máximo 2 decimales'; + } + } } if (!opcion.SKUautomatico?.trim()) { diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx index 5df23474..9d6f9d46 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx @@ -156,7 +156,6 @@ const CamposActualizarProducto = memo( return ( <> - - - - - - - - - - - - - + />{' '} - + onInput={(evento) => { + const valor = evento.target.value; + // Limitar a 5 dígitos enteros y 2 decimales + if (valor) { + const partes = valor.split('.'); + if (partes[0] && partes[0].length > 5) { + // Si la parte entera tiene más de 5 dígitos, truncarla + partes[0] = partes[0].substring(0, 5); + evento.target.value = partes.join('.'); + } + // Si el número es mayor a 99999.99, establecerlo al máximo + if (parseFloat(valor) > 99999.99) { + evento.target.value = '99999.99'; + } + } + }} + />{' '} { + const valor = evento.target.value; + // Limitar a 3 dígitos (máximo 100) + if (valor) { + // Si el valor es mayor que 100, establecerlo a 100 + if (parseFloat(valor) > 100) { + evento.target.value = '100'; + } + // Si tiene más de 5 dígitos en total, truncarlo + const partes = valor.split('.'); + if (partes[0] && partes[0].length > 3) { + partes[0] = partes[0].substring(0, 3); + evento.target.value = partes.join('.'); + } + } + }} /> - - - manejarActualizarOpcion('valorOpcion', evento.target.value)} error={Boolean(errores?.valorOpcion)} helperText={errores?.valorOpcion || ''} - /> + />{' '} manejarActualizarOpcion('cantidad', evento.target.value)} placeholder='Ingresa la cantidad' error={Boolean(errores?.cantidad)} - helperText={errores?.cantidad || ''} + helperText={errores?.cantidad || 'Valor entero positivo (máx. 9,999,999,999)'} min={1} + max={9999999999} onKeyDown={(evento) => { prevenirNumerosNegativos(evento); }} onInput={(evento) => { // Solo permite números enteros positivos const valor = evento.target.value; - if (valor === '' || /^\d+$/.test(valor)) { - evento.target.value = valor; + if (valor === '') { + evento.target.value = ''; + } else if (/^\d+$/.test(valor)) { + // Convertir a número para verificar el rango + const num = Number(valor); + if (num > 9999999999) { + evento.target.value = '9999999999'; + } else if (valor.length > 10) { + evento.target.value = valor.slice(0, 10); + } } else { - evento.target.value = valor.replace(/\D/g, ''); + evento.target.value = valor.replace(/\D/g, '').slice(0, 10); } }} /> @@ -184,7 +193,7 @@ const CamposOpcion = memo( onChange={(evento) => manejarActualizarOpcion('SKUcomercial', evento.target.value)} error={Boolean(errores?.SKUcomercial)} helperText={errores?.SKUcomercial || ''} - /> + />{' '} manejarActualizarOpcion('costoAdicional', evento.target.value)} placeholder='Ingresa el costo adicional' - helperText={errores?.costoAdicional} // Consolidado con helperText - error={errores?.costoAdicional} + helperText={errores?.costoAdicional || 'Máximo 8 dígitos enteros y 2 decimales'} + error={Boolean(errores?.costoAdicional)} min={0} onKeyDown={prevenirNumerosNoDecimales} - /*onInput={(evento) => { - if (evento.target.value && evento.target.value < 1) { - evento.target.value = 1; + onInput={(evento) => { + const valor = evento.target.value; + if (valor) { + const partes = valor.split('.'); + // Limitar a 8 dígitos enteros + if (partes[0] && partes[0].length > 8) { + partes[0] = partes[0].substring(0, 8); + evento.target.value = partes.join('.'); + } + // Limitar a 2 decimales + if (partes[1] && partes[1].length > 2) { + partes[1] = partes[1].substring(0, 2); + evento.target.value = partes.join('.'); + } } - }}*/ - /> + }} + />{' '} manejarActualizarOpcion('descuento', evento.target.value)} placeholder='Ingresa el descuento' - helperText={errores?.descuento} + helperText={errores?.descuento || 'Valores entre 0 y 100'} error={errores?.descuento} min={0} + max={100} onKeyDown={prevenirNumerosNoDecimales} - /*onInput={(evento) => { - if (evento.target.value && evento.target.value < 1) { - evento.target.value = 1; + onInput={(evento) => { + const valor = evento.target.value; + if (valor) { + // Limitar a un valor máximo de 100 + if (parseFloat(valor) > 100) { + evento.target.value = '100'; + } + // Limitar a 2 decimales + const partes = valor.split('.'); + if (partes[1] && partes[1].length > 2) { + partes[1] = partes[1].substring(0, 2); + evento.target.value = partes.join('.'); + } } - }}*/ + }} /> { export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) => { const refInputArchivo = useRef(); - const [cargando, setCargando] = useState(false); - + const [intentosEnviar, setIntentosEnviar] = useState(0); const [alerta, setAlerta] = useState(null); const [variantes, setVariantes] = useState({ @@ -74,9 +75,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = imagenProducto: null, imagenesVariantes: {}, }); - const { proveedores } = useConsultarProveedores(); - const { erroresProducto, erroresVariantes, guardarProducto } = useCrearProducto(); + const { guardarProducto } = useCrearProducto(); + + // Estados para manejar los errores + const [erroresProducto, setErroresProducto] = useState({}); + const [erroresVariantes, setErroresVariantes] = useState({}); const generarSKUAutomatico = useGenerarSKU(); @@ -107,8 +111,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }, })); }, [idsVariantes]); - const manejarActualizarVariante = useCallback((idVariante, campo, valor) => { + // Primero actualizar el estado de las variantes setVariantes((prev) => { if (prev[idVariante] && prev[idVariante][campo] === valor) { return prev; @@ -121,8 +125,51 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = [campo]: valor, }, }; - }, []); - }); + }); + + // Luego, en una operación separada, validar y actualizar errores + setTimeout(() => { + setVariantes((variantesActuales) => { + if (!variantesActuales[idVariante]) return variantesActuales; + + // Validar solo la variante actualizada + const varianteParaValidar = { + [idVariante]: { + ...variantesActuales[idVariante], + identificador: idVariante, + }, + }; + + const validacionPartial = validarVariantes(varianteParaValidar); + + // Actualizar errores de la variante específica + setErroresVariantes((prevErrores) => { + const nuevosErrores = { ...prevErrores }; + + if (validacionPartial[idVariante] && validacionPartial[idVariante][campo]) { + // Si hay un error para este campo en esta variante + if (!nuevosErrores[idVariante]) { + nuevosErrores[idVariante] = {}; + } + nuevosErrores[idVariante][campo] = validacionPartial[idVariante][campo]; + } else if (nuevosErrores[idVariante]) { + // Si no hay error, eliminarlo + delete nuevosErrores[idVariante][campo]; + + // Si la variante no tiene más errores, eliminar la entrada + if (Object.keys(nuevosErrores[idVariante]).length === 0) { + delete nuevosErrores[idVariante]; + } + } + + return nuevosErrores; + }); + + // No modificar el estado, solo devolver el actual + return variantesActuales; + }); + }, 0); + }, []); const manejarEliminarVariante = useCallback((idVariante) => { setVariantes((prev) => { @@ -211,7 +258,6 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }, [generarSKUAutomatico, producto.nombreComun] ); - const manejarActualizarOpcion = useCallback( (idVariante, indiceOpcion, campo, valor) => { setVariantes((prev) => { @@ -219,7 +265,6 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = if (!varianteActual) return prev; const opcionesActuales = varianteActual.opciones || []; - if (indiceOpcion < 0 || indiceOpcion >= opcionesActuales.length) { return prev; } @@ -245,13 +290,74 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }; } - return { + const nuevasVariantes = { ...prev, [idVariante]: { ...varianteActual, opciones: opcionesActualizadas, }, }; + + // Validar solo la variante actualizada + const varianteParaValidar = { + [idVariante]: { + ...nuevasVariantes[idVariante], + identificador: idVariante, + }, + }; + + const validacionPartial = validarVariantes(varianteParaValidar); + + // Actualizar errores de la opción específica + setErroresVariantes((prevErrores) => { + const nuevosErrores = { ...prevErrores }; + + if ( + validacionPartial[idVariante] && + validacionPartial[idVariante].opciones && + validacionPartial[idVariante].opciones[indiceOpcion] && + validacionPartial[idVariante].opciones[indiceOpcion][campo] + ) { + // Asegurarse de que existe la estructura para guardar el error + if (!nuevosErrores[idVariante]) { + nuevosErrores[idVariante] = { opciones: {} }; + } else if (!nuevosErrores[idVariante].opciones) { + nuevosErrores[idVariante].opciones = {}; + } + + if (!nuevosErrores[idVariante].opciones[indiceOpcion]) { + nuevosErrores[idVariante].opciones[indiceOpcion] = {}; + } + + // Guardar el error de este campo específico + nuevosErrores[idVariante].opciones[indiceOpcion][campo] = + validacionPartial[idVariante].opciones[indiceOpcion][campo]; + } else if ( + nuevosErrores[idVariante] && + nuevosErrores[idVariante].opciones && + nuevosErrores[idVariante].opciones[indiceOpcion] + ) { + // Eliminar el error si ya no existe + delete nuevosErrores[idVariante].opciones[indiceOpcion][campo]; + + // Limpiar la estructura si ya no hay errores + if (Object.keys(nuevosErrores[idVariante].opciones[indiceOpcion]).length === 0) { + delete nuevosErrores[idVariante].opciones[indiceOpcion]; + } + + if (Object.keys(nuevosErrores[idVariante].opciones).length === 0) { + delete nuevosErrores[idVariante].opciones; + } + + if (Object.keys(nuevosErrores[idVariante]).length === 0) { + delete nuevosErrores[idVariante]; + } + } + + return nuevosErrores; + }); + + return nuevasVariantes; }); }, [generarSKUAutomatico, producto.nombreComun] @@ -287,22 +393,140 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }, }; }); - }, []); - // Esta función se usa para actualizar un campo individual del producto + }, []); // Esta función se usa para actualizar y validar un campo individual del producto const manejarActualizarProducto = useCallback((evento) => { const { name, value } = evento.target; console.log(`Actualizando campo ${name} con valor ${value}`); + + // Primero actualizar el estado del producto setProducto((prev) => { const nuevoProducto = { ...prev, [name]: value }; console.log('Estado actualizado del producto:', nuevoProducto); return nuevoProducto; }); - }, []); + // Luego, en una operación separada, validar y actualizar errores + setTimeout(() => { + // Esta validación se ejecutará después de que el estado se haya actualizado + setProducto((productoActual) => { + // Validar solo el campo que se está actualizando + const validacionPartial = validarProducto(productoActual); + + // Actualizar solo el error del campo específico + setErroresProducto((prevErrores) => { + const nuevosErrores = { ...prevErrores }; + + // Si hay un error para este campo, actualizarlo + if (validacionPartial[name]) { + nuevosErrores[name] = validacionPartial[name]; + } else { + // Si no hay error, eliminarlo de la lista de errores + delete nuevosErrores[name]; + } + + return nuevosErrores; + }); + + // Devolver el mismo estado, no estamos modificando nada aquí + return productoActual; + }); + }, 0); + }, []); // Esta función se usa para guardar el producto actualizado cuando se presiona "Guardar" const manejarGuardarProductoActualizado = useCallback(async () => { try { setCargando(true); + setAlerta({ + tipo: 'info', + mensaje: 'Validando datos del producto...', + }); + + // Convertir variantes de objeto a array para validación + const variantesArray = Object.entries(variantes).map(([idVariante, datos]) => ({ + identificador: idVariante, + ...datos, + })); + + // Validar datos del producto + const erroresValidacionProducto = validarProducto(producto); + setErroresProducto(erroresValidacionProducto); + + // Validar datos de las variantes + const erroresValidacionVariantes = validarVariantes(variantes); + setErroresVariantes(erroresValidacionVariantes); // Verificar si hay errores + if ( + Object.keys(erroresValidacionProducto).length > 0 || + Object.keys(erroresValidacionVariantes).length > 0 + ) { + // Incrementar contador de intentos enviar para mostrar todos los errores + setIntentosEnviar((prevIntentos) => prevIntentos + 1); + + // Crear un mensaje de error más detallado + let mensajeError = 'Por favor revisa los siguientes campos:'; + + // Agregar errores del producto + if (Object.keys(erroresValidacionProducto).length > 0) { + const camposConErrores = Object.keys(erroresValidacionProducto) + .map((campo) => { + // Convertir camelCase a texto legible + const nombreCampo = campo + .replace(/([A-Z])/g, ' $1') // Insertar un espacio antes de cada letra mayúscula + .toLowerCase() // Convertir todo a minúsculas + .replace(/^./, (str) => str.toUpperCase()); // Capitalizar la primera letra + return nombreCampo; + }) + .join(', '); + + mensajeError += `\n- Datos del producto: ${camposConErrores}`; + } + + // Agregar errores de las variantes + if (Object.keys(erroresValidacionVariantes).length > 0) { + mensajeError += '\n- Hay errores en una o más variantes y sus opciones'; + } + + setAlerta({ + tipo: 'error', + mensaje: mensajeError, + }); + setCargando(false); + return; + } + + // Validar que haya al menos una variante + if (!variantesArray.length) { + setAlerta({ + tipo: 'error', + mensaje: 'Debes agregar al menos una variante al producto.', + }); + setCargando(false); + return; + } + + // Validar que cada variante tenga al menos una opción + const variantesSinOpciones = variantesArray.filter( + (variante) => !Array.isArray(variante.opciones) || variante.opciones.length === 0 + ); + + if (variantesSinOpciones.length > 0) { + setAlerta({ + tipo: 'error', + mensaje: 'Cada variante debe incluir al menos una opción disponible.', + }); + setCargando(false); + return; + } + + // Validar imagen del producto + if (!imagenes.imagenProducto) { + setAlerta({ + tipo: 'error', + mensaje: 'Debes seleccionar una imagen principal para el producto.', + }); + setCargando(false); + return; + } + setAlerta({ tipo: 'info', mensaje: 'Actualizando producto...', @@ -310,13 +534,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = console.log('Guardando producto con datos:', { producto, - variantes, + variantes: variantesArray, imagenes, }); - // Aquí iría la lógica para guardar el producto actualizado + // Aquí iría la lógica para enviar los datos actualizados al backend // Por ahora, simulamos una actualización exitosa - setTimeout(() => { setAlerta({ tipo: 'success', @@ -334,7 +557,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = console.error('Error al actualizar producto:', error); setAlerta({ tipo: 'error', - mensaje: 'Ocurrió un error al actualizar el producto', + mensaje: + 'Ocurrió un error al actualizar el producto: ' + (error.message || 'Error desconocido'), }); setCargando(false); } @@ -535,89 +759,96 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = return imagenesActualizadas; }); - }, [idsVariantes]); - // Función para inicializar los datos del formulario a partir de detalleProducto - const inicializarDatosProducto = useCallback((detalleProducto) => { - if (!detalleProducto) return; - - // Inicializar datos básicos del producto - setProducto({ - nombreComun: detalleProducto.nombreComun || '', - nombreComercial: detalleProducto.nombreComercial || '', - descripcion: detalleProducto.descripcion || '', - marca: detalleProducto.marca || '', - modelo: detalleProducto.modelo || '', - tipoProducto: detalleProducto.tipoProducto || '', - precioPuntos: detalleProducto.precioPuntos || undefined, - precioCliente: detalleProducto.precioCliente || undefined, - precioVenta: detalleProducto.precioVenta || undefined, - costo: detalleProducto.costo || undefined, - impuesto: detalleProducto.impuesto || 16, - descuento: detalleProducto.descuento || undefined, - estado: detalleProducto.estado || 1, - envio: detalleProducto.envio || 1, - idProveedor: detalleProducto.idProveedor || undefined, - }); - - // Inicializar las variantes si existen - if (detalleProducto.variantes && detalleProducto.variantes.length > 0) { - // Transformar el arreglo de variantes a un objeto con idVariante como clave - const variantesObj = {}; - const varianteIds = []; - - detalleProducto.variantes.forEach((variante) => { - const idVariante = variante.idVariante; - varianteIds.push(idVariante); - - variantesObj[idVariante] = { - nombreVariante: variante.nombreVariante || '', - descripcion: variante.descripcion || '', - opciones: - variante.opciones?.map((opcion) => ({ - id: Date.now() + Math.random(), - cantidad: opcion.cantidad || 0, - valorOpcion: opcion.valorOpcion || '', - SKUautomatico: opcion.SKUautomatico || '', - SKUcomercial: opcion.SKUcomercial || '', - costoAdicional: opcion.costoAdicional || undefined, - descuento: opcion.descuento || undefined, - estado: opcion.estado || 1, - })) || [], - }; + }, [idsVariantes]); // Función para inicializar los datos del formulario a partir de detalleProducto + const inicializarDatosProducto = useCallback( + (detalleProducto) => { + if (!detalleProducto) return; + + // Resetear estados de errores al inicializar un producto + setErroresProducto({}); + setErroresVariantes({}); + setIntentosEnviar(0); + + // Inicializar datos básicos del producto + setProducto({ + nombreComun: detalleProducto.nombreComun || '', + nombreComercial: detalleProducto.nombreComercial || '', + descripcion: detalleProducto.descripcion || '', + marca: detalleProducto.marca || '', + modelo: detalleProducto.modelo || '', + tipoProducto: detalleProducto.tipoProducto || '', + precioPuntos: detalleProducto.precioPuntos || undefined, + precioCliente: detalleProducto.precioCliente || undefined, + precioVenta: detalleProducto.precioVenta || undefined, + costo: detalleProducto.costo || undefined, + impuesto: detalleProducto.impuesto || 16, + descuento: detalleProducto.descuento || undefined, + estado: detalleProducto.estado || 1, + envio: detalleProducto.envio || 1, + idProveedor: detalleProducto.idProveedor || undefined, }); - setVariantes(variantesObj); - setIdsVariantes(varianteIds); + // Inicializar las variantes si existen + if (detalleProducto.variantes && detalleProducto.variantes.length > 0) { + // Transformar el arreglo de variantes a un objeto con idVariante como clave + const variantesObj = {}; + const varianteIds = []; + + detalleProducto.variantes.forEach((variante) => { + const idVariante = variante.idVariante; + varianteIds.push(idVariante); + + variantesObj[idVariante] = { + nombreVariante: variante.nombreVariante || '', + descripcion: variante.descripcion || '', + opciones: + variante.opciones?.map((opcion) => ({ + id: Date.now() + Math.random(), + cantidad: opcion.cantidad || 0, + valorOpcion: opcion.valorOpcion || '', + SKUautomatico: opcion.SKUautomatico || '', + SKUcomercial: opcion.SKUcomercial || '', + costoAdicional: opcion.costoAdicional || undefined, + descuento: opcion.descuento || undefined, + estado: opcion.estado || 1, + })) || [], + }; + }); - // Inicializar el objeto de imágenes para cada variante - const nuevasImagenes = { - imagenProducto: detalleProducto.imagenProducto || null, - imagenesVariantes: {}, - }; + setVariantes(variantesObj); + setIdsVariantes(varianteIds); - varianteIds.forEach((id) => { - nuevasImagenes.imagenesVariantes[id] = []; - }); + // Inicializar el objeto de imágenes para cada variante + const nuevasImagenes = { + imagenProducto: detalleProducto.imagenProducto || null, + imagenesVariantes: {}, + }; - setImagenes(nuevasImagenes); - } else { - // Si no hay variantes, inicializar con una variante vacía - setVariantes({ - 1: { - nombreVariante: '', - descripcion: '', - opciones: [], - }, - }); + varianteIds.forEach((id) => { + nuevasImagenes.imagenesVariantes[id] = []; + }); + + setImagenes(nuevasImagenes); + } else { + // Si no hay variantes, inicializar con una variante vacía + setVariantes({ + 1: { + nombreVariante: '', + descripcion: '', + opciones: [], + }, + }); - setIdsVariantes([1]); + setIdsVariantes([1]); - setImagenes({ - imagenProducto: null, - imagenesVariantes: { 1: [] }, - }); - } - }, []); + setImagenes({ + imagenProducto: null, + imagenesVariantes: { 1: [] }, + }); + } + }, + [setErroresProducto, setErroresVariantes, setIntentosEnviar] + ); const contextValue = { refInputArchivo, alerta, @@ -629,6 +860,7 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = setImagenes, erroresProducto, erroresVariantes, + intentosEnviar, listaProveedores, cargando, manejarCrearVariante, From d86ec13b097942aff41fc26fdbf3003780da8795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Fri, 6 Jun 2025 20:03:55 -0600 Subject: [PATCH 07/21] =?UTF-8?q?feat:=20mejorar=20validaciones=20en=20for?= =?UTF-8?q?mularios=20de=20producto=20y=20variantes,=20optimizando=20la=20?= =?UTF-8?q?estructura=20del=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Validaciones/validarProducto.js | 12 +++++----- .../Validaciones/validarVariantes.js | 6 ++--- .../Organismos/Formularios/CamposVariante.jsx | 4 ++-- .../FormularioActualizarProducto.jsx | 4 ++-- .../Paginas/Productos/ListaProductos.jsx | 4 ++-- src/hooks/Productos/ProductoFormProvider.jsx | 24 +++++++++---------- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Utilidades/Validaciones/validarProducto.js b/src/Utilidades/Validaciones/validarProducto.js index d1f814ae..3c21e294 100644 --- a/src/Utilidades/Validaciones/validarProducto.js +++ b/src/Utilidades/Validaciones/validarProducto.js @@ -11,8 +11,8 @@ export const validarProducto = (producto) => { if (producto.precioPuntos == null || producto.precioPuntos === '') { errores.precioPuntos = 'El precio en puntos es obligatorio.'; } else if ( - Number(producto.precioPuntos) <= 0 || - !Number.isInteger(Number(producto.precioPuntos)) + Number(producto.precioPuntos) <= 0 + || !Number.isInteger(Number(producto.precioPuntos)) ) { errores.precioPuntos = 'El precio en puntos debe ser un número entero positivo.'; } else if (!/^[1-9]\d{0,9}$/.test(producto.precioPuntos.toString())) { @@ -62,8 +62,8 @@ export const validarProducto = (producto) => { } else if (Number(producto.impuesto) > 99999.99) { errores.impuesto = 'El impuesto no puede ser mayor a 99999.99.'; } else if (!/^(0|[1-9]\d{0,4})(\.\d{1,2})?$/.test(producto.impuesto.toString())) { - errores.impuesto = - 'El impuesto debe ser un número válido con máximo 5 dígitos enteros y 2 decimales.'; + errores.impuesto + = 'El impuesto debe ser un número válido con máximo 5 dígitos enteros y 2 decimales.'; } } @@ -78,8 +78,8 @@ export const validarProducto = (producto) => { } else if (Number(producto.descuento) > 100) { errores.descuento = 'El descuento debe estar entre 0 y 100.'; } else if (!/^(0|[1-9]?\d|100)(\.\d{1,2})?$/.test(producto.descuento.toString())) { - errores.descuento = - 'El descuento debe ser un número válido entre 0 y 100 con hasta 2 decimales.'; + errores.descuento + = 'El descuento debe ser un número válido entre 0 y 100 con hasta 2 decimales.'; } } diff --git a/src/Utilidades/Validaciones/validarVariantes.js b/src/Utilidades/Validaciones/validarVariantes.js index 88793f9e..9331e7a7 100644 --- a/src/Utilidades/Validaciones/validarVariantes.js +++ b/src/Utilidades/Validaciones/validarVariantes.js @@ -68,9 +68,9 @@ export const validarVariantes = (variantes) => { } } // Validación de costo adicional con formato (10,2) if ( - opcion.costoAdicional !== undefined && - opcion.costoAdicional !== null && - opcion.costoAdicional !== '' + opcion.costoAdicional !== undefined + && opcion.costoAdicional !== null + && opcion.costoAdicional !== '' ) { const costoNum = Number(opcion.costoAdicional); if (isNaN(costoNum)) { diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx index b7c96497..70fa67d3 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposVariante.jsx @@ -189,8 +189,8 @@ const CamposVariante = memo( alAgregarOpcion(varianteId); }, [varianteId, alAgregarOpcion]); - const errores = - erroresVariantes && erroresVariantes[varianteId] ? erroresVariantes[varianteId] : {}; + const errores + = erroresVariantes && erroresVariantes[varianteId] ? erroresVariantes[varianteId] : {}; return ( <> diff --git a/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx index a121d813..cea7dc48 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx @@ -22,7 +22,7 @@ const CampoCrear = memo(({ etiqueta, onClick }) => ( )); -const ContenidoFormulario = memo(({ detalleProducto }) => { +const ContenidoFormulario = memo(() => { const { refInputArchivo, variantes, @@ -154,7 +154,7 @@ const FormularioModal = memo( : null } > - + ); diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index b5963ba0..0fa629a0 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -39,8 +39,8 @@ const ListaProductos = () => { const [imagenProducto, setImagenProducto] = useState(''); const [mostrarModalActualizarProducto, setMostrarModalActualizarProducto] = useState(false); const [openModalExportar, setAbrirPopUpExportar] = useState(false); - const MENSAJE_POPUP_EXPORTAR = - '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; + const MENSAJE_POPUP_EXPORTAR + = '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; const manejarCancelarExportar = () => { setAbrirPopUpExportar(false); }; diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index 506195e4..ef815adc 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -313,10 +313,10 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const nuevosErrores = { ...prevErrores }; if ( - validacionPartial[idVariante] && - validacionPartial[idVariante].opciones && - validacionPartial[idVariante].opciones[indiceOpcion] && - validacionPartial[idVariante].opciones[indiceOpcion][campo] + validacionPartial[idVariante] + && validacionPartial[idVariante].opciones + && validacionPartial[idVariante].opciones[indiceOpcion] + && validacionPartial[idVariante].opciones[indiceOpcion][campo] ) { // Asegurarse de que existe la estructura para guardar el error if (!nuevosErrores[idVariante]) { @@ -330,12 +330,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = } // Guardar el error de este campo específico - nuevosErrores[idVariante].opciones[indiceOpcion][campo] = - validacionPartial[idVariante].opciones[indiceOpcion][campo]; + nuevosErrores[idVariante].opciones[indiceOpcion][campo] + = validacionPartial[idVariante].opciones[indiceOpcion][campo]; } else if ( - nuevosErrores[idVariante] && - nuevosErrores[idVariante].opciones && - nuevosErrores[idVariante].opciones[indiceOpcion] + nuevosErrores[idVariante] + && nuevosErrores[idVariante].opciones + && nuevosErrores[idVariante].opciones[indiceOpcion] ) { // Eliminar el error si ya no existe delete nuevosErrores[idVariante].opciones[indiceOpcion][campo]; @@ -455,8 +455,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const erroresValidacionVariantes = validarVariantes(variantes); setErroresVariantes(erroresValidacionVariantes); // Verificar si hay errores if ( - Object.keys(erroresValidacionProducto).length > 0 || - Object.keys(erroresValidacionVariantes).length > 0 + Object.keys(erroresValidacionProducto).length > 0 + || Object.keys(erroresValidacionVariantes).length > 0 ) { // Incrementar contador de intentos enviar para mostrar todos los errores setIntentosEnviar((prevIntentos) => prevIntentos + 1); @@ -558,7 +558,7 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = setAlerta({ tipo: 'error', mensaje: - 'Ocurrió un error al actualizar el producto: ' + (error.message || 'Error desconocido'), + `Ocurrió un error al actualizar el producto: ${error.message || 'Error desconocido'}`, }); setCargando(false); } From 7b15276517214b45eac225d369283ea5d10419cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Mon, 9 Jun 2025 18:53:47 -0600 Subject: [PATCH 08/21] =?UTF-8?q?feat:=20Implementar=20funcionalidad=20de?= =?UTF-8?q?=20actualizaci=C3=B3n=20de=20productos=20y=20variantes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RepositorioActualizarProducto.js | 160 ++++++++ src/Utilidades/Constantes/rutasAPI.js | 3 +- .../Formularios/CamposActualizarProducto.jsx | 55 +-- .../FormularioActualizarProducto.jsx | 24 +- .../Paginas/Productos/ListaProductos.jsx | 8 +- src/hooks/Productos/ProductoFormProvider.jsx | 366 +++++++++++++----- src/hooks/Productos/useActualizarProducto.js | 57 +++ 7 files changed, 549 insertions(+), 124 deletions(-) create mode 100644 src/Dominio/Repositorios/Productos/RepositorioActualizarProducto.js create mode 100644 src/hooks/Productos/useActualizarProducto.js diff --git a/src/Dominio/Repositorios/Productos/RepositorioActualizarProducto.js b/src/Dominio/Repositorios/Productos/RepositorioActualizarProducto.js new file mode 100644 index 00000000..2ffded0a --- /dev/null +++ b/src/Dominio/Repositorios/Productos/RepositorioActualizarProducto.js @@ -0,0 +1,160 @@ +// RF [29] Actualiza Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF29] +import axios from 'axios'; +import { RUTAS_API } from '@Utilidades/Constantes/rutasAPI'; +import ProductoCompleto from '@Modelos/Productos/ProductoCompleto'; +import Variante from '@Modelos/Productos/Variante'; +const API_KEY = import.meta.env.VITE_API_KEY; + +export class RepositorioActualizarProducto { + /** + * Actualiza un producto y sus variantes en el backend. + * @param {Object} productoRaw - Datos del producto a actualizar. + * @param {Array} variantesRaw - Lista de variantes del producto. + * @param {File|null} imagenProducto - Imagen del producto. + * @param {Object} imagenesVariantes - Mapa de imágenes por variante. + * @returns {Promise} Respuesta del servidor. + */ static async actualizarProducto({ + productoRaw, + variantesRaw, + imagenProducto, + imagenesVariantes, + }) { + const form = new FormData(); + + console.log('Datos del producto recibidos:', productoRaw); + + // Verificar que tengamos un ID de producto válido + if (!productoRaw.idProducto) { + console.error('Error: No se encontró el ID del producto en los datos recibidos'); + throw new Error( + 'El ID del producto es requerido para actualizar. Por favor, asegúrate de seleccionar un producto válido.' + ); + } + + // Asegurarnos de que el ID del producto sea un string + form.append('idProducto', String(productoRaw.idProducto)); // Limpiar el objeto producto antes de enviarlo + const productoLimpio = { + idProducto: productoRaw.idProducto, // Asegurarnos de incluir el idProducto + nombreComun: productoRaw.nombreComun, + nombreComercial: productoRaw.nombreComercial, + descripcion: productoRaw.descripcion, + marca: productoRaw.marca, + modelo: productoRaw.modelo, + tipoProducto: productoRaw.tipoProducto, + precioPuntos: Number(productoRaw.precioPuntos), + precioCliente: Number(productoRaw.precioCliente), + precioVenta: Number(productoRaw.precioVenta), + costo: Number(productoRaw.costo), + impuesto: Number(productoRaw.impuesto), + descuento: Number(productoRaw.descuento), + estado: Number(productoRaw.estado), + envio: Number(productoRaw.envio), + idProveedor: productoRaw.idProveedor, // Agregar también el idProveedor + }; + + // Agregar el producto como JSON string + form.append('producto', JSON.stringify(productoLimpio)); + + // Limpiar y formatear las variantes + const variantesLimpias = variantesRaw.map((variante) => ({ + identificador: String(variante.identificador), + nombreVariante: variante.nombreVariante, + descripcion: variante.descripcion, + opciones: Array.isArray(variante.opciones) + ? variante.opciones.map((opcion) => ({ + valorOpcion: opcion.valorOpcion, + cantidad: Number(opcion.cantidad), + SKUautomatico: opcion.SKUautomatico, + SKUcomercial: opcion.SKUcomercial, + costoAdicional: Number(opcion.costoAdicional || 0), + descuento: Number(opcion.descuento || 0), + estado: Number(opcion.estado || 1), + })) + : [], + })); + + // Agregar las variantes como JSON string + form.append('variantes', JSON.stringify(variantesLimpias)); + + // Agregar imagen del producto si existe + if (imagenProducto instanceof File) { + form.append('imagenProducto', imagenProducto); + } // Agregar imágenes de variantes y construir el mapa + const mapaImagenes = []; + if (imagenesVariantes && typeof imagenesVariantes === 'object') { + for (const [idVariante, imagenesArray] of Object.entries(imagenesVariantes)) { + if (Array.isArray(imagenesArray)) { + for (const img of imagenesArray) { + if (img && img.file instanceof File) { + form.append('imagenesVariante', img.file); + mapaImagenes.push({ + filename: img.file.name, + idVariante: String(idVariante), + }); + } + } + } + } + } + + // Agregar el mapa de imágenes como JSON string + form.append('mapaImagenes', JSON.stringify(mapaImagenes)); // Log para depuración + console.log('Datos del formulario:', Array.from(form.entries())); + try { + // Log de los datos que se están enviando + console.log('Enviando datos al servidor:', { + url: RUTAS_API.PRODUCTOS.ACTUALIZAR_PRODUCTO, + formData: { + idProducto: form.get('idProducto'), + producto: JSON.parse(form.get('producto')), + variantes: JSON.parse(form.get('variantes')), + mapaImagenes: JSON.parse(form.get('mapaImagenes')), + tieneImagenProducto: !!form.get('imagenProducto'), + cantidadImagenesVariante: form.getAll('imagenesVariante').length, + }, + }); // Log de cada campo del FormData individualmente + console.log('=== DATOS ENVIADOS ==='); + for (let [key, value] of form.entries()) { + if (key === 'producto' || key === 'variantes' || key === 'mapaImagenes') { + console.log(`${key}:`, JSON.parse(value)); + } else if (value instanceof File) { + console.log(`${key}: File(${value.name}, ${value.type}, ${value.size} bytes)`); + } else { + console.log(`${key}:`, value); + } + } + + const respuesta = await axios.post(RUTAS_API.PRODUCTOS.ACTUALIZAR_PRODUCTO, form, { + withCredentials: true, + headers: { + 'x-api-key': API_KEY, + 'Content-Type': 'multipart/form-data', + Accept: 'application/json', + }, + transformRequest: [(data) => data], // Prevenir que axios transforme el FormData + }); + + return respuesta.data; + } catch (error) { + // Log detallado del error + console.error('Error detallado:', { + mensaje: error?.response?.data?.mensaje, + status: error?.response?.status, + statusText: error?.response?.statusText, + data: error?.response?.data, + headers: error?.response?.headers, + }); + + // Log de la request que causó el error + console.error('Request data:', { + url: error?.config?.url, + method: error?.config?.method, + headers: error?.config?.headers, + data: error?.config?.data, + }); + + const mensaje = error?.response?.data?.mensaje || 'Error al actualizar el producto'; + throw new Error(mensaje); + } + } +} diff --git a/src/Utilidades/Constantes/rutasAPI.js b/src/Utilidades/Constantes/rutasAPI.js index 9e0caec1..30d5fbb5 100644 --- a/src/Utilidades/Constantes/rutasAPI.js +++ b/src/Utilidades/Constantes/rutasAPI.js @@ -44,7 +44,8 @@ export const RUTAS_API = { ELIMINAR_PRODUCTO: `${BASE_PRODUCTOS}/eliminar`, IMPORTAR: `${BASE_PRODUCTOS}/importar`, LEER_PRODCUTO: `${BASE_PRODUCTOS}/leer-producto`, - EXPORTAR_PRODUCTOS: `${BASE_PRODUCTOS}/exportar-productos` + EXPORTAR_PRODUCTOS: `${BASE_PRODUCTOS}/exportar-productos`, + ACTUALIZAR_PRODUCTO: `${BASE_PRODUCTOS}/actualizar`, }, PROVEEDORES: { BASE: BASE_PROVEEDORES, diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx index 9d6f9d46..f42bd12f 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx @@ -95,7 +95,7 @@ const TituloFormulario = memo(({ titulo, varianteTitulo, tamano = 12 }) => ( )); const CampoImagenProducto = memo( - ({ imagenProducto, setImagenes, refInputArchivo, alAgregarImagenProducto }) => ( + ({ imagenProducto, setImagenes, refInputArchivo, alAgregarImagenProducto, error }) => ( <> {imagenProducto ? ( @@ -104,19 +104,28 @@ const CampoImagenProducto = memo( texto={imagenProducto.name} onEliminar={() => setImagenes((prev) => ({ ...prev, imagenProducto: null }))} tooltipEliminar='Eliminar' - borderColor='primary.light' - backgroundColor='primary.lighter' - iconColor='primary' + borderColor={error ? 'error.main' : 'primary.light'} + backgroundColor={error ? 'error.lighter' : 'primary.lighter'} + iconColor={error ? 'error' : 'primary'} iconSize='large' textoVariant='caption' tabIndex={0} disabled={false} /> + {error && ( + + {error} + + )} ) : ( <> + {' '} Sube la Imagen Principal del Producto Aquí + + Formatos aceptados: JPG, JPEG, PNG - Tamaño máximo: 5MB + { + // Validar antes de pasar al manejador general + const valor = evento.target.value; + + // Si está vacío o es un valor válido entre 0 y 100, actualizar + if (valor === '' || (parseFloat(valor) >= 0 && parseFloat(valor) <= 100)) { + // Formatear para asegurar que no exceda los límites + const valorFormateado = valor === '' ? '' : parseFloat(valor) > 100 ? '100' : valor; + + // Solo llamar al actualizador si el valor es válido + alActualizarProducto({ + target: { + name: 'descuento', + value: valorFormateado, + }, + }); + } + }} placeholder='Ej: 10' tipo='number' required={false} min={0} max={100} onKeyDown={prevenirNumerosNoDecimales} - onInput={(evento) => { - const valor = evento.target.value; - // Limitar a 3 dígitos (máximo 100) - if (valor) { - // Si el valor es mayor que 100, establecerlo a 100 - if (parseFloat(valor) > 100) { - evento.target.value = '100'; - } - // Si tiene más de 5 dígitos en total, truncarlo - const partes = valor.split('.'); - if (partes[0] && partes[0].length > 3) { - partes[0] = partes[0].substring(0, 3); - evento.target.value = partes.join('.'); - } - } - }} /> + />{' '} ); diff --git a/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx index cea7dc48..6f4785f7 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/FormularioActualizarProducto.jsx @@ -134,12 +134,32 @@ const FormularioModal = memo( } }, [formularioAbierto, detalleProducto, inicializarDatosProducto]); + // Efecto para manejar el cierre del modal cuando hay éxito en la actualización + React.useEffect(() => { + if (alerta && alerta.tipo === 'success' && alerta.mensaje?.includes('éxito')) { + // Mostrar el mensaje de éxito durante 1.5 segundos antes de cerrar el modal + const timerCierre = setTimeout(() => { + alCerrarFormularioProducto(); + }, 1500); + + // Limpiar el temporizador si el componente se desmonta o si cambia el estado de alerta + return () => clearTimeout(timerCierre); + } + }, [alerta, alCerrarFormularioProducto]); + + // Función para manejar el cierre manual del modal + const handleCloseModal = React.useCallback(() => { + // Limpiar cualquier alerta pendiente + setAlerta(null); + // Cerrar el modal + alCerrarFormularioProducto(); + }, [setAlerta, alCerrarFormularioProducto]); + return ( <> - {' '} { const cerrarFormularioProducto = useCallback(() => { setMostrarModalProducto(false); recargar(); - }, [recargar]); + }, []); + + const cerrarFormularioActualizarProducto = useCallback(() => { + setMostrarModalActualizarProducto(false); + }, []); const cerrarFormularioProveedor = useCallback(() => { setMostrarModalProveedor(false); @@ -283,7 +287,7 @@ const ListaProductos = () => { {mostrarModalActualizarProducto && ( )} diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index ef815adc..9776b476 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -1,6 +1,7 @@ import { createContext, useContext, useState, useCallback, useMemo, useRef } from 'react'; import { useConsultarProveedores } from '@Hooks/Proveedores/useConsultarProveedores'; import { useCrearProducto } from '@Hooks/Productos/useCrearProducto'; +import { useActualizarProducto } from '@Hooks/Productos/useActualizarProducto'; import { useGenerarSKU } from '@Hooks/Productos/useGenerarSKU'; import { v4 as uuidv4 } from 'uuid'; import { validarProducto } from '@Utilidades/Validaciones/validarProducto'; @@ -77,6 +78,7 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }); const { proveedores } = useConsultarProveedores(); const { guardarProducto } = useCrearProducto(); + const { actualizarProducto } = useActualizarProducto(); // Estados para manejar los errores const [erroresProducto, setErroresProducto] = useState({}); @@ -313,10 +315,10 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const nuevosErrores = { ...prevErrores }; if ( - validacionPartial[idVariante] - && validacionPartial[idVariante].opciones - && validacionPartial[idVariante].opciones[indiceOpcion] - && validacionPartial[idVariante].opciones[indiceOpcion][campo] + validacionPartial[idVariante] && + validacionPartial[idVariante].opciones && + validacionPartial[idVariante].opciones[indiceOpcion] && + validacionPartial[idVariante].opciones[indiceOpcion][campo] ) { // Asegurarse de que existe la estructura para guardar el error if (!nuevosErrores[idVariante]) { @@ -330,12 +332,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = } // Guardar el error de este campo específico - nuevosErrores[idVariante].opciones[indiceOpcion][campo] - = validacionPartial[idVariante].opciones[indiceOpcion][campo]; + nuevosErrores[idVariante].opciones[indiceOpcion][campo] = + validacionPartial[idVariante].opciones[indiceOpcion][campo]; } else if ( - nuevosErrores[idVariante] - && nuevosErrores[idVariante].opciones - && nuevosErrores[idVariante].opciones[indiceOpcion] + nuevosErrores[idVariante] && + nuevosErrores[idVariante].opciones && + nuevosErrores[idVariante].opciones[indiceOpcion] ) { // Eliminar el error si ya no existe delete nuevosErrores[idVariante].opciones[indiceOpcion][campo]; @@ -394,23 +396,22 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }; }); }, []); // Esta función se usa para actualizar y validar un campo individual del producto - const manejarActualizarProducto = useCallback((evento) => { - const { name, value } = evento.target; - console.log(`Actualizando campo ${name} con valor ${value}`); - - // Primero actualizar el estado del producto - setProducto((prev) => { - const nuevoProducto = { ...prev, [name]: value }; - console.log('Estado actualizado del producto:', nuevoProducto); - return nuevoProducto; - }); + const manejarActualizarProducto = useCallback( + (evento) => { + const { name, value } = evento.target; + + // Actualizar el estado del producto sin console.log + setProducto((prev) => { + // No necesitamos duplicar la validación del estado previo + return { ...prev, [name]: value }; + }); - // Luego, en una operación separada, validar y actualizar errores - setTimeout(() => { - // Esta validación se ejecutará después de que el estado se haya actualizado - setProducto((productoActual) => { + // Posponemos la validación para que se ejecute después de que el estado se haya actualizado + // pero evitamos usar setTimeout que puede causar problemas + requestAnimationFrame(() => { // Validar solo el campo que se está actualizando - const validacionPartial = validarProducto(productoActual); + const campoParaValidar = { [name]: value }; + const validacionPartial = validarProducto({ ...producto, ...campoParaValidar }); // Actualizar solo el error del campo específico setErroresProducto((prevErrores) => { @@ -426,62 +427,92 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = return nuevosErrores; }); - - // Devolver el mismo estado, no estamos modificando nada aquí - return productoActual; }); - }, 0); - }, []); + }, + [producto] + ); + // Esta función se usa para guardar el producto actualizado cuando se presiona "Guardar" const manejarGuardarProductoActualizado = useCallback(async () => { try { setCargando(true); + + // Validate that we have a product ID + if (!producto.idProducto) { + setAlerta({ + tipo: 'error', + mensaje: + 'El ID del producto es requerido para actualizar. Por favor, asegúrate de seleccionar un producto válido.', + }); + setCargando(false); + return; + } + + // Validar tamaño de imágenes primero + const tamanioMaximoBytes = 5 * 1024 * 1024; + let erroresImagenes = {}; + + // Validar imagen principal + if (imagenes.imagenProducto && imagenes.imagenProducto.size > tamanioMaximoBytes) { + erroresImagenes.imagenProducto = + 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; + } + + // Validar imágenes de variantes + Object.entries(imagenes.imagenesVariantes).forEach(([idVariante, imagenesVariante]) => { + const imagenesGrandes = imagenesVariante.filter( + (img) => img.file && img.file.size > tamanioMaximoBytes + ); + if (imagenesGrandes.length > 0) { + if (!erroresImagenes.variantes) erroresImagenes.variantes = {}; + erroresImagenes.variantes[ + idVariante + ] = `${imagenesGrandes.length} imagen(es) excede(n) el tamaño máximo de 5MB.`; + } + }); + setAlerta({ tipo: 'info', mensaje: 'Validando datos del producto...', }); - // Convertir variantes de objeto a array para validación - const variantesArray = Object.entries(variantes).map(([idVariante, datos]) => ({ - identificador: idVariante, - ...datos, - })); - // Validar datos del producto const erroresValidacionProducto = validarProducto(producto); - setErroresProducto(erroresValidacionProducto); // Validar datos de las variantes const erroresValidacionVariantes = validarVariantes(variantes); - setErroresVariantes(erroresValidacionVariantes); // Verificar si hay errores - if ( - Object.keys(erroresValidacionProducto).length > 0 - || Object.keys(erroresValidacionVariantes).length > 0 - ) { - // Incrementar contador de intentos enviar para mostrar todos los errores - setIntentosEnviar((prevIntentos) => prevIntentos + 1); - // Crear un mensaje de error más detallado - let mensajeError = 'Por favor revisa los siguientes campos:'; + // Combinar todos los errores + const hayErroresImagenes = Object.keys(erroresImagenes).length > 0; + const hayErroresProducto = Object.keys(erroresValidacionProducto).length > 0; + const hayErroresVariantes = Object.keys(erroresValidacionVariantes).length > 0; + + // Actualizar estados de error + setErroresProducto({ + ...erroresValidacionProducto, + ...(erroresImagenes.imagenProducto && { imagenProducto: erroresImagenes.imagenProducto }), + }); + + setErroresVariantes((prev) => ({ + ...erroresValidacionVariantes, + ...(erroresImagenes.variantes || {}), + })); + + // Si hay errores, mostrar mensaje y detener el proceso + if (hayErroresImagenes || hayErroresProducto || hayErroresVariantes) { + setIntentosEnviar((prev) => prev + 1); - // Agregar errores del producto - if (Object.keys(erroresValidacionProducto).length > 0) { - const camposConErrores = Object.keys(erroresValidacionProducto) - .map((campo) => { - // Convertir camelCase a texto legible - const nombreCampo = campo - .replace(/([A-Z])/g, ' $1') // Insertar un espacio antes de cada letra mayúscula - .toLowerCase() // Convertir todo a minúsculas - .replace(/^./, (str) => str.toUpperCase()); // Capitalizar la primera letra - return nombreCampo; - }) - .join(', '); + let mensajeError = 'Por favor revisa los siguientes campos:'; + if (hayErroresProducto || erroresImagenes.imagenProducto) { + const camposConErrores = [ + ...Object.keys(erroresValidacionProducto), + ...(erroresImagenes.imagenProducto ? ['imagen principal'] : []), + ].join(', '); mensajeError += `\n- Datos del producto: ${camposConErrores}`; } - // Agregar errores de las variantes - if (Object.keys(erroresValidacionVariantes).length > 0) { + if (hayErroresVariantes || erroresImagenes.variantes) { mensajeError += '\n- Hay errores en una o más variantes y sus opciones'; } @@ -493,88 +524,199 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = return; } - // Validar que haya al menos una variante - if (!variantesArray.length) { + // Validar estructura básica del producto + if (!imagenes.imagenProducto) { setAlerta({ tipo: 'error', - mensaje: 'Debes agregar al menos una variante al producto.', + mensaje: 'Debes seleccionar una imagen principal para el producto.', }); setCargando(false); return; } - // Validar que cada variante tenga al menos una opción - const variantesSinOpciones = variantesArray.filter( - (variante) => !Array.isArray(variante.opciones) || variante.opciones.length === 0 - ); + const variantesArray = Object.entries(variantes).map(([idVariante, datos]) => ({ + identificador: idVariante, + ...datos, + })); - if (variantesSinOpciones.length > 0) { + if (!variantesArray.length) { setAlerta({ tipo: 'error', - mensaje: 'Cada variante debe incluir al menos una opción disponible.', + mensaje: 'Debes agregar al menos una variante al producto.', }); setCargando(false); return; } - // Validar imagen del producto - if (!imagenes.imagenProducto) { + const variantesSinOpciones = variantesArray.filter( + (variante) => !Array.isArray(variante.opciones) || variante.opciones.length === 0 + ); + + if (variantesSinOpciones.length > 0) { setAlerta({ tipo: 'error', - mensaje: 'Debes seleccionar una imagen principal para el producto.', + mensaje: 'Cada variante debe incluir al menos una opción disponible.', }); setCargando(false); return; - } - + } // Si llegamos aquí, todo está validado. Proceder con la actualización setAlerta({ tipo: 'info', mensaje: 'Actualizando producto...', }); - console.log('Guardando producto con datos:', { - producto, - variantes: variantesArray, - imagenes, - }); + try { + // Formateamos los datos para la actualización + const datosVariantes = Object.entries(variantes).map(([id, datos]) => ({ + identificador: id, + nombreVariante: datos.nombreVariante, + descripcion: datos.descripcion, + opciones: datos.opciones.map((opcion) => ({ + cantidad: parsearNumero(opcion.cantidad), + valorOpcion: opcion.valorOpcion, + SKUautomatico: opcion.SKUautomatico || '', + SKUcomercial: opcion.SKUcomercial || '', + costoAdicional: parsearNumero(opcion.costoAdicional), + descuento: parsearNumero(opcion.descuento), + estado: Number(opcion.estado) || 1, + })), + })); - // Aquí iría la lógica para enviar los datos actualizados al backend - // Por ahora, simulamos una actualización exitosa - setTimeout(() => { - setAlerta({ - tipo: 'success', - mensaje: 'Producto actualizado con éxito', + const productoFormateado = { + ...producto, + idProducto: producto.idProducto, // Ensure idProducto is included + precioPuntos: parsearNumero(producto.precioPuntos), + precioCliente: parsearNumero(producto.precioCliente), + precioVenta: parsearNumero(producto.precioVenta), + costo: parsearNumero(producto.costo), + impuesto: parsearNumero(producto.impuesto), + descuento: parsearNumero(producto.descuento), + estado: Number(producto.estado) || 1, + envio: parsearNumero(producto.envio), + idProveedor: parsearNumero(producto.idProveedor), + }; + + // Llamada a la API para actualizar el producto + const resultado = await actualizarProducto({ + productoRaw: productoFormateado, + variantesRaw: datosVariantes, + imagenProducto: imagenes.imagenProducto, + imagenesVariantes: imagenes.imagenesVariantes, }); - setCargando(false); + // Si la actualización fue exitosa + if (resultado?.exito) { + setAlerta({ + tipo: 'success', + mensaje: 'Producto actualizado con éxito', + }); + + // Después de un breve retraso, cerrar el formulario + setTimeout(() => { + alCerrarFormularioProducto(); + }, 2000); + } else if (resultado?.mensaje) { + setAlerta({ + tipo: 'error', + mensaje: resultado.mensaje, + }); + } else { + setAlerta({ + tipo: 'success', + mensaje: 'Producto actualizado con éxito', + }); + + // Después de un breve retraso, cerrar el formulario + setTimeout(() => { + alCerrarFormularioProducto(); + }, 2000); + } - // Cerrar el formulario después de unos segundos - setTimeout(() => { - alCerrarFormularioProducto(); - }, 2000); - }, 1000); + // No cerramos el modal aquí, dejamos que el useEffect en FormularioModal se encargue + // después de mostrar el mensaje de éxito por un momento + } catch (error) { + console.error('Error en la comunicación con el servidor:', error); + setAlerta({ + tipo: 'error', + mensaje: 'Error al comunicarse con el servidor', + }); + } } catch (error) { console.error('Error al actualizar producto:', error); setAlerta({ tipo: 'error', - mensaje: - `Ocurrió un error al actualizar el producto: ${error.message || 'Error desconocido'}`, + mensaje: `Ocurrió un error al actualizar el producto: ${ + error.message || 'Error desconocido' + }`, }); + } finally { setCargando(false); } - }, [producto, variantes, imagenes, alCerrarFormularioProducto]); + }, [producto, variantes, imagenes, alCerrarFormularioProducto, actualizarProducto]); const manejarAgregarImagenVariante = useCallback( (idVariante, archivos) => { + // Tamaño máximo permitido: 5MB (5 * 1024 * 1024 bytes) + const tamanioMaximoBytes = 5 * 1024 * 1024; + + // Verificar cada archivo por tamaño + const archivosSobredimensionados = archivos.filter( + (archivo) => archivo.size > tamanioMaximoBytes + ); + + if (archivosSobredimensionados.length > 0) { + const mensajeError = + archivosSobredimensionados.length > 1 + ? `${archivosSobredimensionados.length} imágenes exceden el tamaño máximo de 5MB.` + : 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; + + setAlerta({ + tipo: 'error', + mensaje: mensajeError, + }); + + // Añadir error a la variante específica + setErroresVariantes((prevErrores) => { + const nuevosErrores = { ...prevErrores }; + if (!nuevosErrores[idVariante]) { + nuevosErrores[idVariante] = {}; + } + nuevosErrores[idVariante].imagenes = mensajeError; + return nuevosErrores; + }); + + // Si hay archivos válidos, continuamos con ellos + if (archivosSobredimensionados.length === archivos.length) { + return; // Si todos los archivos exceden el tamaño, no hacemos nada + } + } else { + // Limpiar errores de imágenes para esta variante si todos los archivos son válidos + setErroresVariantes((prevErrores) => { + const nuevosErrores = { ...prevErrores }; + if (nuevosErrores[idVariante]?.imagenes) { + delete nuevosErrores[idVariante].imagenes; + if (Object.keys(nuevosErrores[idVariante]).length === 0) { + delete nuevosErrores[idVariante]; + } + } + return nuevosErrores; + }); + } + + // Filtrar solo los archivos que no exceden el tamaño + const archivosValidos = archivos.filter((archivo) => archivo.size <= tamanioMaximoBytes); + + if (archivosValidos.length === 0) return; + setImagenes((prev) => { const imagenesVariante = prev.imagenesVariantes[idVariante] || []; - const nuevasImagenes = archivos.map((archivo) => ({ + const nuevasImagenes = archivosValidos.map((archivo) => ({ id: `${archivo.name}_${uuidv4()}_${idVariante}`, idVariante, file: archivo, })); - setSiguienteIdImagen(siguienteIdImagen + archivos.length); + setSiguienteIdImagen(siguienteIdImagen + archivosValidos.length); return { ...prev, @@ -588,7 +730,9 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = setAlerta({ tipo: 'success', mensaje: `${ - archivos.length > 1 ? `${archivos.length} imágenes agregadas` : 'Imagen agregada' + archivosValidos.length > 1 + ? `${archivosValidos.length} imágenes agregadas` + : 'Imagen agregada' } a la variante`, }); }, @@ -725,11 +869,38 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = setCargando(false); } }, [guardarProducto, imagenes, producto, variantes, alCerrarFormularioProducto]); - const manejarAgregarImagenProducto = useCallback((evento) => { const archivo = evento.target.files[0]; if (!archivo) return; + // Tamaño máximo permitido: 5MB (5 * 1024 * 1024 bytes) + const tamanioMaximoBytes = 5 * 1024 * 1024; + + if (archivo.size > tamanioMaximoBytes) { + // Mostrar alerta de error y no actualizar la imagen + setAlerta({ + tipo: 'error', + mensaje: 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.', + }); + + // Añadir error específico para la imagen + setErroresProducto((prevErrores) => ({ + ...prevErrores, + imagenProducto: 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.', + })); + + // No actualizar la imagen si excede el tamaño + evento.target.value = ''; + return; + } + + // Limpiar cualquier error de imagen anterior + setErroresProducto((prevErrores) => { + const nuevosErrores = { ...prevErrores }; + delete nuevosErrores.imagenProducto; + return nuevosErrores; + }); + setImagenes((prev) => ({ ...prev, imagenProducto: archivo })); setAlerta({ @@ -771,6 +942,7 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = // Inicializar datos básicos del producto setProducto({ + idProducto: detalleProducto.idProducto, // Ensure idProducto is included nombreComun: detalleProducto.nombreComun || '', nombreComercial: detalleProducto.nombreComercial || '', descripcion: detalleProducto.descripcion || '', diff --git a/src/hooks/Productos/useActualizarProducto.js b/src/hooks/Productos/useActualizarProducto.js new file mode 100644 index 00000000..78fcda1b --- /dev/null +++ b/src/hooks/Productos/useActualizarProducto.js @@ -0,0 +1,57 @@ +//RF [29] Actualiza Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF29] +import { useState } from 'react'; +import axios from 'axios'; +import { RepositorioActualizarProducto } from '@Repositorios/Productos/RepositorioActualizarProducto'; +import { validarProducto } from '@Utilidades/Validaciones/validarProducto'; +import { validarVariantes } from '@Utilidades/Validaciones/validarVariantes'; +const MAX_IMAGE_SIZE = 5 * 1024 * 1024; + +export const useActualizarProducto = () => { + const [cargando, setCargando] = useState(false); + const [error, setError] = useState(null); + + const actualizarProducto = async (datosProducto) => { + setCargando(true); + setError(null); + + try { + // Validar producto + const erroresProducto = validarProducto(datosProducto.productoRaw); + if (Object.keys(erroresProducto).length > 0) { + setError(erroresProducto); + setCargando(false); + return { exito: false, mensaje: 'Hay errores en los datos del producto' }; + } + + // Validar variantes - necesitamos formatear los datos primero + const variantesConIdentificador = {}; + datosProducto.variantesRaw.forEach((variante) => { + variantesConIdentificador[variante.identificador] = { + ...variante, + identificador: variante.identificador, + }; + }); + + const erroresVariantes = validarVariantes(variantesConIdentificador); + if (Object.keys(erroresVariantes).length > 0) { + setError(erroresVariantes); + setCargando(false); + return { exito: false, mensaje: 'Hay errores en los datos de las variantes' }; + } + + // Llamar al repositorio para actualizar el producto + const respuesta = await RepositorioActualizarProducto.actualizarProducto(datosProducto); + setCargando(false); + return { ...respuesta, exito: true }; + } catch (error) { + setError(error.message); + setCargando(false); + return { exito: false, mensaje: error.message || 'Error al actualizar el producto' }; + } + }; + return { + cargando, + error, + actualizarProducto, + }; +}; From 65d2292df4b53b15369610fa30d97279f6c485e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 12:45:16 -0600 Subject: [PATCH 09/21] refactor: Quitar consoles log --- .../RepositorioActualizarProducto.js | 178 +++++++----------- src/hooks/Productos/ProductoFormProvider.jsx | 8 - 2 files changed, 68 insertions(+), 118 deletions(-) diff --git a/src/Dominio/Repositorios/Productos/RepositorioActualizarProducto.js b/src/Dominio/Repositorios/Productos/RepositorioActualizarProducto.js index 2ffded0a..e0a5f273 100644 --- a/src/Dominio/Repositorios/Productos/RepositorioActualizarProducto.js +++ b/src/Dominio/Repositorios/Productos/RepositorioActualizarProducto.js @@ -19,110 +19,85 @@ export class RepositorioActualizarProducto { imagenProducto, imagenesVariantes, }) { - const form = new FormData(); - - console.log('Datos del producto recibidos:', productoRaw); + try { + const form = new FormData(); - // Verificar que tengamos un ID de producto válido - if (!productoRaw.idProducto) { - console.error('Error: No se encontró el ID del producto en los datos recibidos'); - throw new Error( - 'El ID del producto es requerido para actualizar. Por favor, asegúrate de seleccionar un producto válido.' - ); - } + // Verificar que tengamos un ID de producto válido + if (!productoRaw.idProducto) { + console.error('Error: No se encontró el ID del producto en los datos recibidos'); + throw new Error( + 'El ID del producto es requerido para actualizar. Por favor, asegúrate de seleccionar un producto válido.' + ); + } - // Asegurarnos de que el ID del producto sea un string - form.append('idProducto', String(productoRaw.idProducto)); // Limpiar el objeto producto antes de enviarlo - const productoLimpio = { - idProducto: productoRaw.idProducto, // Asegurarnos de incluir el idProducto - nombreComun: productoRaw.nombreComun, - nombreComercial: productoRaw.nombreComercial, - descripcion: productoRaw.descripcion, - marca: productoRaw.marca, - modelo: productoRaw.modelo, - tipoProducto: productoRaw.tipoProducto, - precioPuntos: Number(productoRaw.precioPuntos), - precioCliente: Number(productoRaw.precioCliente), - precioVenta: Number(productoRaw.precioVenta), - costo: Number(productoRaw.costo), - impuesto: Number(productoRaw.impuesto), - descuento: Number(productoRaw.descuento), - estado: Number(productoRaw.estado), - envio: Number(productoRaw.envio), - idProveedor: productoRaw.idProveedor, // Agregar también el idProveedor - }; + // Asegurarnos de que el ID del producto sea un string + form.append('idProducto', String(productoRaw.idProducto)); // Limpiar el objeto producto antes de enviarlo + const productoLimpio = { + idProducto: productoRaw.idProducto, // Asegurarnos de incluir el idProducto + nombreComun: productoRaw.nombreComun, + nombreComercial: productoRaw.nombreComercial, + descripcion: productoRaw.descripcion, + marca: productoRaw.marca, + modelo: productoRaw.modelo, + tipoProducto: productoRaw.tipoProducto, + precioPuntos: Number(productoRaw.precioPuntos), + precioCliente: Number(productoRaw.precioCliente), + precioVenta: Number(productoRaw.precioVenta), + costo: Number(productoRaw.costo), + impuesto: Number(productoRaw.impuesto), + descuento: Number(productoRaw.descuento), + estado: Number(productoRaw.estado), + envio: Number(productoRaw.envio), + idProveedor: productoRaw.idProveedor, // Agregar también el idProveedor + }; - // Agregar el producto como JSON string - form.append('producto', JSON.stringify(productoLimpio)); + // Agregar el producto como JSON string + form.append('producto', JSON.stringify(productoLimpio)); - // Limpiar y formatear las variantes - const variantesLimpias = variantesRaw.map((variante) => ({ - identificador: String(variante.identificador), - nombreVariante: variante.nombreVariante, - descripcion: variante.descripcion, - opciones: Array.isArray(variante.opciones) - ? variante.opciones.map((opcion) => ({ - valorOpcion: opcion.valorOpcion, - cantidad: Number(opcion.cantidad), - SKUautomatico: opcion.SKUautomatico, - SKUcomercial: opcion.SKUcomercial, - costoAdicional: Number(opcion.costoAdicional || 0), - descuento: Number(opcion.descuento || 0), - estado: Number(opcion.estado || 1), - })) - : [], - })); + // Limpiar y formatear las variantes + const variantesLimpias = variantesRaw.map((variante) => ({ + identificador: String(variante.identificador), + nombreVariante: variante.nombreVariante, + descripcion: variante.descripcion, + opciones: Array.isArray(variante.opciones) + ? variante.opciones.map((opcion) => ({ + valorOpcion: opcion.valorOpcion, + cantidad: Number(opcion.cantidad), + SKUautomatico: opcion.SKUautomatico, + SKUcomercial: opcion.SKUcomercial, + costoAdicional: Number(opcion.costoAdicional || 0), + descuento: Number(opcion.descuento || 0), + estado: Number(opcion.estado || 1), + })) + : [], + })); - // Agregar las variantes como JSON string - form.append('variantes', JSON.stringify(variantesLimpias)); + // Agregar las variantes como JSON string + form.append('variantes', JSON.stringify(variantesLimpias)); - // Agregar imagen del producto si existe - if (imagenProducto instanceof File) { - form.append('imagenProducto', imagenProducto); - } // Agregar imágenes de variantes y construir el mapa - const mapaImagenes = []; - if (imagenesVariantes && typeof imagenesVariantes === 'object') { - for (const [idVariante, imagenesArray] of Object.entries(imagenesVariantes)) { - if (Array.isArray(imagenesArray)) { - for (const img of imagenesArray) { - if (img && img.file instanceof File) { - form.append('imagenesVariante', img.file); - mapaImagenes.push({ - filename: img.file.name, - idVariante: String(idVariante), - }); + // Agregar imagen del producto si existe + if (imagenProducto instanceof File) { + form.append('imagenProducto', imagenProducto); + } // Agregar imágenes de variantes y construir el mapa + const mapaImagenes = []; + if (imagenesVariantes && typeof imagenesVariantes === 'object') { + for (const [idVariante, imagenesArray] of Object.entries(imagenesVariantes)) { + if (Array.isArray(imagenesArray)) { + for (const img of imagenesArray) { + if (img && img.file instanceof File) { + form.append('imagenesVariante', img.file); + mapaImagenes.push({ + filename: img.file.name, + idVariante: String(idVariante), + }); + } } } } } - } - // Agregar el mapa de imágenes como JSON string - form.append('mapaImagenes', JSON.stringify(mapaImagenes)); // Log para depuración - console.log('Datos del formulario:', Array.from(form.entries())); - try { - // Log de los datos que se están enviando - console.log('Enviando datos al servidor:', { - url: RUTAS_API.PRODUCTOS.ACTUALIZAR_PRODUCTO, - formData: { - idProducto: form.get('idProducto'), - producto: JSON.parse(form.get('producto')), - variantes: JSON.parse(form.get('variantes')), - mapaImagenes: JSON.parse(form.get('mapaImagenes')), - tieneImagenProducto: !!form.get('imagenProducto'), - cantidadImagenesVariante: form.getAll('imagenesVariante').length, - }, - }); // Log de cada campo del FormData individualmente - console.log('=== DATOS ENVIADOS ==='); - for (let [key, value] of form.entries()) { - if (key === 'producto' || key === 'variantes' || key === 'mapaImagenes') { - console.log(`${key}:`, JSON.parse(value)); - } else if (value instanceof File) { - console.log(`${key}: File(${value.name}, ${value.type}, ${value.size} bytes)`); - } else { - console.log(`${key}:`, value); - } - } + // Agregar el mapa de imágenes como JSON string + form.append('mapaImagenes', JSON.stringify(mapaImagenes)); // Log para depuración const respuesta = await axios.post(RUTAS_API.PRODUCTOS.ACTUALIZAR_PRODUCTO, form, { withCredentials: true, @@ -136,23 +111,6 @@ export class RepositorioActualizarProducto { return respuesta.data; } catch (error) { - // Log detallado del error - console.error('Error detallado:', { - mensaje: error?.response?.data?.mensaje, - status: error?.response?.status, - statusText: error?.response?.statusText, - data: error?.response?.data, - headers: error?.response?.headers, - }); - - // Log de la request que causó el error - console.error('Request data:', { - url: error?.config?.url, - method: error?.config?.method, - headers: error?.config?.headers, - data: error?.config?.data, - }); - const mensaje = error?.response?.data?.mensaje || 'Error al actualizar el producto'; throw new Error(mensaje); } diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index 9776b476..5ba74df6 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -400,14 +400,10 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = (evento) => { const { name, value } = evento.target; - // Actualizar el estado del producto sin console.log setProducto((prev) => { - // No necesitamos duplicar la validación del estado previo return { ...prev, [name]: value }; }); - // Posponemos la validación para que se ejecute después de que el estado se haya actualizado - // pero evitamos usar setTimeout que puede causar problemas requestAnimationFrame(() => { // Validar solo el campo que se está actualizando const campoParaValidar = { [name]: value }; @@ -432,12 +428,9 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = [producto] ); - // Esta función se usa para guardar el producto actualizado cuando se presiona "Guardar" const manejarGuardarProductoActualizado = useCallback(async () => { try { setCargando(true); - - // Validate that we have a product ID if (!producto.idProducto) { setAlerta({ tipo: 'error', @@ -448,7 +441,6 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = return; } - // Validar tamaño de imágenes primero const tamanioMaximoBytes = 5 * 1024 * 1024; let erroresImagenes = {}; From ea99686a3e24ff44dc224e794d5fc58ba90eb1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 12:48:15 -0600 Subject: [PATCH 10/21] =?UTF-8?q?refactor:=20Mejorar=20la=20legibilidad=20?= =?UTF-8?q?del=20c=C3=B3digo=20al=20ajustar=20el=20formato=20de=20las=20de?= =?UTF-8?q?claraciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Paginas/Productos/ListaProductos.jsx | 4 +-- src/hooks/Productos/ProductoFormProvider.jsx | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index 8fdbdceb..a7463511 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -44,8 +44,8 @@ const ListaProductos = () => { const [imagenProducto, setImagenProducto] = useState(''); const [mostrarModalActualizarProducto, setMostrarModalActualizarProducto] = useState(false); const [openModalExportar, setAbrirPopUpExportar] = useState(false); - const MENSAJE_POPUP_EXPORTAR = - '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; + const MENSAJE_POPUP_EXPORTAR + = '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; const manejarCancelarExportar = () => { setAbrirPopUpExportar(false); }; diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index 5ba74df6..4ba4b933 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -315,10 +315,10 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const nuevosErrores = { ...prevErrores }; if ( - validacionPartial[idVariante] && - validacionPartial[idVariante].opciones && - validacionPartial[idVariante].opciones[indiceOpcion] && - validacionPartial[idVariante].opciones[indiceOpcion][campo] + validacionPartial[idVariante] + && validacionPartial[idVariante].opciones + && validacionPartial[idVariante].opciones[indiceOpcion] + && validacionPartial[idVariante].opciones[indiceOpcion][campo] ) { // Asegurarse de que existe la estructura para guardar el error if (!nuevosErrores[idVariante]) { @@ -332,12 +332,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = } // Guardar el error de este campo específico - nuevosErrores[idVariante].opciones[indiceOpcion][campo] = - validacionPartial[idVariante].opciones[indiceOpcion][campo]; + nuevosErrores[idVariante].opciones[indiceOpcion][campo] + = validacionPartial[idVariante].opciones[indiceOpcion][campo]; } else if ( - nuevosErrores[idVariante] && - nuevosErrores[idVariante].opciones && - nuevosErrores[idVariante].opciones[indiceOpcion] + nuevosErrores[idVariante] + && nuevosErrores[idVariante].opciones + && nuevosErrores[idVariante].opciones[indiceOpcion] ) { // Eliminar el error si ya no existe delete nuevosErrores[idVariante].opciones[indiceOpcion][campo]; @@ -442,12 +442,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = } const tamanioMaximoBytes = 5 * 1024 * 1024; - let erroresImagenes = {}; + const erroresImagenes = {}; // Validar imagen principal if (imagenes.imagenProducto && imagenes.imagenProducto.size > tamanioMaximoBytes) { - erroresImagenes.imagenProducto = - 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; + erroresImagenes.imagenProducto + = 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; } // Validar imágenes de variantes @@ -657,8 +657,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = ); if (archivosSobredimensionados.length > 0) { - const mensajeError = - archivosSobredimensionados.length > 1 + const mensajeError + = archivosSobredimensionados.length > 1 ? `${archivosSobredimensionados.length} imágenes exceden el tamaño máximo de 5MB.` : 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; From 55a849e151c5052ef3acb7742895b052ab3ec0f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 13:03:33 -0600 Subject: [PATCH 11/21] =?UTF-8?q?refactor:=20Ajustar=20formato=20de=20c?= =?UTF-8?q?=C3=B3digo=20para=20mejorar=20la=20legibilidad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Vistas/Paginas/Productos/ListaProductos.jsx | 8 ++++---- src/hooks/Empleados/useConsultarEmpleados.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index a7463511..c7506b72 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -44,8 +44,8 @@ const ListaProductos = () => { const [imagenProducto, setImagenProducto] = useState(''); const [mostrarModalActualizarProducto, setMostrarModalActualizarProducto] = useState(false); const [openModalExportar, setAbrirPopUpExportar] = useState(false); - const MENSAJE_POPUP_EXPORTAR - = '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; + const MENSAJE_POPUP_EXPORTAR = + '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; const manejarCancelarExportar = () => { setAbrirPopUpExportar(false); }; @@ -123,10 +123,10 @@ const ListaProductos = () => { setMostrarModalProducto(false); recargar(); }, []); - const cerrarFormularioActualizarProducto = useCallback(() => { setMostrarModalActualizarProducto(false); - }, []); + recargar(); + }, [recargar]); const cerrarFormularioProveedor = useCallback(() => { setMostrarModalProveedor(false); diff --git a/src/hooks/Empleados/useConsultarEmpleados.js b/src/hooks/Empleados/useConsultarEmpleados.js index 2b95ee3b..f1786b63 100644 --- a/src/hooks/Empleados/useConsultarEmpleados.js +++ b/src/hooks/Empleados/useConsultarEmpleados.js @@ -50,8 +50,8 @@ export function useConsultarEmpleados() { }, [usuario, recargarToken]); const recargar = () => { - setRecargarToken(prev => prev + 1); + setRecargarToken((prev) => prev + 1); }; return { empleados, mensaje, cargando, error, recargar }; -} \ No newline at end of file +} From 1eb28b9c2a65e1675f16ceb063008b2fbcc75ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 13:08:05 -0600 Subject: [PATCH 12/21] =?UTF-8?q?refactor:=20Eliminar=20importaci=C3=B3n?= =?UTF-8?q?=20innecesaria=20de=20axios=20en=20useActualizarProducto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/Productos/useActualizarProducto.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/Productos/useActualizarProducto.js b/src/hooks/Productos/useActualizarProducto.js index 78fcda1b..d93f335c 100644 --- a/src/hooks/Productos/useActualizarProducto.js +++ b/src/hooks/Productos/useActualizarProducto.js @@ -1,6 +1,5 @@ //RF [29] Actualiza Producto - [https://codeandco-wiki.netlify.app/docs/proyectos/textiles/documentacion/requisitos/RF29] import { useState } from 'react'; -import axios from 'axios'; import { RepositorioActualizarProducto } from '@Repositorios/Productos/RepositorioActualizarProducto'; import { validarProducto } from '@Utilidades/Validaciones/validarProducto'; import { validarVariantes } from '@Utilidades/Validaciones/validarVariantes'; From b00a73692e929b9a7af9a1b3304a02ccf242ae14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 13:09:31 -0600 Subject: [PATCH 13/21] =?UTF-8?q?refactor:=20Mejorar=20la=20legibilidad=20?= =?UTF-8?q?del=20c=C3=B3digo=20al=20ajustar=20el=20formato=20de=20las=20de?= =?UTF-8?q?claraciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Paginas/Productos/ListaProductos.jsx | 4 +-- src/hooks/Productos/ProductoFormProvider.jsx | 26 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index c7506b72..7350c120 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -44,8 +44,8 @@ const ListaProductos = () => { const [imagenProducto, setImagenProducto] = useState(''); const [mostrarModalActualizarProducto, setMostrarModalActualizarProducto] = useState(false); const [openModalExportar, setAbrirPopUpExportar] = useState(false); - const MENSAJE_POPUP_EXPORTAR = - '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; + const MENSAJE_POPUP_EXPORTAR + = '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; const manejarCancelarExportar = () => { setAbrirPopUpExportar(false); }; diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index 4ba4b933..b2b51d77 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -315,10 +315,10 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const nuevosErrores = { ...prevErrores }; if ( - validacionPartial[idVariante] - && validacionPartial[idVariante].opciones - && validacionPartial[idVariante].opciones[indiceOpcion] - && validacionPartial[idVariante].opciones[indiceOpcion][campo] + validacionPartial[idVariante] && + validacionPartial[idVariante].opciones && + validacionPartial[idVariante].opciones[indiceOpcion] && + validacionPartial[idVariante].opciones[indiceOpcion][campo] ) { // Asegurarse de que existe la estructura para guardar el error if (!nuevosErrores[idVariante]) { @@ -332,12 +332,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = } // Guardar el error de este campo específico - nuevosErrores[idVariante].opciones[indiceOpcion][campo] - = validacionPartial[idVariante].opciones[indiceOpcion][campo]; + nuevosErrores[idVariante].opciones[indiceOpcion][campo] = + validacionPartial[idVariante].opciones[indiceOpcion][campo]; } else if ( - nuevosErrores[idVariante] - && nuevosErrores[idVariante].opciones - && nuevosErrores[idVariante].opciones[indiceOpcion] + nuevosErrores[idVariante] && + nuevosErrores[idVariante].opciones && + nuevosErrores[idVariante].opciones[indiceOpcion] ) { // Eliminar el error si ya no existe delete nuevosErrores[idVariante].opciones[indiceOpcion][campo]; @@ -446,8 +446,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = // Validar imagen principal if (imagenes.imagenProducto && imagenes.imagenProducto.size > tamanioMaximoBytes) { - erroresImagenes.imagenProducto - = 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; + erroresImagenes.imagenProducto = + 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; } // Validar imágenes de variantes @@ -657,8 +657,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = ); if (archivosSobredimensionados.length > 0) { - const mensajeError - = archivosSobredimensionados.length > 1 + const mensajeError = + archivosSobredimensionados.length > 1 ? `${archivosSobredimensionados.length} imágenes exceden el tamaño máximo de 5MB.` : 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; From f2a6992660266e5255518dc18aaa486114e6572e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 13:25:51 -0600 Subject: [PATCH 14/21] =?UTF-8?q?refactor:=20Simplificar=20la=20l=C3=B3gic?= =?UTF-8?q?a=20de=20eliminaci=C3=B3n=20de=20opciones=20y=20limpiar=20error?= =?UTF-8?q?es=20asociados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/Productos/ProductoFormProvider.jsx | 41 +++++++++++++++----- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index b2b51d77..67d838fd 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -364,23 +364,21 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }, [generarSKUAutomatico, producto.nombreComun] ); - const manejarEliminarOpcion = useCallback((idVariante, indiceOpcion) => { - setVariantes((prev) => { - const varianteActual = prev[idVariante]; + setVariantes((variantesActuales) => { + const varianteActual = variantesActuales[idVariante]; // prettier-ignore if ( !varianteActual || !varianteActual.opciones || indiceOpcion >= varianteActual.opciones.length ) { - return prev; + return variantesActuales; } - const opcionesActualizadas = [ - ...varianteActual.opciones.slice(0, indiceOpcion), - ...varianteActual.opciones.slice(indiceOpcion + 1), - ]; + const opcionesActualizadas = varianteActual.opciones.filter( + (_, index) => index !== indiceOpcion + ); setAlerta({ tipo: 'success', @@ -388,13 +386,38 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }); return { - ...prev, + ...variantesActuales, [idVariante]: { ...varianteActual, opciones: opcionesActualizadas, }, }; }); + + // Also clear any errors associated with the removed option + setErroresVariantes((erroresPrevios) => { + if (!erroresPrevios[idVariante]?.opciones?.[indiceOpcion]) { + return erroresPrevios; + } + + const nuevosErrores = { ...erroresPrevios }; + + if (nuevosErrores[idVariante]?.opciones) { + const opcionesActualizadas = { ...nuevosErrores[idVariante].opciones }; + delete opcionesActualizadas[indiceOpcion]; + + if (Object.keys(opcionesActualizadas).length === 0) { + delete nuevosErrores[idVariante].opciones; + if (Object.keys(nuevosErrores[idVariante]).length === 0) { + delete nuevosErrores[idVariante]; + } + } else { + nuevosErrores[idVariante].opciones = opcionesActualizadas; + } + } + + return nuevosErrores; + }); }, []); // Esta función se usa para actualizar y validar un campo individual del producto const manejarActualizarProducto = useCallback( (evento) => { From 5b5d67c7ab7909104fdcdd7a4a7f933dee1ce34f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 13:46:56 -0600 Subject: [PATCH 15/21] =?UTF-8?q?refactor:=20Optimizar=20la=20gesti=C3=B3n?= =?UTF-8?q?=20de=20im=C3=A1genes=20variantes=20al=20asegurar=20la=20inicia?= =?UTF-8?q?lizaci=C3=B3n=20adecuada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/Productos/ProductoFormProvider.jsx | 25 ++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index 67d838fd..8ebbc70c 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -105,14 +105,17 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = setIdsVariantes((prev) => [...prev, nuevoId].sort((id1, id2) => id1 - id2)); - setImagenes((prev) => ({ - ...prev, - imagenesVariantes: { - ...prev.imagenesVariantes, - [nuevoId]: [], - }, - })); + setImagenes((prev) => { + const imagenesActualizadas = { ...prev }; + idsVariantes.forEach((id) => { + if (!imagenesActualizadas.imagenesVariantes[id]) { + imagenesActualizadas.imagenesVariantes[id] = []; + } + }); + return imagenesActualizadas; + }); }, [idsVariantes]); + const manejarActualizarVariante = useCallback((idVariante, campo, valor) => { // Primero actualizar el estado de las variantes setVariantes((prev) => { @@ -375,9 +378,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = ) { return variantesActuales; } - const opcionesActualizadas = varianteActual.opciones.filter( - (_, index) => index !== indiceOpcion + (opcion, index) => index !== indiceOpcion ); setAlerta({ @@ -932,10 +934,9 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = })) || [], [proveedores] ); - useMemo(() => { - setImagenes((prev) => { - const imagenesActualizadas = { ...prev }; + setImagenes((imagenesActuales) => { + const imagenesActualizadas = { ...imagenesActuales }; idsVariantes.forEach((id) => { if (!imagenesActualizadas.imagenesVariantes[id]) { From dd063f80e36f9b169d7cab750ab347d4a8602d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 13:48:11 -0600 Subject: [PATCH 16/21] =?UTF-8?q?refactor:=20Mejorar=20la=20legibilidad=20?= =?UTF-8?q?del=20c=C3=B3digo=20al=20ajustar=20el=20formato=20de=20las=20co?= =?UTF-8?q?ndiciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/Productos/ProductoFormProvider.jsx | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index 8ebbc70c..1cd4305b 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -318,10 +318,10 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const nuevosErrores = { ...prevErrores }; if ( - validacionPartial[idVariante] && - validacionPartial[idVariante].opciones && - validacionPartial[idVariante].opciones[indiceOpcion] && - validacionPartial[idVariante].opciones[indiceOpcion][campo] + validacionPartial[idVariante] + && validacionPartial[idVariante].opciones + && validacionPartial[idVariante].opciones[indiceOpcion] + && validacionPartial[idVariante].opciones[indiceOpcion][campo] ) { // Asegurarse de que existe la estructura para guardar el error if (!nuevosErrores[idVariante]) { @@ -335,12 +335,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = } // Guardar el error de este campo específico - nuevosErrores[idVariante].opciones[indiceOpcion][campo] = - validacionPartial[idVariante].opciones[indiceOpcion][campo]; + nuevosErrores[idVariante].opciones[indiceOpcion][campo] + = validacionPartial[idVariante].opciones[indiceOpcion][campo]; } else if ( - nuevosErrores[idVariante] && - nuevosErrores[idVariante].opciones && - nuevosErrores[idVariante].opciones[indiceOpcion] + nuevosErrores[idVariante] + && nuevosErrores[idVariante].opciones + && nuevosErrores[idVariante].opciones[indiceOpcion] ) { // Eliminar el error si ya no existe delete nuevosErrores[idVariante].opciones[indiceOpcion][campo]; @@ -471,8 +471,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = // Validar imagen principal if (imagenes.imagenProducto && imagenes.imagenProducto.size > tamanioMaximoBytes) { - erroresImagenes.imagenProducto = - 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; + erroresImagenes.imagenProducto + = 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; } // Validar imágenes de variantes @@ -682,8 +682,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = ); if (archivosSobredimensionados.length > 0) { - const mensajeError = - archivosSobredimensionados.length > 1 + const mensajeError + = archivosSobredimensionados.length > 1 ? `${archivosSobredimensionados.length} imágenes exceden el tamaño máximo de 5MB.` : 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; From b06e156131b41a3ceee2be416e37f923dbecc448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 18:15:27 -0600 Subject: [PATCH 17/21] =?UTF-8?q?refactor:=20Simplificar=20la=20actualizac?= =?UTF-8?q?i=C3=B3n=20de=20errores=20en=20el=20formulario=20de=20producto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Vistas/Paginas/Productos/ListaProductos.jsx | 2 +- src/hooks/Productos/ProductoFormProvider.jsx | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index 7350c120..b2a35ead 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -122,7 +122,7 @@ const ListaProductos = () => { const cerrarFormularioProducto = useCallback(() => { setMostrarModalProducto(false); recargar(); - }, []); + }, [recargar]); const cerrarFormularioActualizarProducto = useCallback(() => { setMostrarModalActualizarProducto(false); recargar(); diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index 1cd4305b..b7193217 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -435,8 +435,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const validacionPartial = validarProducto({ ...producto, ...campoParaValidar }); // Actualizar solo el error del campo específico - setErroresProducto((prevErrores) => { - const nuevosErrores = { ...prevErrores }; + setErroresProducto((erroresActuales) => { + const nuevosErrores = { ...erroresActuales }; // Si hay un error para este campo, actualizarlo if (validacionPartial[name]) { @@ -510,7 +510,7 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = ...(erroresImagenes.imagenProducto && { imagenProducto: erroresImagenes.imagenProducto }), }); - setErroresVariantes((prev) => ({ + setErroresVariantes(() => ({ ...erroresValidacionVariantes, ...(erroresImagenes.variantes || {}), })); @@ -901,8 +901,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }); // Añadir error específico para la imagen - setErroresProducto((prevErrores) => ({ - ...prevErrores, + setErroresProducto((erroresActuales) => ({ + ...erroresActuales, imagenProducto: 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.', })); @@ -912,8 +912,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = } // Limpiar cualquier error de imagen anterior - setErroresProducto((prevErrores) => { - const nuevosErrores = { ...prevErrores }; + setErroresProducto((erroresActuales) => { + const nuevosErrores = { ...erroresActuales }; delete nuevosErrores.imagenProducto; return nuevosErrores; }); From f728ff4f9acfa90fc32ca3541e2e65d5ea031499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Tue, 10 Jun 2025 18:50:50 -0600 Subject: [PATCH 18/21] =?UTF-8?q?refactor:=20Ajustar=20la=20asignaci=C3=B3?= =?UTF-8?q?n=20de=20MENSAJE=5FPOPUP=5FEXPORTAR=20y=20mejorar=20la=20legibi?= =?UTF-8?q?lidad=20del=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Vistas/Paginas/Productos/ListaProductos.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index b2a35ead..c16b6057 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -44,8 +44,8 @@ const ListaProductos = () => { const [imagenProducto, setImagenProducto] = useState(''); const [mostrarModalActualizarProducto, setMostrarModalActualizarProducto] = useState(false); const [openModalExportar, setAbrirPopUpExportar] = useState(false); - const MENSAJE_POPUP_EXPORTAR - = '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; + const MENSAJE_POPUP_EXPORTAR = + '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; const manejarCancelarExportar = () => { setAbrirPopUpExportar(false); }; @@ -109,9 +109,9 @@ const ListaProductos = () => { setMostrarModalProducto(true); setMostrarModalProveedor(false); }, []); - const mostrarFormularioActualizarProducto = useCallback(() => { setMostrarModalActualizarProducto(true); + setAbrirModalDetalle(false); }, []); const mostrarFormularioProveedor = useCallback(() => { From 554ff84c66de759509d57e7cb30896e2c547bae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Thu, 12 Jun 2025 15:26:11 -0600 Subject: [PATCH 19/21] =?UTF-8?q?refactor:=20Deja=20actualizar=20producto?= =?UTF-8?q?=20sin=20tener=20que=20reesubir=20las=20im=C3=A1genes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/Productos/ProductoFormProvider.jsx | 39 ++++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index b7193217..c373b35b 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -318,10 +318,10 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const nuevosErrores = { ...prevErrores }; if ( - validacionPartial[idVariante] - && validacionPartial[idVariante].opciones - && validacionPartial[idVariante].opciones[indiceOpcion] - && validacionPartial[idVariante].opciones[indiceOpcion][campo] + validacionPartial[idVariante] && + validacionPartial[idVariante].opciones && + validacionPartial[idVariante].opciones[indiceOpcion] && + validacionPartial[idVariante].opciones[indiceOpcion][campo] ) { // Asegurarse de que existe la estructura para guardar el error if (!nuevosErrores[idVariante]) { @@ -335,12 +335,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = } // Guardar el error de este campo específico - nuevosErrores[idVariante].opciones[indiceOpcion][campo] - = validacionPartial[idVariante].opciones[indiceOpcion][campo]; + nuevosErrores[idVariante].opciones[indiceOpcion][campo] = + validacionPartial[idVariante].opciones[indiceOpcion][campo]; } else if ( - nuevosErrores[idVariante] - && nuevosErrores[idVariante].opciones - && nuevosErrores[idVariante].opciones[indiceOpcion] + nuevosErrores[idVariante] && + nuevosErrores[idVariante].opciones && + nuevosErrores[idVariante].opciones[indiceOpcion] ) { // Eliminar el error si ya no existe delete nuevosErrores[idVariante].opciones[indiceOpcion][campo]; @@ -471,8 +471,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = // Validar imagen principal if (imagenes.imagenProducto && imagenes.imagenProducto.size > tamanioMaximoBytes) { - erroresImagenes.imagenProducto - = 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; + erroresImagenes.imagenProducto = + 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; } // Validar imágenes de variantes @@ -539,17 +539,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = }); setCargando(false); return; - } - - // Validar estructura básica del producto - if (!imagenes.imagenProducto) { - setAlerta({ - tipo: 'error', - mensaje: 'Debes seleccionar una imagen principal para el producto.', - }); - setCargando(false); - return; - } + } // No se valida imagen para actualización, el backend usa la existente si no hay nueva + // La validación de imagen solo aplica para productos nuevos const variantesArray = Object.entries(variantes).map(([idVariante, datos]) => ({ identificador: idVariante, @@ -682,8 +673,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = ); if (archivosSobredimensionados.length > 0) { - const mensajeError - = archivosSobredimensionados.length > 1 + const mensajeError = + archivosSobredimensionados.length > 1 ? `${archivosSobredimensionados.length} imágenes exceden el tamaño máximo de 5MB.` : 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; From 65568df9b64760b33b1be68aabd3358c2733b029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Thu, 12 Jun 2025 15:37:45 -0600 Subject: [PATCH 20/21] =?UTF-8?q?refactor:=20Mejorar=20la=20legibilidad=20?= =?UTF-8?q?del=20c=C3=B3digo=20al=20ajustar=20la=20asignaci=C3=B3n=20de=20?= =?UTF-8?q?variables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Paginas/Productos/ListaProductos.jsx | 4 +-- src/hooks/Empleados/useCrearEmpleado.js | 10 +++---- src/hooks/Productos/ProductoFormProvider.jsx | 26 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Vistas/Paginas/Productos/ListaProductos.jsx b/src/Vistas/Paginas/Productos/ListaProductos.jsx index c16b6057..c43db86b 100644 --- a/src/Vistas/Paginas/Productos/ListaProductos.jsx +++ b/src/Vistas/Paginas/Productos/ListaProductos.jsx @@ -44,8 +44,8 @@ const ListaProductos = () => { const [imagenProducto, setImagenProducto] = useState(''); const [mostrarModalActualizarProducto, setMostrarModalActualizarProducto] = useState(false); const [openModalExportar, setAbrirPopUpExportar] = useState(false); - const MENSAJE_POPUP_EXPORTAR = - '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; + const MENSAJE_POPUP_EXPORTAR + = '¿Deseas exportar la lista de productos? El archivo será generado en formato .xlsx'; const manejarCancelarExportar = () => { setAbrirPopUpExportar(false); }; diff --git a/src/hooks/Empleados/useCrearEmpleado.js b/src/hooks/Empleados/useCrearEmpleado.js index 1129347e..edb25758 100644 --- a/src/hooks/Empleados/useCrearEmpleado.js +++ b/src/hooks/Empleados/useCrearEmpleado.js @@ -59,8 +59,8 @@ const validarDatosCrearEmpleado = (datos) => { } else if (!tieneNumero.test(datos.contrasenia)) { errores.contrasenia = 'Debe contener al menos un número'; } else if (datos.contrasenia.replace(/\s/g, '').length < 2) { - errores.contrasenia = - 'La contraseña no puede estar compuesta solo de espacios y un carácter especial'; + errores.contrasenia + = 'La contraseña no puede estar compuesta solo de espacios y un carácter especial'; } if (!datos.confirmarContrasenia || datos.confirmarContrasenia.trim() === '') { @@ -78,9 +78,9 @@ const validarDatosCrearEmpleado = (datos) => { errores.posicion = true; } if ( - !datos.cantidadPuntos || - isNaN(Number(datos.cantidadPuntos)) || - Number(datos.cantidadPuntos) < 0 + !datos.cantidadPuntos + || isNaN(Number(datos.cantidadPuntos)) + || Number(datos.cantidadPuntos) < 0 ) { errores.cantidadPuntos = 'La cantidad de puntos debe ser un número positivo'; } diff --git a/src/hooks/Productos/ProductoFormProvider.jsx b/src/hooks/Productos/ProductoFormProvider.jsx index c373b35b..d1f234fe 100644 --- a/src/hooks/Productos/ProductoFormProvider.jsx +++ b/src/hooks/Productos/ProductoFormProvider.jsx @@ -318,10 +318,10 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = const nuevosErrores = { ...prevErrores }; if ( - validacionPartial[idVariante] && - validacionPartial[idVariante].opciones && - validacionPartial[idVariante].opciones[indiceOpcion] && - validacionPartial[idVariante].opciones[indiceOpcion][campo] + validacionPartial[idVariante] + && validacionPartial[idVariante].opciones + && validacionPartial[idVariante].opciones[indiceOpcion] + && validacionPartial[idVariante].opciones[indiceOpcion][campo] ) { // Asegurarse de que existe la estructura para guardar el error if (!nuevosErrores[idVariante]) { @@ -335,12 +335,12 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = } // Guardar el error de este campo específico - nuevosErrores[idVariante].opciones[indiceOpcion][campo] = - validacionPartial[idVariante].opciones[indiceOpcion][campo]; + nuevosErrores[idVariante].opciones[indiceOpcion][campo] + = validacionPartial[idVariante].opciones[indiceOpcion][campo]; } else if ( - nuevosErrores[idVariante] && - nuevosErrores[idVariante].opciones && - nuevosErrores[idVariante].opciones[indiceOpcion] + nuevosErrores[idVariante] + && nuevosErrores[idVariante].opciones + && nuevosErrores[idVariante].opciones[indiceOpcion] ) { // Eliminar el error si ya no existe delete nuevosErrores[idVariante].opciones[indiceOpcion][campo]; @@ -471,8 +471,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = // Validar imagen principal if (imagenes.imagenProducto && imagenes.imagenProducto.size > tamanioMaximoBytes) { - erroresImagenes.imagenProducto = - 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; + erroresImagenes.imagenProducto + = 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; } // Validar imágenes de variantes @@ -673,8 +673,8 @@ export const ProductoFormProvider = ({ children, alCerrarFormularioProducto }) = ); if (archivosSobredimensionados.length > 0) { - const mensajeError = - archivosSobredimensionados.length > 1 + const mensajeError + = archivosSobredimensionados.length > 1 ? `${archivosSobredimensionados.length} imágenes exceden el tamaño máximo de 5MB.` : 'La imagen es demasiado grande. El tamaño máximo permitido es 5MB.'; From 1f6754cc3e13149dc13ac60c8836a313a5426a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Antonio=20Ben=C3=ADtez=20De=20La=20Portilla?= Date: Thu, 12 Jun 2025 17:31:26 -0600 Subject: [PATCH 21/21] =?UTF-8?q?refactor:=20Ajustar=20el=20tama=C3=B1o=20?= =?UTF-8?q?del=20t=C3=ADtulo=20del=20proveedor=20y=20mejorar=20la=20legibi?= =?UTF-8?q?lidad=20del=20c=C3=B3digo=20en=20useLeerProducto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Formularios/CamposActualizarProducto.jsx | 3 +- src/hooks/Productos/useLeerProducto.js | 33 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx index f42bd12f..9f401e44 100644 --- a/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx +++ b/src/Vistas/Componentes/Organismos/Formularios/CamposActualizarProducto.jsx @@ -164,7 +164,7 @@ const CamposActualizarProducto = memo( if (!producto) return null; return ( <> - + diff --git a/src/hooks/Productos/useLeerProducto.js b/src/hooks/Productos/useLeerProducto.js index f3b146d2..4410aa32 100644 --- a/src/hooks/Productos/useLeerProducto.js +++ b/src/hooks/Productos/useLeerProducto.js @@ -2,29 +2,30 @@ import { useEffect, useState } from 'react'; import { RepositorioLeerProducto } from '@Repositorios/Productos/RepositorioLeerProducto.js'; export const useLeerProducto = (idProducto) => { - const [detalleProducto, setDetalleProducto] = useState(null) - const [cargando, setCargando] =useState(true) - const [error, setError] = useState(null) + const [detalleProducto, setDetalleProducto] = useState(null); + const [cargando, setCargando] = useState(true); + const [error, setError] = useState(null); useEffect(() => { const obtenerInfoProducto = async () => { - setCargando(true) - setError(null) + setCargando(true); + setError(null); - try{ + try { const productoInfo = await RepositorioLeerProducto.obtenerPorId(idProducto); - setDetalleProducto(productoInfo) - }catch (err) { - setError(err.message) - }finally { - setCargando(false) + console.log('Producto obtenido:', productoInfo); + setDetalleProducto(productoInfo); + } catch (err) { + setError(err.message); + } finally { + setCargando(false); } - } + }; - if(idProducto) { - obtenerInfoProducto() + if (idProducto) { + obtenerInfoProducto(); } }, [idProducto]); - return {detalleProducto, cargando, error} -} \ No newline at end of file + return { detalleProducto, cargando, error }; +};